From 0aa6794b4b080029a9d2e6ed97903b388f2bf3f7 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 9 Sep 2025 09:22:06 +0200 Subject: [PATCH 1/7] Don't allow instantiated ResultCaps to subsume anything We map them to fresh caps but their hidden sets should not accept new elements. --- compiler/src/dotty/tools/dotc/cc/Capability.scala | 8 +++++++- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 7 +++++-- tests/neg-custom-args/captures/frozen-result.scala | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/frozen-result.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 41a084a5d87e..82142ac86e77 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -1031,7 +1031,13 @@ object Capabilities: override def mapCapability(c: Capability, deep: Boolean) = c match case c @ ResultCap(binder) => if localBinders.contains(binder) then c // keep bound references - else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap + else + // Create a fresh skolem that does not subsume anything + def freshSkolem = + val c = FreshCap(origin) + c.hiddenSet.markSolved(provisional = false) + c + seen.getOrElseUpdate(c, freshSkolem) // map free references to FreshCap case _ => super.mapCapability(c, deep) end subst diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 9eec54ef3dd0..8c26011a538e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1206,6 +1206,7 @@ object CaptureSet: if alias ne this then alias.add(elem) else def addToElems() = + assert(!isConst) includeElem(elem) deps.foreach: dep => assert(dep != this) @@ -1378,8 +1379,10 @@ object CaptureSet: * but the special state VarState.Separate overrides this. */ def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = - hidden.add(elem)(using ctx, this) - true + if hidden.isConst then false + else + hidden.add(elem)(using ctx, this) + true /** If root1 and root2 belong to the same binder but have different originalBinders * it means that one of the roots was mapped to the binder of the other by a diff --git a/tests/neg-custom-args/captures/frozen-result.scala b/tests/neg-custom-args/captures/frozen-result.scala new file mode 100644 index 000000000000..e82d751c0ab9 --- /dev/null +++ b/tests/neg-custom-args/captures/frozen-result.scala @@ -0,0 +1,9 @@ +class B + +def Test(consume b1: B^, consume b2: B^) = + def f(): B^ = B() + var x: B^ = f() + x = b1 // error separation but should be OK, see #23889 + var y = f() + y = b2 // error + From 02c70480b8991c8de2fe583e2ab5cd8b0f248955 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 9 Sep 2025 12:47:57 +0200 Subject: [PATCH 2/7] Refine printing of recursive types Show the self type binder of a RecType only if the RecType was referred to in its body. This is needed so that we can add RecTypes to class types without polluting error messages. --- .../tools/dotc/printing/PlainPrinter.scala | 17 ++++++++++++----- tests/neg/show-rec-types.check | 7 +++++++ tests/neg/show-rec-types.scala | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/neg/show-rec-types.check create mode 100644 tests/neg/show-rec-types.scala diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 4a23f00f3c53..c9dd96a85eff 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -39,6 +39,7 @@ class PlainPrinter(_ctx: Context) extends Printer { private var elideCapabilityCaps = false private var openRecs: List[RecType] = Nil + private var referredRecs: Set[RecType] = Set.empty protected def maxToTextRecursions: Int = 100 @@ -260,11 +261,15 @@ class PlainPrinter(_ctx: Context) extends Printer { refinementChain(tp).reverse: @unchecked toTextLocal(parent) ~ "{" ~ Text(refined map toTextRefinement, "; ").close ~ "}" case tp: RecType => - try { + try openRecs = tp :: openRecs - "{" ~ selfRecName(openRecs.length) ~ " => " ~ toTextGlobal(tp.parent) ~ "}" - } - finally openRecs = openRecs.tail + val parentTxt = toTextGlobal(tp.parent) + if referredRecs.contains(tp) || printDebug + then "{" ~ selfRecName(openRecs.length) ~ " => " ~ parentTxt ~ "}" + else parentTxt + finally + referredRecs -= openRecs.head + openRecs = openRecs.tail case AndType(tp1, tp2) => changePrec(AndTypePrec) { toText(tp1) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(tp2) } } case OrType(tp1, tp2) => @@ -457,7 +462,9 @@ class PlainPrinter(_ctx: Context) extends Printer { ParamRefNameString(pref) ~ hashStr(pref.binder) case tp: RecThis => val idx = openRecs.reverse.indexOf(tp.binder) - if (idx >= 0) selfRecName(idx + 1) + if idx >= 0 then + referredRecs += tp.binder + selfRecName(idx + 1) else "{...}.this" // TODO move underlying type to an addendum, e.g. ... z3 ... where z3: ... case tp: SkolemType => def reprStr = toText(tp.repr) ~ hashStr(tp) diff --git a/tests/neg/show-rec-types.check b/tests/neg/show-rec-types.check new file mode 100644 index 000000000000..d8303d2810cd --- /dev/null +++ b/tests/neg/show-rec-types.check @@ -0,0 +1,7 @@ +-- [E007] Type Mismatch Error: tests/neg/show-rec-types.scala:13:13 ---------------------------------------------------- +13 |val _: Int = c // error + | ^ + | Found: (c : {z1 => C{val x: {z2 => C{val x: z2.T}}; val y: z1.T}}) + | Required: Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/show-rec-types.scala b/tests/neg/show-rec-types.scala new file mode 100644 index 000000000000..2d77dc5235a1 --- /dev/null +++ b/tests/neg/show-rec-types.scala @@ -0,0 +1,15 @@ +trait C { + type T + val x: Any + val y: Any +} + +val c: C { + val x: C { + val x: this.T + } + val y: this.T +} = ??? +val _: Int = c // error + + From 3c9d209e9a52dd9aa89356ef0312c8cc47b22183 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 9 Sep 2025 12:53:10 +0200 Subject: [PATCH 3/7] Wrap inferred class types in RecTypes --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b925442e8feb..888f53df02b5 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -266,19 +266,19 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp.typeSymbol match case cls: ClassSymbol if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) => - cls.paramGetters.foldLeft(tp) { (core, getter) => - if atPhase(thisPhase.next)(getter.hasTrackedParts) - && getter.isRefiningParamAccessor - && !refiningNames.contains(getter.name) // Don't add a refinement if we have already an explicit one for the same name - then - val getterType = - mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias - RefinedType(core, getter.name, - CapturingType(getterType, new CaptureSet.RefiningVar(ctx.owner))) - .showing(i"add capture refinement $tp --> $result", capt) - else - core - } + RecType: _ => + cls.paramGetters.foldLeft(tp): (core, getter) => + if atPhase(thisPhase.next)(getter.hasTrackedParts) + && getter.isRefiningParamAccessor + && !refiningNames.contains(getter.name) // Don't add a refinement if we have already an explicit one for the same name + then + val getterType = + mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias + RefinedType(core, getter.name, + CapturingType(getterType, new CaptureSet.RefiningVar(ctx.owner))) + .showing(i"add capture refinement $tp --> $result", capt) + else + core case _ => tp case _ => tp From efa14d528aaafc12cc7ccf9bb42b6fc73e9b77e8 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 9 Sep 2025 23:16:52 +0200 Subject: [PATCH 4/7] Add a prefix to FreshCaps The prefix is mapped as a normal type. It determines whether the hidden set allows to add new elements. ThisType and NoPrefix prefixes allow it, other prefixes forbid it. --- .../src/dotty/tools/dotc/cc/Capability.scala | 37 ++++++++++++++++--- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 4 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 2 + .../tools/dotc/printing/PlainPrinter.scala | 9 ++++- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 82142ac86e77..086db35470a1 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -148,10 +148,21 @@ object Capabilities: * @param origin an indication where and why the FreshCap was created, used * for diagnostics */ - case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability: - val hiddenSet = CaptureSet.HiddenSet(owner, this: @unchecked) + case class FreshCap(val prefix: Type) + (val owner: Symbol, val origin: Origin, origHidden: CaptureSet.HiddenSet | Null) + (using @constructorOnly ctx: Context) + extends RootCapability: + val hiddenSet = + if origHidden == null then CaptureSet.HiddenSet(owner, this: @unchecked) + else origHidden // fails initialization check without the @unchecked + def derivedFreshCap(newPrefix: Type)(using Context): FreshCap = + if newPrefix eq prefix then this + else FreshCap(newPrefix)(owner, origin, hiddenSet) + + //assert(rootId != 10, i"fresh $prefix, ${ctx.owner}") + /** Is this fresh cap (definitely) classified? If that's the case, the * classifier cannot be changed anymore. * We need to distinguish `FreshCap`s that can still be classified from @@ -198,8 +209,10 @@ object Capabilities: i"a fresh root capability$classifierStr$originStr" object FreshCap: + def apply(prefix: Type, origin: Origin)(using Context): FreshCap = + new FreshCap(prefix)(ctx.owner, origin, null) def apply(origin: Origin)(using Context): FreshCap = - FreshCap(ctx.owner, origin) + apply(ctx.owner.skipWeakOwner.thisType, origin) /** A root capability associated with a function type. These are conceptually * existentially quantified over the function's result type. @@ -703,12 +716,24 @@ object Capabilities: (this eq y) || this.match case x: FreshCap => + def classifierOK = + if y.tryClassifyAs(x.hiddenSet.classifier) then true + else + capt.println(i"$y cannot be classified as $x") + false + + def prefixAllowsAddHidden: Boolean = x.prefix match + case NoPrefix | _: ThisType => true + case _ if CCState.collapseFresh => true + case pre => + capt.println(i"fresh not open $x, ${x.rootId}, $pre, ${x.ccOwner.skipWeakOwner.thisType}") + false + vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y))) || x.acceptsLevelOf(y) - && ( y.tryClassifyAs(x.hiddenSet.classifier) - || { capt.println(i"$y cannot be classified as $x"); false } - ) + && classifierOK && canAddHidden + && prefixAllowsAddHidden && vs.addHidden(x.hiddenSet, y) case x: ResultCap => val result = y match diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 8c26011a538e..822461bd588b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -559,8 +559,10 @@ object CaptureSet: def universal(using Context): Const = Const(SimpleIdentitySet(GlobalCap)) + def fresh(prefix: Type, origin: Origin)(using Context): Const = + FreshCap(prefix, origin).singletonCaptureSet def fresh(origin: Origin)(using Context): Const = - FreshCap(origin).singletonCaptureSet + fresh(ctx.owner.thisType, origin) /** The shared capture set `{cap.rd}` */ def shared(using Context): Const = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 888f53df02b5..fe1e8b776d66 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -694,7 +694,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if cls.derivesFrom(defn.Caps_Capability) then // If cls is a capability class, we need to add a fresh readonly capability to // ensure we cannot treat the class as pure. - CaptureSet.fresh(Origin.InDecl(cls)).readOnly.subCaptures(cs) + CaptureSet.fresh(cls.thisType, Origin.InDecl(cls)).readOnly.subCaptures(cs) CapturingType(cinfo.selfType, cs) // Compute new parent types diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8070dcbeda30..29c244db8c52 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6312,6 +6312,8 @@ object Types extends TypeUtils { null def mapCapability(c: Capability, deep: Boolean = false): Capability | (CaptureSet, Boolean) = c match + case c @ FreshCap(prefix) => + c.derivedFreshCap(apply(prefix)) case c: RootCapability => c case Reach(c1) => mapCapability(c1, deep = true) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index c9dd96a85eff..315116db1678 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -496,8 +496,13 @@ class PlainPrinter(_ctx: Context) extends Printer { def classified = if c.hiddenSet.classifier == defn.AnyClass then "" else s" classified as ${c.hiddenSet.classifier.name.show}" - if ccVerbose then s"" - else "cap" + def prefixTxt: Text = c.prefix match + case NoPrefix | _: ThisType => "" + case pre => pre.show ~ "." + def core: Text = + if ccVerbose then s"" + else "cap" + prefixTxt ~ core case tp: TypeProxy => homogenize(tp) match case tp: SingletonType => toTextRef(tp) From 7987429c2f533f260b86977cd298b254a75a9f7d Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 10 Sep 2025 10:26:56 +0200 Subject: [PATCH 5/7] Hash-cons FreshCaps Have at most one FreshCap for a given prefix and hidden set. --- compiler/src/dotty/tools/dotc/cc/Capability.scala | 6 +++++- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 086db35470a1..1f9b55de8add 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -159,7 +159,11 @@ object Capabilities: def derivedFreshCap(newPrefix: Type)(using Context): FreshCap = if newPrefix eq prefix then this - else FreshCap(newPrefix)(owner, origin, hiddenSet) + else if newPrefix eq hiddenSet.owningCap.prefix then + hiddenSet.owningCap + else + hiddenSet.derivedCaps + .getOrElseUpdate(newPrefix, FreshCap(newPrefix)(owner, origin, hiddenSet)) //assert(rootId != 10, i"fresh $prefix, ${ctx.owner}") diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 822461bd588b..ec9f47aed401 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -12,7 +12,7 @@ import annotation.internal.sharable import reporting.trace import printing.{Showable, Printer} import printing.Texts.* -import util.{SimpleIdentitySet, Property} +import util.{SimpleIdentitySet, Property, EqHashMap} import typer.ErrorReporting.Addenda import scala.collection.{mutable, immutable} import TypeComparer.ErrorNote @@ -1168,6 +1168,9 @@ object CaptureSet: override def owner = givenOwner + /** The FreshCaps generated by derivedFreshCap, indexed by prefix */ + val derivedCaps = new EqHashMap[Type, FreshCap]() + //assert(id != 3) description = i"of elements subsumed by a fresh cap in $initialOwner" From 27154fab206b5fc6859420a6a3fec8b8aaa7d8a0 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 10 Sep 2025 19:24:00 +0200 Subject: [PATCH 6/7] Compare RecTypes earlier We need to align RecTypes before comparing any wrapped CapturingTypes --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index ca6fff87ee14..a6cff7dcf1b8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -367,6 +367,14 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp2: LazyRef => isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref) + case tp2: RecType => + def compareRec = tp1.safeDealias match + case tp1: RecType => + val rthis1 = tp1.recThis + recur(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) + case _ => + secondTry + compareRec case CapturingType(_, _) => secondTry case tp2: AnnotatedType if !tp2.isRefining => @@ -719,7 +727,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling compareRefined case tp2: RecType => def compareRec = tp1.safeDealias match { - case tp1: RecType => + case tp1: RecType => // TODO this is now also in firstTry, needed here? val rthis1 = tp1.recThis recur(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) case NoType => false From 0cbf315115019d62efaac66ccf6187efab66f0f9 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 11 Sep 2025 20:09:39 +0200 Subject: [PATCH 7/7] Keep track of provenance of capturing types --- .../tools/dotc/cc/CaptureAnnotation.scala | 24 +++++++++++++++++++ .../src/dotty/tools/dotc/cc/CaptureOps.scala | 19 +++++++++++---- .../dotty/tools/dotc/cc/CapturingType.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 11 +++++---- .../src/dotty/tools/dotc/cc/ccConfig.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 8 ------- 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 60183126bca2..e0f499a4ab34 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -25,6 +25,30 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte import CaptureAnnotation.* import tpd.* + private var myOrigins: Set[AnnotatedType] | Null = null + + def origins: Set[AnnotatedType] = + if myOrigins == null then myOrigins = Set() + myOrigins.nn + + def withOrigins(origins: Set[AnnotatedType]): CaptureAnnotation = + if myOrigins == null then + myOrigins = origins + this + else if origins.subsetOf(myOrigins.nn) then + this + else + CaptureAnnotation(refs, boxed)(cls).withOrigins(myOrigins.nn ++ origins) + + private var myProvenance: Set[AnnotatedType] | Null = null + + def provenance: Set[AnnotatedType] = + if myProvenance == null then + myProvenance = origins ++ origins.flatMap: + case AnnotatedType(_, ann: CaptureAnnotation) => ann.provenance + case _ => Set() + myProvenance.nn + /** A cache for the version of this annotation which differs in its boxed status. */ var boxDual: CaptureAnnotation | Null = null diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index d5f8b635fa58..789f535c0cbd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -179,14 +179,23 @@ extension (tp: Type) && !cs.keepAlways then tp else tp match - case CapturingType(parent, cs1) => parent.capturing(cs1 ++ cs) + case tp @ CapturingType(parent, cs1) => parent.capturing(cs1 ++ cs).withOrigin(tp) case _ => CapturingType(tp, cs) + def withOrigins(origins: Set[AnnotatedType])(using Context) = tp match + case tp @ AnnotatedType(p, ann: CaptureAnnotation) => + tp.derivedAnnotatedType(p, ann.withOrigins(origins)) + case _ => + tp + + def withOrigin(origin: AnnotatedType)(using Context) = + withOrigins(Set(origin)) + /** @pre `tp` is a CapturingType */ def derivedCapturingType(parent: Type, refs: CaptureSet)(using Context): Type = tp match case tp @ CapturingType(p, r) => if (parent eq p) && (refs eq r) then tp - else CapturingType(parent, refs, tp.isBoxed) + else CapturingType(parent, refs, tp.isBoxed).withOrigin(tp) /** If this is a unboxed capturing type with nonempty capture set, its boxed version. * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. @@ -196,7 +205,7 @@ extension (tp: Type) case tp @ CapturingType(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty => tp.annot match case ann: CaptureAnnotation if !parent.derivesFrom(defn.Caps_CapSet) => - AnnotatedType(parent, ann.boxedAnnot) + AnnotatedType(parent, ann.boxedAnnot).withOrigin(tp) case ann => tp case tp: RealTypeBounds => tp.derivedTypeBounds(tp.lo.boxed, tp.hi.boxed) @@ -209,7 +218,7 @@ extension (tp: Type) */ def unboxed(using Context): Type = tp.dealias match case tp @ CapturingType(parent, refs) if tp.isBoxed && !refs.isAlwaysEmpty => - CapturingType(parent, refs) + CapturingType(parent, refs).withOrigin(tp) case tp: RealTypeBounds => tp.derivedTypeBounds(tp.lo.unboxed, tp.hi.unboxed) case _ => @@ -303,7 +312,7 @@ extension (tp: Type) case ref: Capability if ref.isTracked || ref.isInstanceOf[DerivedCapability] => ref.singletonCaptureSet case _ => refs - CapturingType(parent, refs1, boxed) + CapturingType(parent, refs1, boxed).withOrigin(tp) case _ => tp diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index b9545bf39b8b..5fd1691b60e9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -37,7 +37,7 @@ object CapturingType: if refs.isAlwaysEmpty && !refs.keepAlways then parent else parent match case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed => - apply(parent1, refs ++ refs1, boxed) + apply(parent1, refs ++ refs1, boxed).withOrigin(parent) case _ => if parent.derivesFromMutable then refs.setMutable() refs.adoptClassifier(parent.classifier) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index fe1e8b776d66..d5a25a2b4364 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -204,11 +204,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Pull out an embedded capture set from a part of `tp` */ def normalizeCaptures(tp: Type)(using Context): Type = tp match case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => - CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, parent.isBoxed) + CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, parent.isBoxed).withOrigin(parent) case tp: RecType => tp.parent match case parent @ CapturingType(parent1, refs) => - CapturingType(tp.derivedRecType(parent1), refs, parent.isBoxed) + CapturingType(tp.derivedRecType(parent1), refs, parent.isBoxed).withOrigin(parent) case _ => tp // can return `tp` here since unlike RefinedTypes, RecTypes are never created // by `mapInferred`. Hence if the underlying type admits capture variables @@ -216,13 +216,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case AndType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => assert(tp1.isBoxed == tp2.isBoxed) CapturingType(AndType(parent1, parent2), refs1 ** refs2, tp1.isBoxed) + .withOrigins(Set(tp1, tp2)) case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => assert(tp1.isBoxed == tp2.isBoxed) CapturingType(OrType(parent1, parent2, tp.isSoft), refs1 ++ refs2, tp1.isBoxed) + .withOrigins(Set(tp1, tp2)) case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2) => - CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed) + CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed).withOrigin(tp1) case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) => - CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) + CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed).withOrigin(tp2) case tp @ AppliedType(tycon, args) if !defn.isFunctionClass(tp.dealias.typeSymbol) && (tp.dealias eq tp) => tp.derivedAppliedType(tycon, args.mapConserve(_.boxDeeply)) @@ -266,7 +268,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp.typeSymbol match case cls: ClassSymbol if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) => - RecType: _ => cls.paramGetters.foldLeft(tp): (core, getter) => if atPhase(thisPhase.next)(getter.hasTrackedParts) && getter.isRefiningParamAccessor diff --git a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala index 57ff55fb5c6e..c44c03cb1c27 100644 --- a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala +++ b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala @@ -54,7 +54,7 @@ object ccConfig: /** Not used currently. Handy for trying out new features */ def newScheme(using ctx: Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.9`) def allowUse(using Context): Boolean = Feature.sourceVersion.stable.isAtMost(SourceVersion.`3.7`) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index a6cff7dcf1b8..c4cb7bc94f10 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -367,14 +367,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp2: LazyRef => isBottom(tp1) || !tp2.evaluating && recur(tp1, tp2.ref) - case tp2: RecType => - def compareRec = tp1.safeDealias match - case tp1: RecType => - val rthis1 = tp1.recThis - recur(tp1.parent, tp2.parent.substRecThis(tp2, rthis1)) - case _ => - secondTry - compareRec case CapturingType(_, _) => secondTry case tp2: AnnotatedType if !tp2.isRefining =>