From 29a6a9f0c55a530c9bfb9abcb59565713b63dc23 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 12 May 2025 11:46:59 +0200 Subject: [PATCH 1/7] Changes to existing code base that mirror changes in refactor-capabilities --- .../src/dotty/tools/dotc/cc/CaptureRef.scala | 44 ++++++++++--------- .../src/dotty/tools/dotc/core/Types.scala | 3 +- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 21e05e4663b3..145a90b4e294 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -139,9 +139,10 @@ trait CaptureRef extends TypeProxy, ValueType: hidden.owner case ref: NamedType => if ref.isCap then NoSymbol - else ref.prefix match - case prefix: CaptureRef => prefix.ccOwner - case _ => ref.symbol + else ref.pathRoot match + case ref: ThisType => ref.cls + case ref: NamedType => ref.symbol + case _ => NoSymbol case ref: ThisType => ref.cls case QualifiedCapability(ref1) => @@ -222,7 +223,7 @@ trait CaptureRef extends TypeProxy, ValueType: def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match case info: SingletonCaptureRef => test(info) case CapturingType(parent, _) => - if this.derivesFrom(defn.Caps_CapSet) then test(info) + if this.derivesFrom(defn.Caps_CapSet) && false then test(info) /* If `this` is a capture set variable `C^`, then it is possible that it can be reached from term variables in a reachability chain through the context. @@ -235,7 +236,7 @@ trait CaptureRef extends TypeProxy, ValueType: case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) case _ => false - (this eq y) + try (this eq y) || maxSubsumes(y, canAddHidden = !vs.isOpen) || y.match case y: TermRef if !y.isCap => @@ -262,9 +263,13 @@ trait CaptureRef extends TypeProxy, ValueType: // They can be other capture set variables, which are bounded by `CapSet`, // like `def test[X^, Y^, Z >: X <: Y]`. y.info match - case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi) + case TypeBounds(_, hi @ CapturingType(parent, refs)) if parent.derivesFrom(defn.Caps_CapSet) => + refs.elems.forall(this.subsumes) + case TypeBounds(_, hi: CaptureRef) => + this.subsumes(hi) case _ => y.captureSetOfInfo.elems.forall(this.subsumes) case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) || this.derivesFrom(defn.Caps_CapSet) => + assert(false, this) /* The second condition in the guard is for `this` being a `CapSet^{a,b...}` and etablishing a potential reachability chain through `y`'s capture to a binding with `this`'s capture set (cf. `CapturingType` case in `def viaInfo` above for more context). @@ -277,13 +282,18 @@ trait CaptureRef extends TypeProxy, ValueType: case x: TypeRef if assumedContainsOf(x).contains(y) => true case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => x.info match + case TypeBounds(lo @ CapturingType(parent, refs), _) if parent.derivesFrom(defn.Caps_CapSet) => + refs.elems.exists(_.subsumes(y)) case TypeBounds(lo: CaptureRef, _) => lo.subsumes(y) case _ => x.captureSetOfInfo.elems.exists(_.subsumes(y)) case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => - refs.elems.exists(_.subsumes(y)) + assert(false, this) case _ => false + catch case ex: AssertionError => + println(i"error while subsumes $this >> $y") + throw ex end subsumes /** This is a maximal capability that subsumes `y` in given context and VarState. @@ -312,7 +322,7 @@ trait CaptureRef extends TypeProxy, ValueType: else !y.stripReadOnly.isCap && !yIsExistential - && !y.isInstanceOf[ParamRef] + && !y.stripReadOnly.isInstanceOf[ParamRef] vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y))) || levelOK @@ -325,21 +335,15 @@ trait CaptureRef extends TypeProxy, ValueType: if !result then ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y)) result + case _ if this.isCap => + if y.isCap then true + else if yIsExistential then false + else y.derivesFromSharedCapability + || canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot case _ => y match case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) - case _ if this.isCap => - y.isCap - || y.derivesFromSharedCapability - || !yIsExistential - && canAddHidden - && vs != VarState.HardSeparate - && (CCState.capIsRoot - // || { println(i"no longer $this maxSubsumes $y, ${y.isCap}"); false } // debug - ) - || false - case _ => - false + case _ => false /** `x covers y` if we should retain `y` when computing the overlap of * two footprints which have `x` respectively `y` as elements. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c2c508dc27ea..871e8f27712c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6173,8 +6173,7 @@ object Types extends TypeUtils { ensureTrackable(result) /** A restriction of the inverse to a function on tracked CaptureRefs */ - def backward(ref: CaptureRef): CaptureRef = inverse(ref) match - case result: CaptureRef if result.isTrackableRef => result + def backward(ref: CaptureRef): CaptureRef = inverse.forward(ref) /** Fuse with another map */ def fuse(next: BiTypeMap)(using Context): Option[TypeMap] = None From 6078b03a887506a67a668973f383372c181c20f1 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 13 May 2025 18:03:58 +0200 Subject: [PATCH 2/7] Refactor type maps to split out capability handling Note i15923 does not signal a leak anymore. I moved it and some variants to pending. Note: There seems to be something more fundamentally wrong with this test: We get an infinite recursion for variant i15923b. --- .../tools/dotc/cc/CaptureAnnotation.scala | 6 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 17 ++-- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 34 ++++--- .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 28 ++++-- compiler/src/dotty/tools/dotc/cc/root.scala | 90 ++++++++++--------- .../dotty/tools/dotc/core/Substituters.scala | 34 +++++-- .../src/dotty/tools/dotc/core/Types.scala | 68 ++++++++++---- .../dotty/tools/dotc/transform/Recheck.scala | 2 +- .../neg-custom-args/captures/i15923.scala | 0 .../neg-custom-args/captures/i15923a.scala | 13 +++ .../neg-custom-args/captures/i15923b.scala | 13 +++ tests/pending/pos/i4560.scala | 12 +++ 13 files changed, 217 insertions(+), 102 deletions(-) rename tests/{ => pending}/neg-custom-args/captures/i15923.scala (100%) create mode 100644 tests/pending/neg-custom-args/captures/i15923a.scala create mode 100644 tests/pending/neg-custom-args/captures/i15923b.scala create mode 100644 tests/pending/pos/i4560.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 2be492ed6189..0498ccf7b3d4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -63,9 +63,11 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte override def mapWith(tm: TypeMap)(using Context) = val elems = refs.elems.toList - val elems1 = elems.mapConserve(tm) + val elems1 = elems.mapConserve(tm.mapCapability(_)) if elems1 eq elems then this - else if elems1.forall(_.isTrackableRef) + else if elems1.forall: + case elem1: CaptureRef => elem1.isTrackableRef + case _ => false then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed) else EmptyAnnotation diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f53650f425b2..b6acbab29bab 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -114,6 +114,7 @@ extension (tp: Type) !tp.underlying.exists // might happen during construction of lambdas || tp.derivesFrom(defn.Caps_CapSet) case root.Result(_) => true + case root.Fresh(_) => true case AnnotatedType(parent, annot) => defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef case _ => @@ -143,9 +144,9 @@ extension (tp: Type) if dcs.isAlwaysEmpty then tp.captureSet else tp match case tp @ ReachCapability(_) => - tp.singletonCaptureSet + assert(false); tp.singletonCaptureSet case ReadOnlyCapability(ref) => - ref.deepCaptureSet(includeTypevars).readOnly + assert(false); ref.deepCaptureSet(includeTypevars).readOnly case tp: SingletonCaptureRef if tp.isTrackableRef => tp.reach.singletonCaptureSet case _ => @@ -195,9 +196,12 @@ extension (tp: Type) * are of the form this.C but their pathroot is still this.C, not this. */ final def pathRoot(using Context): Type = tp.dealias match - case tp1: NamedType - if tp1.symbol.maybeOwner.isClass && tp1.symbol != defn.captureRoot && !tp1.symbol.is(TypeParam) => - tp1.prefix.pathRoot + case tp1: NamedType => + if tp1.symbol.maybeOwner.isClass && tp1.symbol != defn.captureRoot && !tp1.symbol.is(TypeParam) then + tp1.prefix match + case pre: CaptureRef => pre.pathRoot + case _ => tp1 + else tp1 case tp1 => tp1 /** If this part starts with `C.this`, the class `C`. @@ -214,7 +218,8 @@ extension (tp: Type) tp1.prefix match case _: ThisType | NoPrefix => tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) - case prefix => prefix.isParamPath + case prefix: CaptureRef => prefix.isParamPath + case _ => false case _: ParamRef => true case _ => false diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 882f557fec24..190fd1aae141 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -319,7 +319,7 @@ sealed abstract class CaptureSet extends Showable: def map(tm: TypeMap)(using Context): CaptureSet = tm match case tm: BiTypeMap => - val mappedElems = elems.map(tm.forward) + val mappedElems = elems.map(tm.mapCapability(_)) if isConst then if mappedElems == elems then this else Const(mappedElems) @@ -487,7 +487,7 @@ object CaptureSet: override def toString = elems.toString end Const - case class EmptyWithProvenance(ref: CaptureRef, mapped: Type) extends Const(SimpleIdentitySet.empty): + case class EmptyWithProvenance(ref: CaptureRef, mapped: CaptureSet) extends Const(SimpleIdentitySet.empty): override def optionalInfo(using Context): String = if ctx.settings.YccDebug.value then i" under-approximating the result of mapping $ref to $mapped" @@ -587,8 +587,7 @@ object CaptureSet: */ private def checkSkippedMaps(elem: CaptureRef)(using Context): Unit = for tm <- skippedMaps do - val elem1 = tm(elem) - for elem1 <- tm(elem).captureSet.elems do + for elem1 <- extrapolateCaptureRef(elem, tm, variance = 1).elems do assert(elem.subsumes(elem1), i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this") @@ -817,14 +816,14 @@ object CaptureSet: override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if origin eq source then - val mappedElem = bimap.forward(elem) + val mappedElem = bimap.mapCapability(elem) if accountsFor(mappedElem) then CompareResult.OK else addNewElem(mappedElem) else if accountsFor(elem) then CompareResult.OK else try - source.tryInclude(bimap.backward(elem), this) + source.tryInclude(bimap.inverse.mapCapability(elem), this) .showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug) .andAlso(addNewElem(elem)) catch case ex: AssertionError => @@ -1031,15 +1030,12 @@ object CaptureSet: * - Otherwise assertion failure */ def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = - val r1 = tm(r) - val upper = r1.captureSet - def isExact = - upper.isAlwaysEmpty - || upper.isConst && upper.elems.size == 1 && upper.elems.contains(r1) - || r.derivesFrom(defn.Caps_CapSet) - if variance > 0 || isExact then upper - else if variance < 0 then CaptureSet.EmptyWithProvenance(r, r1) - else upper.maybe + tm.mapCapability(r) match + case c: CaptureRef => c.captureSet + case (cs: CaptureSet, exact) => + if cs.isAlwaysEmpty || exact || variance > 0 then cs + else if variance < 0 then CaptureSet.EmptyWithProvenance(r, cs) + else cs.maybe /** Apply `f` to each element in `xs`, and join result sets with `++` */ def mapRefs(xs: Refs, f: CaptureRef => CaptureSet)(using Context): CaptureSet = @@ -1289,9 +1285,11 @@ object CaptureSet: def mapRef(ref: CaptureRef): CaptureRef - def apply(t: Type) = t match - case t: CaptureRef if t.isTrackableRef => mapRef(t) - case _ => mapOver(t) + def apply(t: Type) = mapOver(t) + + override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + case c: CaptureRef if c.isTrackableRef => mapRef(c) + case _ => super.mapCapability(c, deep) override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index d15fa06e64fc..04cacac06a64 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -441,7 +441,7 @@ class CheckCaptures extends Recheck, SymTransformer: markFree(sym, sym.termRef, tree) def markFree(sym: Symbol, ref: CaptureRef, tree: Tree)(using Context): Unit = - if sym.exists && ref.isTracked then markFree(ref.captureSet, tree) + if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree) /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 88bb883df67e..6d812eb1f8c8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -209,6 +209,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: innerApply(tp) finally isTopLevel = saved + override def mapArg(arg: Type, tparam: ParamInfo): Type = + super.mapArg(Recheck.mapExprType(arg), tparam) + /** Map parametric functions with results that have a capture set somewhere * to dependent functions. */ @@ -504,12 +507,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def add = new TypeTraverser: var reach = false def traverse(t: Type): Unit = t match - case root.Fresh(hidden) => - if reach then hidden.elems += ref.reach - else if ref.isTracked then hidden.elems += ref - case t @ CapturingType(_, _) if t.isBoxed && !reach => - reach = true - try traverseChildren(t) finally reach = false + case t @ CapturingType(parent, refs) => + val saved = reach + reach |= t.isBoxed + try + traverse(parent) + for case root.Fresh(hidden) <- refs.elems.iterator do + if reach then hidden.elems += ref.reach + else if ref.isTracked then hidden.elems += ref + finally reach = saved case _ => traverseChildren(t) if ref.isTrackableRef then add.traverse(tp) @@ -660,9 +666,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def paramsToCap(mt: Type)(using Context): Type = mt match case mt: MethodType => - mt.derivedLambdaType( - paramInfos = mt.paramInfos.map(root.freshToCap), - resType = paramsToCap(mt.resType)) + try + mt.derivedLambdaType( + paramInfos = mt.paramInfos.map(root.freshToCap), + resType = paramsToCap(mt.resType)) + catch case ex: AssertionError => + println(i"error while mapping params ${mt.paramInfos} of $sym") + throw ex case mt: PolyType => mt.derivedLambdaType(resType = paramsToCap(mt.resType)) case _ => mt diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala index 36dd1c5661e3..374d7d13f279 100644 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ b/compiler/src/dotty/tools/dotc/cc/root.scala @@ -154,21 +154,6 @@ object root: override def eql(that: Annotation) = that match case Annot(kind) => this.kind eq kind case _ => false - - /** Special treatment of `SubstBindingMaps` which can change the binder of - * Result instances - */ - override def mapWith(tm: TypeMap)(using Context) = kind match - case Kind.Result(binder) => tm match - case tm: Substituters.SubstBindingMap[MethodType] @unchecked if tm.from eq binder => - derivedAnnotation(tm.to) - case tm: Substituters.SubstBindingsMap => - var i = 0 - while i < tm.from.length && (tm.from(i) ne binder) do i += 1 - if i < tm.from.length then derivedAnnotation(tm.to(i).asInstanceOf[MethodType]) - else this - case _ => this - case _ => this end Annot def cap(using Context): TermRef = defn.captureRoot.termRef @@ -222,8 +207,7 @@ object root: override def apply(t: Type) = if variance <= 0 then t else t match - case t: CaptureRef if t.isCap => - Fresh(origin) + case root(_) => assert(false) case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => t case t @ CapturingType(_, _) => @@ -237,6 +221,11 @@ object root: case _ => mapFollowingAliases(t) + override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + case c: CaptureRef if c.isCap => Fresh(origin) + case root(_) => c + case _ => super.mapCapability(c, deep) + override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse => assert(false); Some(IdentityTypeMap) case _ => None @@ -245,13 +234,14 @@ object root: class Inverse extends BiTypeMap, FollowAliasesMap: def apply(t: Type): Type = t match - case t @ Fresh(_) => cap + case root(_) => assert(false) case t @ CapturingType(_, refs) => mapOver(t) case _ => mapFollowingAliases(t) - override def fuse(next: BiTypeMap)(using Context) = next match - case next: CapToFresh => assert(false); Some(IdentityTypeMap) - case _ => None + override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + case c @ Fresh(_) => cap + case root(_) => c + case _ => super.mapCapability(c, deep) def inverse = thisMap override def toString = thisMap.toString + ".inverse" @@ -283,9 +273,7 @@ object root: var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty def apply(t: Type): Type = t match - case t @ Result(binder) => - if localBinders.contains(binder) then t // keep bound references - else seen.getOrElseUpdate(t.annot, Fresh(origin)) // map free references to Fresh() + case root(_) => assert(false) case t: MethodType => // skip parameters val saved = localBinders @@ -298,6 +286,14 @@ object root: case _ => mapOver(t) + override def mapCapability(c: CaptureRef, deep: Boolean) = c match + case t @ Result(binder) => + if localBinders.contains(binder) then t // keep bound references + else seen.getOrElseUpdate(t.annot, Fresh(origin)) // map free references to Fresh() + case root(_) => c + case _ => super.mapCapability(c, deep) + end subst + subst(tp) end resultToFresh @@ -320,15 +316,7 @@ object root: private val seen = EqHashMap[CaptureRef, Result]() def apply(t: Type) = t match - case t: CaptureRef if t.isCapOrFresh => - if variance > 0 then - seen.getOrElseUpdate(t, Result(mt)) - else - if variance == 0 then - fail(em"""$tp captures the root capability `cap` in invariant position. - |This capability cannot be converted to an existential in the result type of a function.""") - // we accept variance < 0, and leave the cap as it is - super.mapOver(t) + case root(_) => assert(false) case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => if variance > 0 then super.mapOver: @@ -337,26 +325,48 @@ object root: else mapOver(t) case _ => mapOver(t) + + override def mapCapability(c: CaptureRef, deep: Boolean) = c match + case c: CaptureRef if c.isCapOrFresh => + if variance > 0 then + seen.getOrElseUpdate(c, Result(mt)) + else + if variance == 0 then + fail(em"""$tp captures the root capability `cap` in invariant position. + |This capability cannot be converted to an existential in the result type of a function.""") + // we accept variance < 0, and leave the cap as it is + c + case root(_) => c + case _ => + super.mapCapability(c, deep) + //.showing(i"mapcap $t = $result") override def toString = "toVar" object inverse extends BiTypeMap: def apply(t: Type) = t match - case t @ Result(`mt`) => + case root(_) => assert(false) + case _ => mapOver(t) + def inverse = toVar.this + override def toString = "toVar.inverse" + + override def mapCapability(c: CaptureRef, deep: Boolean) = c match + case c @ Result(`mt`) => // do a reverse getOrElseUpdate on `seen` to produce the // `Fresh` assosicated with `t` val it = seen.iterator var ref: CaptureRef | Null = null while it.hasNext && ref == null do - val (k, v) = it.next() - if v.annot eq t.annot then ref = k + val (k, v) = it.next + if v eq c then ref = k if ref == null then ref = Fresh(Origin.Unknown) - seen(ref) = t + seen(ref) = c ref - case _ => mapOver(t) - def inverse = toVar.this - override def toString = "toVar.inverse" + case root(_) => c + case _ => + super.mapCapability(c, deep) + end inverse end toVar toVar(tp) diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index 6cd238bb0e19..bf0a4146182f 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc package core import Types.*, Symbols.*, Contexts.* +import cc.{root, rootAnnot, CaptureRef} /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -163,23 +164,22 @@ object Substituters: } final class SubstBindingMap[BT <: BindingType](val from: BT, val to: BT)(using Context) extends DeepTypeMap, BiTypeMap { + def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) + override def mapCapability(c: CaptureRef, deep: Boolean = false) = c match + case c @ root.Result(binder: MethodType) if binder eq from => + c.derivedAnnotatedType(c.parent, c.rootAnnot.derivedAnnotation(to.asInstanceOf[MethodType])) + case _ => + super.mapCapability(c, deep) + override def fuse(next: BiTypeMap)(using Context) = next match case next: SubstBindingMap[_] => if next.from eq to then Some(SubstBindingMap(from, next.to)) else Some(SubstBindingsMap(Array(from, next.from), Array(to, next.to))) case _ => None - def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) def inverse = SubstBindingMap(to, from) } final class SubstBindingsMap(val from: Array[BindingType], val to: Array[BindingType])(using Context) extends DeepTypeMap, BiTypeMap { - override def fuse(next: BiTypeMap)(using Context) = next match - case next: SubstBindingMap[_] => - var i = 0 - while i < from.length && (to(i) ne next.from) do i += 1 - if i < from.length then Some(SubstBindingsMap(from, to.updated(i, next.to))) - else Some(SubstBindingsMap(from :+ next.from, to :+ next.to)) - case _ => None def apply(tp: Type): Type = tp match case tp: BoundType => @@ -189,6 +189,24 @@ object Substituters: case _ => mapOver(tp) + override def mapCapability(c: CaptureRef, deep: Boolean = false) = c match + case c @ root.Result(binder: MethodType) => + var i = 0 + while i < from.length && (from(i) ne binder) do i += 1 + if i < from.length + then c.derivedAnnotatedType(c.parent, c.rootAnnot.derivedAnnotation(to(i).asInstanceOf[MethodType])) + else c + case _ => + super.mapCapability(c, deep) + + override def fuse(next: BiTypeMap)(using Context) = next match + case next: SubstBindingMap[_] => + var i = 0 + while i < from.length && (to(i) ne next.from) do i += 1 + if i < from.length then Some(SubstBindingsMap(from, to.updated(i, next.to))) + else Some(SubstBindingsMap(from :+ next.from, to :+ next.to)) + case _ => None + def inverse = SubstBindingsMap(to, from) } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 871e8f27712c..62a4f2e9e804 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3993,7 +3993,7 @@ object Types extends TypeUtils { override def resultType(using Context): Type = if (dependencyStatus == FalseDeps) { // dealias all false dependencies - val dealiasMap = new TypeMap with IdentityCaptRefMap { + object dealiasMap extends TypeMap with IdentityCaptRefMap { def apply(tp: Type) = tp match { case tp @ TypeRef(pre, _) => tp.info match { @@ -4113,7 +4113,7 @@ object Types extends TypeUtils { /** The least supertype of `resultType` that does not contain parameter dependencies */ def nonDependentResultApprox(using Context): Type = if isResultDependent then - val dropDependencies = new ApproximatingTypeMap { + object dropDependencies extends ApproximatingTypeMap { def apply(tp: Type) = tp match { case tp @ TermParamRef(`thisLambdaType`, _) => range(defn.NothingType, atVariance(1)(apply(tp.underlying))) @@ -4133,6 +4133,12 @@ object Types extends TypeUtils { parent1 case _ => mapOver(tp) } + override def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + case ReachCapability(tp1) => + apply(tp1) match + case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach + case _ => root.cap + case _ => super.mapCapability(c, deep) } dropDependencies(resultType) else resultType @@ -6159,21 +6165,11 @@ object Types extends TypeUtils { /** The inverse of the type map */ def inverse: BiTypeMap - /** A restriction of this map to a function on tracked CaptureRefs */ - def forward(ref: CaptureRef): CaptureRef = - val result = this(ref) - def ensureTrackable(tp: Type): CaptureRef = tp match - case tp: CaptureRef => - if tp.isTrackableRef then tp - else ensureTrackable(tp.underlying) - case tp: TypeAlias => - ensureTrackable(tp.alias) - case _ => - assert(false, i"not a trackable CaptureRef: $result with underlying ${result.underlyingIterator.toList}") - ensureTrackable(result) - - /** A restriction of the inverse to a function on tracked CaptureRefs */ - def backward(ref: CaptureRef): CaptureRef = inverse.forward(ref) + /** A restriction of this map to a function on tracked Capabilities */ + override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = + super.mapCapability(c, deep) match + case c1: CaptureRef => c1 + case (cs, _) => assert(false, i"bimap $toString should map $c to a capability, but result = $cs") /** Fuse with another map */ def fuse(next: BiTypeMap)(using Context): Option[TypeMap] = None @@ -6258,6 +6254,44 @@ object Types extends TypeUtils { try derivedCapturingType(tp, this(parent), refs.map(this)) finally variance = saved + def toTrackableRef(tp: Type): CaptureRef | Null = tp match + case CapturingType(_) => + null + case tp: CaptureRef => + if tp.isTrackableRef then tp + else toTrackableRef(tp.underlying) + case tp: TypeAlias => + toTrackableRef(tp.alias) + case _ => + null + + def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + case root(_) => c + case ReachCapability(c1) => + mapCapability(c1, deep = true) + case ReadOnlyCapability(c1) => + assert(!deep) + mapCapability(c1) match + case c2: CaptureRef => c2.readOnly + case (cs: CaptureSet, exact) => (cs.readOnly, exact) + case MaybeCapability(c1) => + assert(!deep) + mapCapability(c1) match + case c2: CaptureRef => c2.maybe + case (cs: CaptureSet, exact) => (cs.maybe, exact) + case ref => + val tp1 = apply(c) + val ref1 = toTrackableRef(tp1) + if ref1 != null then + if deep then ref1.reach + else ref1 + else + val isLiteral = tp1.typeSymbol == defn.Caps_CapSet + val cs = + if deep && !isLiteral then CaptureSet.ofTypeDeeply(tp1) + else CaptureSet.ofType(tp1, followResult = false) + (cs, isLiteral) + /** Utility method. Maps the supertype of a type proxy. Returns the * type proxy itself if the mapping leaves the supertype unchanged. * This avoids needless changes in mapped types. diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index a37035cab9f0..cdc5a47b2788 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -75,7 +75,7 @@ object Recheck: * as by-name arguments of applied types. See note in doc comment for * ElimByName phase. Test case is bynamefun.scala. */ - private def mapExprType(tp: Type)(using Context): Type = tp match + def mapExprType(tp: Type)(using Context): Type = tp match case ExprType(rt) => defn.ByNameFunction(rt) case _ => tp diff --git a/tests/neg-custom-args/captures/i15923.scala b/tests/pending/neg-custom-args/captures/i15923.scala similarity index 100% rename from tests/neg-custom-args/captures/i15923.scala rename to tests/pending/neg-custom-args/captures/i15923.scala diff --git a/tests/pending/neg-custom-args/captures/i15923a.scala b/tests/pending/neg-custom-args/captures/i15923a.scala new file mode 100644 index 000000000000..83a4a30d987a --- /dev/null +++ b/tests/pending/neg-custom-args/captures/i15923a.scala @@ -0,0 +1,13 @@ +trait Cap { def use(): Int } +class Id[X](val value: [T] -> (op: X => T) -> T) +def mkId[X](x: X): Id[X] = Id([T] => (op: X => T) => op(x)) + +def bar() = { + def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { + val cap: Cap = new Cap { def use() = { println("cap is used"); 0 } } + val result = op(using caps.cap)(cap) + result + } + + val leak = withCap(cap => mkId(cap)) +} \ No newline at end of file diff --git a/tests/pending/neg-custom-args/captures/i15923b.scala b/tests/pending/neg-custom-args/captures/i15923b.scala new file mode 100644 index 000000000000..83a4a30d987a --- /dev/null +++ b/tests/pending/neg-custom-args/captures/i15923b.scala @@ -0,0 +1,13 @@ +trait Cap { def use(): Int } +class Id[X](val value: [T] -> (op: X => T) -> T) +def mkId[X](x: X): Id[X] = Id([T] => (op: X => T) => op(x)) + +def bar() = { + def withCap[X](op: (lcap: caps.Capability) ?-> Cap^{lcap} => X): X = { + val cap: Cap = new Cap { def use() = { println("cap is used"); 0 } } + val result = op(using caps.cap)(cap) + result + } + + val leak = withCap(cap => mkId(cap)) +} \ No newline at end of file diff --git a/tests/pending/pos/i4560.scala b/tests/pending/pos/i4560.scala new file mode 100644 index 000000000000..b18a05206040 --- /dev/null +++ b/tests/pending/pos/i4560.scala @@ -0,0 +1,12 @@ +class Ref + +class C { type T } + +trait Trait { + type A <: C { type T = B } + type B <: A +} + +trait SubTrait extends Trait { + val v: A +} \ No newline at end of file From 0aae591e0490bcee3624f70be52f996015bf7edd Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 18 May 2025 09:03:12 +0200 Subject: [PATCH 3/7] Fix pathRoot and pathOwner There was some accidental type confusion which made every capture root type end up with cap as pathRoot and caps as pathOwner. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 37 ++++++++++++------- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- tests/neg-custom-args/captures/i15772.check | 14 ++++--- tests/neg-custom-args/captures/reaches.check | 8 ---- tests/neg-custom-args/captures/reaches.scala | 2 +- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b6acbab29bab..d1bff2a6a12a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -192,25 +192,34 @@ extension (tp: Type) case _ => tp - /** The first element of this path type. Note that class parameter references - * are of the form this.C but their pathroot is still this.C, not this. + /** The first element of this path type, skipping selections + * and qualifiers. Note that class parameter references are of + * the form this.C but their pathroot is still this.C, not this. */ - final def pathRoot(using Context): Type = tp.dealias match - case tp1: NamedType => - if tp1.symbol.maybeOwner.isClass && tp1.symbol != defn.captureRoot && !tp1.symbol.is(TypeParam) then - tp1.prefix match - case pre: CaptureRef => pre.pathRoot - case _ => tp1 - else tp1 - case tp1 => tp1 - - /** If this part starts with `C.this`, the class `C`. - * Otherwise, if it starts with a reference `r`, `r`'s owner. - * Otherwise NoSymbol. + final def pathRoot(using Context): Type = tp match + case root(_) => tp + case QualifiedCapability(tp1) => tp1.pathRoot + case _ => tp.dealias match + case tp1: NamedType => + if tp1.symbol.maybeOwner.isClass && !tp1.symbol.is(TypeParam) then + tp1.prefix match + case pre: CaptureRef => pre.pathRoot + case _ => tp1 + else tp1 + case tp1 => tp1 + + /** The logical owner of the root of this class: + * - If this path starts with `C.this`, the class `C`. + * - If it starts with a reference `r`, `r`'s owner. + * - If it starts with cap, the `scala.caps` package class. + * - If it starts with a fresh instance, its owner. + * - If it starts with a ParamRef or a result root, NoSymbol. */ final def pathOwner(using Context): Symbol = pathRoot match + case tp1: TermRef if tp1.isCap => defn.CapsModule.moduleClass case tp1: NamedType => tp1.symbol.owner case tp1: ThisType => tp1.cls + case tp1 @ root.Fresh(_) => tp1.ccOwner case _ => NoSymbol final def isParamPath(using Context): Boolean = tp.dealias match diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 190fd1aae141..305b4b60604c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -313,7 +313,7 @@ sealed abstract class CaptureSet extends Showable: * will also map any elements added in the future to themselves. This assumption * can be tested to hold by setting the ccConfig.checkSkippedMaps setting to true. * - If the map is some other map that does not map all elements to themselves, - * freeze the current set (i.e. make it porvisionally solved) and return + * freeze the current set (i.e. make it provisionally solved) and return * the mapped elements as a constant set. */ def map(tm: TypeMap)(using Context): CaptureSet = diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 574040685ad4..4e0db9606fe4 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -18,14 +18,18 @@ | ^ refers to the universal root capability | ^² refers to a fresh root capability in the type of value arg | cap is the universal root capability --- Error: tests/neg-custom-args/captures/i15772.scala:35:33 ------------------------------------------------------------ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:35:33 --------------------------------------- 35 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - | reference cap is not included in the allowed capture set ? - | of the enclosing method main3 + |Found: (C{val arg: C^}^² => Unit) -> Unit + |Required: (C => Unit) =>² Unit | - | Note that the universal capability cap - | cannot be included in capture set ? + |where: => refers to the universal root capability + | =>² refers to a fresh root capability in the type of value boxed2 + | ^ refers to a fresh root capability in the type of value arg + | ^² refers to a fresh root capability created in value boxed2 when instantiating method c's type -> C^{cap} + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:46:2 ---------------------------------------- 46 | x: (() -> Unit) // error | ^ diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index 1cbd53de836c..a54bb05b8222 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -21,14 +21,6 @@ 34 | (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:39:31 ----------------------------------------------------------- -39 | val next: () => Unit = cur.head // error - | ^^^^^^^^ - | reference cap is not included in the allowed capture set ? - | of the enclosing method runAll2 - | - | Note that the universal capability cap - | cannot be included in capture set ? -- Error: tests/neg-custom-args/captures/reaches.scala:44:16 ----------------------------------------------------------- 44 | val cur = Ref[List[Proc]](xs) // error | ^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index edec54719e4c..37732517de18 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -36,7 +36,7 @@ def runAll1(@use xs: List[Proc]): Unit = def runAll2(@consume xs: List[Proc]): Unit = var cur: List[Proc] = xs while cur.nonEmpty do - val next: () => Unit = cur.head // error + val next: () => Unit = cur.head // was error, now OK next() cur = cur.tail From ad3583945b1e8bf26646ee83ddb4b1aaaa7e9b61 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 14 May 2025 19:13:59 +0200 Subject: [PATCH 4/7] Implement != for SimpleIdentitySet --- compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala index 714e3a5fc0d6..82ef8b93e707 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -54,7 +54,10 @@ abstract class SimpleIdentitySet[+Elem <: AnyRef] { else this.filter(that.contains) def == [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): Boolean = - this.size == that.size && forall(that.contains) + (this eq that) || this.size == that.size && forall(that.contains) + + def != [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]): Boolean = + !(this == that) override def toString: String = toList.mkString("{", ", ", "}") } From 4c8d28febe6482ab47f36dc7cbbab4cb4495d9a9 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 10 May 2025 15:36:36 +0200 Subject: [PATCH 5/7] Refactoring: Capabilities as a separate type Split out Capability from Type. For now we keep most of the overall structure. E.g. capability handling code is in file CaptureRef.scala. We will change this afterwards in separate refactorings. --- .../tools/dotc/cc/CaptureAnnotation.scala | 13 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 269 ++---- .../src/dotty/tools/dotc/cc/CaptureRef.scala | 778 +++++++++++------- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 185 ++--- .../dotty/tools/dotc/cc/CheckCaptures.scala | 42 +- .../src/dotty/tools/dotc/cc/SepCheck.scala | 27 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 21 +- .../src/dotty/tools/dotc/cc/Synthetics.scala | 5 +- compiler/src/dotty/tools/dotc/cc/root.scala | 136 +-- .../dotty/tools/dotc/core/Definitions.scala | 5 +- .../dotty/tools/dotc/core/Substituters.scala | 16 +- .../src/dotty/tools/dotc/core/Types.scala | 52 +- .../tools/dotc/printing/PlainPrinter.scala | 55 +- .../dotty/tools/dotc/printing/Printer.scala | 3 +- .../tools/dotc/printing/RefinedPrinter.scala | 2 - .../dotty/tools/dotc/reporting/Message.scala | 25 +- tests/neg-custom-args/captures/ho-ref.scala | 7 + .../run-custom-args/captures/minicheck.scala | 2 - 18 files changed, 755 insertions(+), 888 deletions(-) create mode 100644 tests/neg-custom-args/captures/ho-ref.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 0498ccf7b3d4..2af01594192f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -10,6 +10,7 @@ import Decorators.* import config.Printers.capt import printing.Printer import printing.Texts.Text +import cc.Capabilities.{Capability, RootCapability} /** An annotation representing a capture set and whether it is boxed. * It simulates a normal @retains annotation except that it is more efficient, @@ -39,10 +40,10 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte /** Reconstitute annotation tree from capture set */ override def tree(using Context) = val elems = refs.elems.toList.map { - case cr: TermRef => ref(cr) - case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr) - case cr: ThisType => This(cr.cls) - case root(_) => ref(root.cap) + case c: TermRef => ref(c) + case c: TermParamRef => untpd.Ident(c.paramName).withType(c) + case c: ThisType => This(c.cls) + case c: RootCapability => ref(defn.captureRoot) // TODO: Will crash if the type is an annotated type, for example `cap.rd` } val arg = repeated(elems, TypeTree(defn.AnyType)) @@ -66,9 +67,9 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte val elems1 = elems.mapConserve(tm.mapCapability(_)) if elems1 eq elems then this else if elems1.forall: - case elem1: CaptureRef => elem1.isTrackableRef + case elem1: Capability => elem1.isWellformed case _ => false - then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed) + then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[Capability]]*), boxed) else EmptyAnnotation override def refersToParamOf(tl: TermLambda)(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index d1bff2a6a12a..832cfc51590c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -12,6 +12,8 @@ import util.Property.Key import tpd.* import Annotations.Annotation import CaptureSet.VarState +import Capabilities.* +import StdNames.nme /** Attachment key for capturing type trees */ private val Captures: Key[CaptureSet] = Key() @@ -60,6 +62,8 @@ extension (tree: Tree) case CapsOfApply(arg) => arg.toCaptureRefs case _ => tree.tpe.dealiasKeepAnnots match + case ref: TermRef if ref.isCapRef => + GlobalCap :: Nil case ref: CaptureRef if ref.isTrackableRef => ref :: Nil case AnnotatedType(parent, ann) @@ -86,7 +90,7 @@ extension (tree: Tree) elems case _ => if tree.symbol.maybeOwner == defn.RetainsCapAnnot - then ref(root.cap) :: Nil + then ref(defn.captureRoot) :: Nil else Nil extension (tp: Type) @@ -106,17 +110,12 @@ extension (tp: Type) || ((tp.prefix eq NoPrefix) || tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef - || tp.isCap ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) case tp: TypeParamRef => !tp.underlying.exists // might happen during construction of lambdas || tp.derivesFrom(defn.Caps_CapSet) - case root.Result(_) => true - case root.Fresh(_) => true - case AnnotatedType(parent, annot) => - defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef case _ => false @@ -127,10 +126,10 @@ extension (tp: Type) * - For all other types: The result of CaptureSet.ofType */ final def captureSet(using Context): CaptureSet = tp match - case tp: CaptureRef if tp.isTrackableRef => + case tp: CoreCapability if tp.isTrackableRef => val cs = tp.captureSetOfInfo if cs.isAlwaysEmpty then cs else tp.singletonCaptureSet - case tp: SingletonCaptureRef => tp.captureSetOfInfo + case tp: ObjectCapability => tp.captureSetOfInfo case _ => CaptureSet.ofType(tp, followResult = false) /** The deep capture set of a type. This is by default the union of all @@ -143,14 +142,8 @@ extension (tp: Type) val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars) if dcs.isAlwaysEmpty then tp.captureSet else tp match - case tp @ ReachCapability(_) => - assert(false); tp.singletonCaptureSet - case ReadOnlyCapability(ref) => - assert(false); ref.deepCaptureSet(includeTypevars).readOnly - case tp: SingletonCaptureRef if tp.isTrackableRef => - tp.reach.singletonCaptureSet - case _ => - tp.captureSet ++ dcs + case tp: ObjectCapability if tp.isTrackableRef => tp.reach.singletonCaptureSet + case _ => tp.captureSet ++ dcs def deepCaptureSet(using Context): CaptureSet = deepCaptureSet(includeTypevars = false) @@ -192,46 +185,6 @@ extension (tp: Type) case _ => tp - /** The first element of this path type, skipping selections - * and qualifiers. Note that class parameter references are of - * the form this.C but their pathroot is still this.C, not this. - */ - final def pathRoot(using Context): Type = tp match - case root(_) => tp - case QualifiedCapability(tp1) => tp1.pathRoot - case _ => tp.dealias match - case tp1: NamedType => - if tp1.symbol.maybeOwner.isClass && !tp1.symbol.is(TypeParam) then - tp1.prefix match - case pre: CaptureRef => pre.pathRoot - case _ => tp1 - else tp1 - case tp1 => tp1 - - /** The logical owner of the root of this class: - * - If this path starts with `C.this`, the class `C`. - * - If it starts with a reference `r`, `r`'s owner. - * - If it starts with cap, the `scala.caps` package class. - * - If it starts with a fresh instance, its owner. - * - If it starts with a ParamRef or a result root, NoSymbol. - */ - final def pathOwner(using Context): Symbol = pathRoot match - case tp1: TermRef if tp1.isCap => defn.CapsModule.moduleClass - case tp1: NamedType => tp1.symbol.owner - case tp1: ThisType => tp1.cls - case tp1 @ root.Fresh(_) => tp1.ccOwner - case _ => NoSymbol - - final def isParamPath(using Context): Boolean = tp.dealias match - case tp1: NamedType => - tp1.prefix match - case _: ThisType | NoPrefix => - tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) - case prefix: CaptureRef => prefix.isParamPath - case _ => false - case _: ParamRef => true - case _ => false - /** 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. * The identity for all other types. @@ -256,11 +209,12 @@ extension (tp: Type) val pcs = getBoxed(parent, pre) if !tp.isBoxed then pcs - else if pre.exists && refs.containsRootCapability then - val reachRef = if refs.isReadOnly then pre.reach.readOnly else pre.reach - pcs ++ reachRef.singletonCaptureSet - else - pcs ++ refs + else pre match + case pre: ObjectCapability if refs.containsTerminalCapability => + val reachRef = if refs.isReadOnly then pre.reach.readOnly else pre.reach + pcs ++ reachRef.singletonCaptureSet + case _ => + pcs ++ refs case ref: CaptureRef if ref.isTracked && !pre.exists => getBoxed(ref, ref) case tp: TypeRef if tp.symbol.isAbstractOrParamType => CaptureSet.empty case tp: TypeProxy => getBoxed(tp.superType, pre) @@ -336,6 +290,11 @@ extension (tp: Type) && tp.membersBasedOnFlags(Mutable | Method, EmptyFlags) .exists(_.hasAltWith(_.symbol.isUpdateMethod)) + /** Is this a reference to caps.cap? Note this is _not_ the GlobalCap capability. */ + def isCapRef(using Context): Boolean = tp match + case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot + case _ => false + /** Knowing that `tp` is a function type, is it an alias to a function other * than `=>`? */ @@ -365,10 +324,6 @@ extension (tp: Type) val sym = tp.typeSymbol if sym.isClass then sym.derivesFrom(cls) else tp.superType.derivesFromCapTrait(cls) - case ReachCapability(tp1) => - tp1.widen.derivesFromCapTraitDeeply(cls) - case ReadOnlyCapability(tp1) => - tp1.derivesFromCapTrait(cls) case tp: (TypeProxy & ValueType) => tp.superType.derivesFromCapTrait(cls) case tp: AndType => @@ -392,110 +347,43 @@ extension (tp: Type) mapOver(t) tm(tp) - /** If `x` is a capture ref, its maybe capability `x?`, represented internally - * as `x @maybeCapability`. `x?` stands for a capability `x` that might or might - * not be part of a capture set. We have `{} <: {x?} <: {x}`. Maybe capabilities - * cannot be propagated between sets. If `a <: b` and `a` acquires `x?` then - * `x` is propagated to `b` as a conservative approximation. - * - * Maybe capabilities should only arise for capture sets that appear in invariant - * position in their surrounding type. They are similar to TypeBunds types, but - * restricted to capture sets. For instance, - * - * Array[C^{x?}] - * - * should be morally equivalent to - * - * Array[_ >: C^{} <: C^{x}] - * - * but it has fewer issues with type inference. - */ - def maybe(using Context): CaptureRef = tp match - case tp @ AnnotatedType(_, annot) if annot.symbol == defn.MaybeCapabilityAnnot => tp - case _ => MaybeCapability(tp) - - /** If `x` is a capture ref, its reach capability `x*`, represented internally - * as `x @reachCapability`. `x*` stands for all capabilities reachable through `x`". - * We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x` - * is the union of all capture sets that appear in covariant position in the - * type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}` - * are unrelated. - * - * Reach capabilities cannot wrap read-only capabilities or maybe capabilities. - * We have - * (x.rd).reach = x*.rd - * (x.rd)? = (x*)? - */ - def reach(using Context): CaptureRef = tp match - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.MaybeCapabilityAnnot => - tp1.reach.maybe - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReadOnlyCapabilityAnnot => - tp1.reach.readOnly - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReachCapabilityAnnot => - tp - case _ => - ReachCapability(tp) - - /** If `x` is a capture ref, its read-only capability `x.rd`, represented internally - * as `x @readOnlyCapability`. We have {x.rd} <: {x}. If `x` is a reach capability `y*`, - * then its read-only version is `x.rd*`. - * - * Read-only capabilities cannot wrap maybe capabilities - * but they can wrap reach capabilities. We have - * (x?).readOnly = (x.rd)? - */ - def readOnly(using Context): CaptureRef = tp match - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.MaybeCapabilityAnnot => - tp1.readOnly.maybe - case tp @ AnnotatedType(tp1: CaptureRef, annot) - if annot.symbol == defn.ReadOnlyCapabilityAnnot => - tp - case _ => - ReadOnlyCapability(tp) - /** If `x` is a capture ref, replace all no-flip covariant occurrences of `cap` * in type `tp` with `x*`. */ - def withReachCaptures(ref: Type)(using Context): Type = - object narrowCaps extends TypeMap: - var change = false - def apply(t: Type) = - if variance <= 0 then t - else t.dealias match - case t @ CapturingType(p, cs) if cs.containsRootCapability => - change = true - val reachRef = if cs.isReadOnly then ref.reach.readOnly else ref.reach - t.derivedCapturingType(apply(p), reachRef.singletonCaptureSet) - case t @ AnnotatedType(parent, ann) => - // Don't map annotations, which includes capture sets - t.derivedAnnotatedType(this(parent), ann) - case t @ FunctionOrMethod(args, res) => - if args.forall(_.isAlwaysPure) then - // Also map existentials in results to reach capabilities if all - // preceding arguments are known to be always pure - t.derivedFunctionOrMethod( - args, - apply(root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol)))) - else - t - case _ => - mapOver(t) - end narrowCaps - - ref match - case ref: CaptureRef if ref.isTrackableRef => - val tp1 = narrowCaps(tp) - if narrowCaps.change then - capt.println(i"narrow $tp of $ref to $tp1") - tp1 - else - tp - case _ => + def withReachCaptures(ref: Type)(using Context): Type = ref match + case ref: ObjectCapability if ref.isTrackableRef => + object narrowCaps extends TypeMap: + var change = false + def apply(t: Type) = + if variance <= 0 then t + else t.dealias match + case t @ CapturingType(p, cs) if cs.containsTerminalCapability => + change = true + val reachRef = if cs.isReadOnly then ref.reach.readOnly else ref.reach + t.derivedCapturingType(apply(p), reachRef.singletonCaptureSet) + case t @ AnnotatedType(parent, ann) => + // Don't map annotations, which includes capture sets + t.derivedAnnotatedType(this(parent), ann) + case t @ FunctionOrMethod(args, res) => + if args.forall(_.isAlwaysPure) then + // Also map existentials in results to reach capabilities if all + // preceding arguments are known to be always pure + t.derivedFunctionOrMethod( + args, + apply(root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol)))) + else + t + case _ => + mapOver(t) + end narrowCaps + val tp1 = narrowCaps(tp) + if narrowCaps.change then + capt.println(i"narrow $tp of $ref to $tp1") + tp1 + else tp + case _ => + tp end withReachCaptures /** Does this type contain no-flip covariant occurrences of `cap`? */ @@ -513,12 +401,6 @@ extension (tp: Type) foldOver(x, t) acc(false, tp) - def level(using Context): CCState.Level = - tp match - case tp: TermRef => ccState.symLevel(tp.symbol) - case tp: ThisType => ccState.symLevel(tp.cls).nextInner - case _ => CCState.undefinedLevel - def refinedOverride(name: Name, rinfo: Type)(using Context): Type = RefinedType(tp, name, AnnotatedType(rinfo, Annotation(defn.RefineOverrideAnnot, util.Spans.NoSpan))) @@ -646,9 +528,6 @@ extension (tp: AnnotatedType) case ann: CaptureAnnotation => ann.boxed case _ => false - def rootAnnot: root.Annot = (tp.annot: @unchecked) match - case ann: root.Annot => ann - /** Drop retains annotations in the type. */ class CleanupRetains(using Context) extends TypeMap: def apply(tp: Type): Type = @@ -694,48 +573,6 @@ object CapsOfApply: case TypeApply(capsOf, arg :: Nil) if capsOf.symbol == defn.Caps_capsOf => Some(arg) case _ => None -abstract class AnnotatedCapability(annotCls: Context ?=> ClassSymbol): - def apply(tp: Type)(using Context): AnnotatedType = - assert(tp.isTrackableRef, i"not a trackable ref: $tp") - tp match - case AnnotatedType(_, annot) => - assert(!unwrappable.contains(annot.symbol), i"illegal combination of derived capabilities: $annotCls over ${annot.symbol}") - case _ => - tp match - case tp: CaptureRef => tp.derivedRef(annotCls) - case _ => AnnotatedType(tp, Annotation(annotCls, util.Spans.NoSpan)) - - def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match - case AnnotatedType(parent: CaptureRef, ann) if ann.hasSymbol(annotCls) => Some(parent) - case _ => None - - protected def unwrappable(using Context): Set[Symbol] -end AnnotatedCapability - -object QualifiedCapability: - def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match - case AnnotatedType(parent: CaptureRef, ann) - if defn.capabilityQualifierAnnots.contains(ann.symbol) => Some(parent) - case _ => None - -/** An extractor for `ref @maybeCapability`, which is used to express - * the maybe capability `ref?` as a type. - */ -object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot): - protected def unwrappable(using Context) = Set() - -/** An extractor for `ref @readOnlyCapability`, which is used to express - * the read-only capability `ref.rd` as a type. - */ -object ReadOnlyCapability extends AnnotatedCapability(defn.ReadOnlyCapabilityAnnot): - protected def unwrappable(using Context) = Set(defn.MaybeCapabilityAnnot) - -/** An extractor for `ref @annotation.internal.reachCapability`, which is used to express - * the reach capability `ref*` as a type. - */ -object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot): - protected def unwrappable(using Context) = Set(defn.MaybeCapabilityAnnot, defn.ReadOnlyCapabilityAnnot) - /** An extractor for all kinds of function types as well as method and poly types. * It includes aliases of function types such as `=>`. TODO: Can we do without? * @return 1st half: The argument types or empty if this is a type function diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 145a90b4e294..1ae2feda972e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -17,366 +17,510 @@ import Annotations.Annotation import Flags.* import config.Printers.capt import CCState.{Level, undefinedLevel} - -object CaptureRef: +import annotation.constructorOnly +import ast.tpd +import printing.{Printer, Showable} +import printing.Texts.Text +import annotation.internal.sharable + +type CaptureRef = Capabilities.Capability + +/** Capability --+-- RootCapabilty -----+-- GlobalCap + * | +-- FreshCap + * | +-- ResultCap + * | + * +-- CoreCapability ----+-- ObjectCapability --+-- TermRef + * | | +-- ThisType + * | | +-- TermParamRef + * | | + * | +-- SetCapability -----+-- TypeRef + * | +-- TypeParamRef + * | + * +-- DerivedCapability -+-- ReadOnly + * +-- Reach + * +-- Maybe + * + * All CoreCapabilities are Types, or, more specifically instances of TypeProxy. + */ +object Capabilities: opaque type Validity = Int def validId(runId: Int, iterId: Int): Validity = runId + (iterId << RunWidth) def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId) val invalid: Validity = validId(NoRunId, 0) -/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, - * as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities. - * If there are several annotations they come with an order: - * `*` first, `.rd` next, `?` last. - */ -trait CaptureRef extends TypeProxy, ValueType: - import CaptureRef.* - - private var myCaptureSet: CaptureSet | Null = uninitialized - private var myCaptureSetValid: Validity = invalid - private var mySingletonCaptureSet: CaptureSet.Const | Null = null - private var myDerivedRefs: List[AnnotatedType] = Nil - - /** A derived reach, readOnly or maybe reference. Derived references are cached. */ - def derivedRef(annotCls: ClassSymbol)(using Context): AnnotatedType = - def recur(refs: List[AnnotatedType]): AnnotatedType = refs match - case ref :: refs1 => - if ref.annot.symbol == annotCls then ref else recur(refs1) - case Nil => - val derived = AnnotatedType(this, Annotation(annotCls, util.Spans.NoSpan)) - myDerivedRefs = derived :: myDerivedRefs - derived - recur(myDerivedRefs) - - /** Is the reference tracked? This is true if it can be tracked and the capture - * set of the underlying type is not always empty. - */ - final def isTracked(using Context): Boolean = - this.isTrackableRef && (isRootCapability || !captureSetOfInfo.isAlwaysEmpty) + @sharable var nextRootId = 0 + + /** The base trait of all root capabilities */ + trait RootCapability extends Capability: + val rootId = nextRootId + nextRootId += 1 + + /** The base trait of all capabilties represented as types */ + trait CoreCapability extends TypeProxy, Capability: + override def toText(printer: Printer): Text = printer.toText(this) + + trait ObjectCapability extends CoreCapability - /** Is this a maybe reference of the form `x?`? */ - final def isMaybe(using Context): Boolean = this ne stripMaybe + trait SetCapability extends CoreCapability + + trait DerivedCapability extends Capability: + def underlying: Capability + + /** If `x` is a capture ref, its maybe capability `x?`. `x?` stands for a capability + * `x` that might or might not be part of a capture set. We have `{} <: {x?} <: {x}`. + * Maybe capabilities cannot be propagated between sets. If `a <: b` and `a` + * acquires `x?` then `x` is propagated to `b` as a conservative approximation. + * + * Maybe capabilities should only arise for capture sets that appear in invariant + * position in their surrounding type. They are similar to TypeBounds types, but + * restricted to capture sets. For instance, + * + * Array[C^{x?}] + * + * should be morally equivalent to + * + * Array[_ >: C^{} <: C^{x}] + * + * but it has fewer issues with type inference. + */ + case class Maybe(underlying: Capability) extends DerivedCapability - /** Is this a read-only reference of the form `x.rd` or a capture set variable - * with only read-ony references in its upper bound? + /** The readonly capability `x.rd`. We have {x.rd} <: {x}. + * + * Read-only capabilities cannot wrap maybe capabilities + * but they can wrap reach capabilities. We have + * (x?).readOnly = (x.rd)? */ - final def isReadOnly(using Context): Boolean = this match - case tp: TypeRef => tp.captureSetOfInfo.isReadOnly - case _ => this ne stripReadOnly - - /** Is this a reach reference of the form `x*`? */ - final def isReach(using Context): Boolean = this ne stripReach - - final def stripMaybe(using Context): CaptureRef = this match - case AnnotatedType(tp1: CaptureRef, annot) if annot.symbol == defn.MaybeCapabilityAnnot => - tp1 - case _ => - this - - final def stripReadOnly(using Context): CaptureRef = this match - case tp @ AnnotatedType(tp1: CaptureRef, annot) => - val sym = annot.symbol - if sym == defn.ReadOnlyCapabilityAnnot then - tp1 - else if sym == defn.MaybeCapabilityAnnot then - tp.derivedAnnotatedType(tp1.stripReadOnly, annot) - else - this - case _ => - this - - final def stripReach(using Context): CaptureRef = this match - case tp @ AnnotatedType(tp1: CaptureRef, annot) => - val sym = annot.symbol - if sym == defn.ReachCapabilityAnnot then - tp1 - else if sym == defn.ReadOnlyCapabilityAnnot || sym == defn.MaybeCapabilityAnnot then - tp.derivedAnnotatedType(tp1.stripReach, annot) - else - this - case _ => - this - - /** Is this reference the generic root capability `cap` ? */ - final def isCap(using Context): Boolean = this match - case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot - case _ => false - - /** Is this reference a Fresh instance? */ - final def isFresh(using Context): Boolean = this match - case root.Fresh(_) => true - case _ => false - - final def isResultRoot(using Context): Boolean = this match - case root.Result(_) => true - case _ => false - - /** Is this reference the generic root capability `cap` or a Fresh instance? */ - final def isCapOrFresh(using Context): Boolean = isCap || isFresh - - /** Is this reference one of the generic root capabilities `cap` or `cap.rd` ? */ - final def isRootCapability(using Context): Boolean = this match - case ReadOnlyCapability(tp1) => tp1.isRootCapability - case root(_) => true - case _ => isCap - - /** An exclusive capability is a capability that derives - * indirectly from a maximal capability without going through - * a read-only capability first. + case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach) + extends DerivedCapability: + assert(!underlying.isInstanceOf[Maybe]) + + /** If `x` is a capture ref, its reach capability `x*`. `x*` stands for all + * capabilities reachable through `x`. + * We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x` + * is the union of all capture sets that appear in covariant position in the + * type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}` + * are unrelated. + * + * Reach capabilities cannot wrap read-only capabilities or maybe capabilities. + * We have + * (x.rd).reach = x*.rd + * (x.rd)? = (x*)? */ - final def isExclusive(using Context): Boolean = - !isReadOnly && (isRootCapability || captureSetOfInfo.isExclusive) - - /** The owning symbol associated with a capability this is - * - for Fresh capabilities: the owner of the hidden set - * - for TermRefs and TypeRefs: the symbol it refers to - * - for derived and path capabilities: the owner of the underlying capability - * - otherwise NoSymbol + case class Reach(underlying: ObjectCapability) extends DerivedCapability: + assert(!underlying.isInstanceOf[Maybe | ReadOnly]) + + /** The global root capability referenced as `caps.cap` + * `cap` does not subsume other capabilities, except in arguments of + * `withCapAsRoot` calls. */ - final def ccOwner(using Context): Symbol = this match - case root.Fresh(hidden) => - hidden.owner - case ref: NamedType => - if ref.isCap then NoSymbol - else ref.pathRoot match - case ref: ThisType => ref.cls - case ref: NamedType => ref.symbol - case _ => NoSymbol - case ref: ThisType => - ref.cls - case QualifiedCapability(ref1) => - ref1.ccOwner - case _ => - NoSymbol - - /** The symbol that represents the level closest-enclosing ccOwner. - * Symbols representing levels are - * - class symbols, but not inner (non-static) module classes - * - method symbols, but not accessors or constructors + @sharable // We override below all operations that access internal capability state + object GlobalCap extends RootCapability: + override val maybe = Maybe(this) + override val readOnly = ReadOnly(this) + override def reach = unsupported("cap.reach") + override def singletonCaptureSet(using Context) = CaptureSet.universal + override def captureSetOfInfo(using Context) = singletonCaptureSet + override def cached[C <: DerivedCapability](newRef: C): C = unsupported("cached") + override def invalidateCaches() = () + + /** The class of "fresh" roots. These do subsume other capabilties in scope. + * They track with hidden sets which other capabilities were subsumed. + * Hidden sets are inspected by separation checking. + * @param owner the owner of the context in which the FreshCap was created + * @param origin an indication where and why the FreshCap was created, used + * for diagnostics */ - final def levelOwner(using Context): Symbol = - def adjust(owner: Symbol): Symbol = - if !owner.exists - || owner.isClass && (!owner.is(Flags.Module) || owner.isStatic) - || owner.is(Flags.Method, butNot = Flags.Accessor) && !owner.isConstructor - then owner - else adjust(owner.owner) - adjust(ccOwner) - - // With the support of paths, we don't need to normalize the `TermRef`s anymore. - // /** Normalize reference so that it can be compared with `eq` for equality */ - // final def normalizedRef(using Context): CaptureRef = this match - // case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef => - // tp.derivedAnnotatedType(parent.normalizedRef, annot) - // case tp: TermRef if tp.isTrackableRef => - // tp.symbol.termRef - // case _ => this - - /** The capture set consisting of exactly this reference */ - final def singletonCaptureSet(using Context): CaptureSet.Const = - if mySingletonCaptureSet == null then - mySingletonCaptureSet = CaptureSet(this) - mySingletonCaptureSet.uncheckedNN - - /** The capture set of the type underlying this reference */ - final def captureSetOfInfo(using Context): CaptureSet = - if myCaptureSetValid == currentId then myCaptureSet.nn - else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty - else - myCaptureSet = CaptureSet.Pending - val computed = CaptureSet.ofInfo(this) - if !isCaptureChecking - || ctx.mode.is(Mode.IgnoreCaptures) - || !underlying.exists - || underlying.isProvisional - then - myCaptureSet = null + case class FreshCap private (owner: Symbol, origin: root.Origin)(using @constructorOnly ctx: Context) + extends RootCapability: + val hiddenSet = CaptureSet.HiddenSet(owner) + hiddenSet.owningCap = this + + override def equals(that: Any) = that match + case that: FreshCap => this eq that + case _ => false + + object FreshCap: + def apply(origin: root.Origin)(using Context): FreshCap | GlobalCap.type = + if ccConfig.useSepChecks then FreshCap(ctx.owner, origin) + else GlobalCap + + case class ResultCap(binder: MethodicType) extends RootCapability: + private var myOriginalBinder = binder + def originalBinder: MethodicType = myOriginalBinder + + def derivedResult(binder1: MethodicType): ResultCap = + if binder1 eq binder then this else - myCaptureSet = computed - myCaptureSetValid = currentId - computed - - final def invalidateCaches() = - myCaptureSetValid = invalid - - /** x subsumes x - * x =:= y ==> x subsumes y - * x subsumes y ==> x subsumes y.f - * x subsumes y ==> x* subsumes y, x subsumes y? - * x subsumes y ==> x* subsumes y*, x? subsumes y? - * x: x1.type /\ x1 subsumes y ==> x subsumes y - * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y - * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y - * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y - * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y - * Contains[X, y] ==> X subsumes y + val res = ResultCap(binder1) + res.myOriginalBinder = myOriginalBinder + res + end ResultCap + + /** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, + * as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities. + * If there are several annotations they come with an order: + * `*` first, `.rd` next, `?` last. */ - final def subsumes(y: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + trait Capability extends Showable: + + private var myCaptureSet: CaptureSet | Null = uninitialized + private var myCaptureSetValid: Validity = invalid + private var mySingletonCaptureSet: CaptureSet.Const | Null = null + private var myDerived: List[DerivedCapability] = Nil + + protected def cached[C <: DerivedCapability](newRef: C): C = + def recur(refs: List[DerivedCapability]): C = refs match + case ref :: refs1 => + if ref.getClass == newRef.getClass then ref.asInstanceOf[C] else recur(refs1) + case Nil => + myDerived = newRef :: myDerived + newRef + recur(myDerived) + + def maybe: Maybe = this match + case self: Maybe => self + case _ => cached(Maybe(this)) + + def readOnly: ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.readOnly) + case self: ReadOnly => self + case self: (ObjectCapability | RootCapability | Reach) => cached(ReadOnly(self)) + + def reach: Reach | ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.reach) + case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach]) + case self: Reach => self + case self: ObjectCapability => cached(Reach(self)) + + /** Is this a maybe reference of the form `x?`? */ + final def isMaybe(using Context): Boolean = this ne stripMaybe + + /** Is this a read-only reference of the form `x.rd` or `x.rd?` or a + * capture set variable with only read-ony references in its upper bound? + */ + final def isReadOnly(using Context): Boolean = this match + case tp: SetCapability => tp.captureSetOfInfo.isReadOnly + case _ => this ne stripReadOnly + + /** Is this a reach reference of the form `x*` or a readOnly or maybe variant + * of a reach reference? + */ + final def isReach(using Context): Boolean = this ne stripReach + + final def stripMaybe(using Context): Capability = this match + case Maybe(ref1) => ref1 + case _ => this + + final def stripReadOnly(using Context): Capability = this match + case ReadOnly(ref1) => ref1 + case Maybe(ref1) => ref1.stripReadOnly.maybe + case _ => this + + final def stripReach(using Context): Capability = this match + case Reach(ref1) => ref1 + case ReadOnly(ref1) => ref1.stripReach.readOnly + case Maybe(ref1) => ref1.stripReach.maybe + case _ => this + + /** Is this reference the generic root capability `cap` or a Fresh instance? */ + final def isCapOrFresh(using Context): Boolean = this match + case GlobalCap | _: FreshCap => true + case _ => false - def subsumingRefs(x: Type, y: Type): Boolean = x match - case x: CaptureRef => y match - case y: CaptureRef => x.subsumes(y) - case _ => false + /** Is this reference a root capability or a derived version of one? + * These capabilities have themselves as their captureSetOfInfo. + */ + final def isTerminalCapability(using Context): Boolean = + core.isInstanceOf[RootCapability] + + /** Is the reference tracked? This is true if it can be tracked and the capture + * set of the underlying type is not always empty. + */ + final def isTracked(using Context): Boolean = this.core match + case _: RootCapability => true + case tp: CoreCapability => tp.isTrackableRef && !captureSetOfInfo.isAlwaysEmpty + + /** An exclusive capability is a capability that derives + * indirectly from a maximal capability without going through + * a read-only capability first. + */ + final def isExclusive(using Context): Boolean = + !isReadOnly && (isTerminalCapability || captureSetOfInfo.isExclusive) + + final def isWellformed(using Context): Boolean = this match + case self: CoreCapability => self.isTrackableRef + case _ => true + + /** The non-derived capability underlying this capability */ + final def core: CoreCapability | RootCapability = this match + case self: (CoreCapability | RootCapability) => self + case self: DerivedCapability => self.underlying.core + + /** The type underlying this capability, NoType for root capabilities */ + final def coreType: CoreCapability | NoType.type = core match + case c: CoreCapability => c + case _ => NoType + + /** The first element of this path type, skipping selections + * and qualifiers. Note that class parameter references are of + * the form this.C but their pathroot is still this.C, not this. + */ + final def pathRoot(using Context): Capability = this match + case _: RootCapability => this + case self: DerivedCapability => self.underlying.pathRoot + case self: CoreCapability => self.dealias match + case tp1: (TermRef | TypeRef) => // can't use NamedType here since it is not a capability + if tp1.symbol.maybeOwner.isClass && !tp1.symbol.is(TypeParam) then + tp1.prefix match + case pre: Capability => pre.pathRoot + case _ => tp1 + else tp1 + case tp1: CoreCapability => tp1 + case _ => self + + /** The logical owner of the root of this class: + * - If this path starts with `C.this`, the class `C`. + * - If it starts with a reference `r`, `r`'s owner. + * - If it starts with cap, the `scala.caps` package class. + * - If it starts with a fresh instance, its owner. + * - If it starts with a ParamRef or a result root, NoSymbol. + */ + final def pathOwner(using Context): Symbol = pathRoot match + case tp1: ThisType => tp1.cls + case tp1: NamedType => tp1.symbol.owner + case GlobalCap => defn.CapsModule.moduleClass + case tp1: FreshCap => tp1.ccOwner + case _ => NoSymbol + + final def isParamPath(using Context): Boolean = this match + case tp1: NamedType => + tp1.prefix match + case _: ThisType | NoPrefix => + tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) + case prefix: CoreCapability => prefix.isParamPath + case _ => false + case _: ParamRef => true case _ => false - def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match - case info: SingletonCaptureRef => test(info) - case CapturingType(parent, _) => - if this.derivesFrom(defn.Caps_CapSet) && false then test(info) - /* - If `this` is a capture set variable `C^`, then it is possible that it can be - reached from term variables in a reachability chain through the context. - For instance, in `def test[C^](src: Foo^{C^}) = { val x: Foo^{src} = src; val y: Foo^{x} = x; y }` - we expect that `C^` subsumes `x` and `y` in the body of the method - (cf. test case cc-poly-varargs.scala for a more involved example). - */ - else viaInfo(parent)(test) - case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test) - case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) + final def ccOwner(using Context): Symbol = this match + case self: ThisType => self.cls + case TermRef(prefix: Capability, _) => prefix.ccOwner + case self: NamedType => self.symbol + case self: DerivedCapability => self.underlying.ccOwner + case self: FreshCap => self.hiddenSet.owner + case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol + + /** The symbol that represents the level closest-enclosing ccOwner. + * Symbols representing levels are + * - class symbols, but not inner (non-static) module classes + * - method symbols, but not accessors or constructors + */ + final def levelOwner(using Context): Symbol = + def adjust(owner: Symbol): Symbol = + if !owner.exists + || owner.isClass && (!owner.is(Flags.Module) || owner.isStatic) + || owner.is(Flags.Method, butNot = Flags.Accessor) && !owner.isConstructor + then owner + else adjust(owner.owner) + adjust(ccOwner) + + /** Tests whether the capability derives from capability class `cls`. */ + def derivesFromCapTrait(cls: ClassSymbol)(using Context): Boolean = this match + case Reach(ref1) => ref1.widen.derivesFromCapTraitDeeply(cls) + case self: DerivedCapability => self.underlying.derivesFromCapTrait(cls) + case self: CoreCapability => self.superType.derivesFromCapTrait(cls) case _ => false - try (this eq y) - || maxSubsumes(y, canAddHidden = !vs.isOpen) - || y.match - case y: TermRef if !y.isCap => + def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability) + def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable) + def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability) + + /** The capture set consisting of exactly this reference */ + def singletonCaptureSet(using Context): CaptureSet.Const = + if mySingletonCaptureSet == null then + mySingletonCaptureSet = CaptureSet(this) + mySingletonCaptureSet.uncheckedNN + + /** The capture set of the type underlying this reference */ + def captureSetOfInfo(using Context): CaptureSet = + if myCaptureSetValid == currentId then myCaptureSet.nn + else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty + else + myCaptureSet = CaptureSet.Pending + val computed = CaptureSet.ofInfo(this) + def isProvisional = this.core match + case core: TypeProxy => !core.underlying.exists || core.underlying.isProvisional + case _ => false + if !isCaptureChecking || ctx.mode.is(Mode.IgnoreCaptures) || isProvisional then + myCaptureSet = null + else + myCaptureSet = computed + myCaptureSetValid = currentId + computed + + def invalidateCaches() = + myCaptureSetValid = invalid + + /** x subsumes x + * x =:= y ==> x subsumes y + * x subsumes y ==> x subsumes y.f + * x subsumes y ==> x* subsumes y, x subsumes y? + * x subsumes y ==> x* subsumes y*, x? subsumes y? + * x: x1.type /\ x1 subsumes y ==> x subsumes y + * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y + * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y + * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y + * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y + * Contains[X, y] ==> X subsumes y + */ + final def subsumes(y: Capability)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + + /** Are `x` and `y` capabilities such that x subsumes y? */ + def subsumingRefs(x: Type | Capability, y: Type | Capability): Boolean = x match + case x: Capability => y match + case y: Capability => x.subsumes(y) + case _ => false + case _ => false + + /** Perform `test` on all object capabilities in `info` */ + def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match + case info: ObjectCapability => test(info) + case CapturingType(parent, _) => viaInfo(parent)(test) + case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test) + case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test) + case _ => false + + try (this eq y) + || maxSubsumes(y, canAddHidden = !vs.isOpen) + || y.match + case y: TermRef => y.prefix.match - case ypre: CaptureRef => + case ypre: Capability => this.subsumes(ypre) || this.match - case x @ TermRef(xpre: CaptureRef, _) if x.symbol == y.symbol => + case x @ TermRef(xpre: Capability, _) if x.symbol == y.symbol => // To show `{x.f} <:< {y.f}`, it is important to prove `x` and `y` // are equvalent, which means `x =:= y` in terms of subtyping, // not just `{x} =:= {y}` in terms of subcapturing. // It is possible to construct two singleton types `x` and `y`, // which subsume each other, but are not equal references. // See `tests/neg-custom-args/captures/path-prefix.scala` for example. - withMode(Mode.IgnoreCaptures) {TypeComparer.isSameRef(xpre, ypre)} + withMode(Mode.IgnoreCaptures): + TypeComparer.isSameRef(xpre, ypre) case _ => false case _ => false || viaInfo(y.info)(subsumingRefs(this, _)) - case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) - case ReadOnlyCapability(y1) => this.stripReadOnly.subsumes(y1) + case Maybe(y1) => this.stripMaybe.subsumes(y1) + case ReadOnly(y1) => this.stripReadOnly.subsumes(y1) case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) => // The upper and lower bounds don't have to be in the form of `CapSet^{...}`. // They can be other capture set variables, which are bounded by `CapSet`, // like `def test[X^, Y^, Z >: X <: Y]`. y.info match - case TypeBounds(_, hi @ CapturingType(parent, refs)) if parent.derivesFrom(defn.Caps_CapSet) => + case TypeBounds(_, hi @ CapturingType(parent, refs)) => refs.elems.forall(this.subsumes) - case TypeBounds(_, hi: CaptureRef) => + case TypeBounds(_, hi: Capability) => this.subsumes(hi) - case _ => y.captureSetOfInfo.elems.forall(this.subsumes) - case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) || this.derivesFrom(defn.Caps_CapSet) => - assert(false, this) - /* The second condition in the guard is for `this` being a `CapSet^{a,b...}` and etablishing a - potential reachability chain through `y`'s capture to a binding with - `this`'s capture set (cf. `CapturingType` case in `def viaInfo` above for more context). - */ - refs.elems.forall(this.subsumes) - case _ => false - || this.match - case ReachCapability(x1) => x1.subsumes(y.stripReach) - case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) - case x: TypeRef if assumedContainsOf(x).contains(y) => true - case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => - x.info match - case TypeBounds(lo @ CapturingType(parent, refs), _) if parent.derivesFrom(defn.Caps_CapSet) => - refs.elems.exists(_.subsumes(y)) - case TypeBounds(lo: CaptureRef, _) => - lo.subsumes(y) case _ => - x.captureSetOfInfo.elems.exists(_.subsumes(y)) - case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => - assert(false, this) + y.captureSetOfInfo.elems.forall(this.subsumes) case _ => false - catch case ex: AssertionError => - println(i"error while subsumes $this >> $y") - throw ex - end subsumes - - /** This is a maximal capability that subsumes `y` in given context and VarState. - * @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities. - * We add those capabilities to the hidden set if this is a Fresh instance. - * If false we only accept `y` elements that are already in the - * hidden set of this Fresh instance. The idea is that in a VarState that - * accepts additions we first run `maxSubsumes` with `canAddHidden = false` - * so that new variables get added to the sets. If that fails, we run - * the test again with canAddHidden = true as a last effort before we - * fail a comparison. - */ - def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = - def yIsExistential = y.stripReadOnly match - case root.Result(_) => - capt.println(i"failed existential $this >: $y") - true - case _ => false - (this eq y) - || this.match - case x @ root.Fresh(hidden) => - def levelOK = - if ccConfig.useFreshLevels && !CCState.ignoreFreshLevels then - val yOwner = y.levelOwner - yOwner.isStaticOwner || x.ccOwner.isContainedIn(yOwner) - else - !y.stripReadOnly.isCap - && !yIsExistential - && !y.stripReadOnly.isInstanceOf[ParamRef] - - vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y))) - || levelOK - && canAddHidden - && vs.addHidden(hidden, y) - case x @ root.Result(binder) => - val result = y match - case y @ root.Result(_) => vs.unify(x, y) - case _ => y.derivesFromSharedCapability - if !result then - ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y)) - result - case _ if this.isCap => - if y.isCap then true - else if yIsExistential then false - else y.derivesFromSharedCapability - || canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot - case _ => - y match - case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) + || this.match + case Reach(x1) => x1.subsumes(y.stripReach) + case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) + case x: TypeRef if assumedContainsOf(x).contains(y) => true + case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) => + x.info match + case TypeBounds(CapturingType(_, lorefs), _) => + lorefs.elems.exists(_.subsumes(y)) + case TypeBounds(lo: Capability, _) => + lo.subsumes(y) + case _ => + x.captureSetOfInfo.elems.exists(_.subsumes(y)) case _ => false - - /** `x covers y` if we should retain `y` when computing the overlap of - * two footprints which have `x` respectively `y` as elements. - * We assume that .rd have already been stripped on both sides. - * We have: - * - * x covers x - * x covers y ==> x covers y.f - * x covers y ==> x* covers y*, x? covers y? - * TODO what other clauses from subsumes do we need to port here? - */ - final def covers(y: CaptureRef)(using Context): Boolean = - (this eq y) - || y.match - case y @ TermRef(ypre: CaptureRef, _) if !y.isCap => - this.covers(ypre) - case ReachCapability(y1) => - this match - case ReachCapability(x1) => x1.covers(y1) - case _ => false - case MaybeCapability(y1) => - this match - case MaybeCapability(x1) => x1.covers(y1) - case _ => false - case root.Fresh(hidden) => - hidden.superCaps.exists(this covers _) + catch case ex: AssertionError => + println(i"error while subsumes $this >> $y") + throw ex + end subsumes + + /** This is a maximal capability that subsumes `y` in given context and VarState. + * @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities. + * We add those capabilities to the hidden set if this is a Fresh instance. + * If false we only accept `y` elements that are already in the + * hidden set of this Fresh instance. The idea is that in a VarState that + * accepts additions we first run `maxSubsumes` with `canAddHidden = false` + * so that new variables get added to the sets. If that fails, we run + * the test again with canAddHidden = true as a last effort before we + * fail a comparison. + */ + def maxSubsumes(y: Capability, canAddHidden: Boolean)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + (this eq y) + || this.match + case x: FreshCap => + def levelOK = + if ccConfig.useFreshLevels && !CCState.ignoreFreshLevels then + val yOwner = y.levelOwner + yOwner.isStaticOwner || x.ccOwner.isContainedIn(yOwner) + else y.core match + case GlobalCap | ResultCap(_) | _: ParamRef => false + case _ => true + + vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y))) + || levelOK + && canAddHidden + && vs.addHidden(x.hiddenSet, y) + case x: ResultCap => + val result = y match + case y: ResultCap => vs.unify(x, y) + case _ => y.derivesFromSharedCapability + if !result then + ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y)) + result + case GlobalCap => + y match + case GlobalCap => true + case _: ResultCap => false + case _ => + y.derivesFromSharedCapability + || canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot case _ => - false - - def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[CaptureRef] = - CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) + y match + case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) + case _ => false -end CaptureRef + /** `x covers y` if we should retain `y` when computing the overlap of + * two footprints which have `x` respectively `y` as elements. + * We assume that .rd have already been stripped on both sides. + * We have: + * + * x covers x + * x covers y ==> x covers y.f + * x covers y ==> x* covers y*, x? covers y? + * TODO what other clauses from subsumes do we need to port here? + */ + final def covers(y: Capability)(using Context): Boolean = + (this eq y) + || y.match + case y @ TermRef(ypre: Capability, _) => + this.covers(ypre) + case Reach(y1) => + this match + case Reach(x1) => x1.covers(y1) + case _ => false + case Maybe(y1) => + this match + case Maybe(x1) => x1.covers(y1) + case _ => false + case y: FreshCap => + y.hiddenSet.superCaps.exists(this covers _) + case _ => + false -trait SingletonCaptureRef extends SingletonType, CaptureRef + def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[Capability] = + CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) + def toText(printer: Printer): Text = printer.toTextCaptureRef(this) + end Capability +end Capabilities \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 305b4b60604c..ce9af5876393 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -19,6 +19,7 @@ import scala.collection.{mutable, immutable} import CCState.* import TypeOps.AvoidMap import compiletime.uninitialized +import Capabilities.* /** A class for capture sets. Capture sets can be constants or variables. * Capture sets support inclusion constraints <:< where <:< is subcapturing. @@ -92,14 +93,14 @@ sealed abstract class CaptureSet extends Showable: /** Does this capture set contain the root reference `cap` as element? */ final def isUniversal(using Context) = - elems.exists(_.isCap) + elems.contains(GlobalCap) /** Does this capture set contain a root reference `cap` or `cap.rd` as element? */ - final def containsRootCapability(using Context) = - elems.exists(_.isRootCapability) + final def containsTerminalCapability(using Context) = + elems.exists(_.isTerminalCapability) final def containsCap(using Context) = - elems.exists(_.stripReadOnly.isCap) + elems.exists(_.core eq GlobalCap) final def isReadOnly(using Context): Boolean = elems.forall(_.isReadOnly) @@ -145,7 +146,7 @@ sealed abstract class CaptureSet extends Showable: * capture set. */ protected final def addNewElem(elem: CaptureRef)(using ctx: Context, vs: VarState): CompareResult = - if elem.isRootCapability || !vs.isOpen then + if elem.isTerminalCapability || !vs.isOpen then addThisElem(elem) else addThisElem(elem).orElse: @@ -189,9 +190,9 @@ sealed abstract class CaptureSet extends Showable: elems.exists(_.subsumes(x)) || // Even though subsumes already follows captureSetOfInfo, this is not enough. // For instance x: C^{y, z}. Then neither y nor z subsumes x but {y, z} accounts for x. - !x.isRootCapability - && !x.derivesFrom(defn.Caps_CapSet) - && !(vs.isSeparating && x.captureSetOfInfo.containsRootCapability) + !x.isTerminalCapability + && !x.coreType.derivesFrom(defn.Caps_CapSet) + && !(vs.isSeparating && x.captureSetOfInfo.containsTerminalCapability) // in VarState.Separate, don't try to widen to cap since that might succeed with {cap} <: {cap} && x.captureSetOfInfo.subCaptures(this, VarState.Separate).isOK @@ -212,7 +213,7 @@ sealed abstract class CaptureSet extends Showable: CCState.withCapAsRoot: CCState.ignoringFreshLevels: // OK here since we opportunistically choose an alternative which gets checked later elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded)) - || !x.isRootCapability + || !x.isTerminalCapability && { val elems = x.captureSetOfInfo.elems !elems.isEmpty && elems.forall(mightAccountFor) @@ -361,11 +362,10 @@ sealed abstract class CaptureSet extends Showable: */ protected def isBadRoot(rootLimit: Symbol | Null, elem: CaptureRef)(using Context): Boolean = if rootLimit == null then false - else - val elem1 = elem.stripReadOnly - elem1.isCap - || elem1.isResultRoot - || elem1.isFresh && elem1.ccOwner.isContainedIn(rootLimit) + else elem.core match + case GlobalCap | _: ResultCap => true + case elem: FreshCap => elem.ccOwner.isContainedIn(rootLimit) + case _ => false /** Invoke `handler` if this set has (or later aquires) a root capability. * Excluded are Fresh instances unless their ccOwner is contained in `upto`. @@ -433,33 +433,32 @@ object CaptureSet: val empty: CaptureSet.Const = Const(emptyRefs) /** The universal capture set `{cap}` */ - def universal(using Context): CaptureSet = - root.cap.singletonCaptureSet + def universal(using Context): Const = + Const(SimpleIdentitySet(GlobalCap)) - /** The same as CaptureSet.universal but generated implicitly for + /** The same as {cap.rd} but generated implicitly for * references of Capability subtypes */ - def universalImpliedByCapability(using Context) = - defn.universalCSImpliedByCapability + val csImpliedByCapability = Const(SimpleIdentitySet(GlobalCap.readOnly)) - def fresh(origin: root.Origin)(using Context): CaptureSet = - root.Fresh(origin).singletonCaptureSet + def fresh(origin: root.Origin)(using Context): Const = + FreshCap(origin).singletonCaptureSet /** The shared capture set `{cap.rd}` */ - def shared(using Context): CaptureSet = - root.cap.readOnly.singletonCaptureSet + def shared(using Context): Const = + GlobalCap.readOnly.singletonCaptureSet /** Used as a recursion brake */ @sharable private[dotc] val Pending = Const(SimpleIdentitySet.empty) - def apply(elems: CaptureRef*)(using Context): CaptureSet.Const = + def apply(elems: Capability*)(using Context): Const = if elems.isEmpty then empty else for elem <- elems do - assert(elem.isTrackableRef, i"not a trackable ref: $elem") + assert(elem.isWellformed, i"not a trackable ref: $elem") Const(SimpleIdentitySet(elems*)) - def apply(elems: Refs)(using Context): CaptureSet.Const = + def apply(elems: Refs)(using Context): Const = if elems.isEmpty then empty else Const(elems) /** The subclass of constant capture sets with given elements `elems` */ @@ -598,7 +597,7 @@ object CaptureSet: CompareResult.LevelError(this, elem) // or `elem` is not visible at the level of the set. else // id == 108 then assert(false, i"trying to add $elem to $this") - assert(elem.isTrackableRef, elem) + assert(elem.isWellformed, elem) assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState]) elems += elem if isBadRoot(rootLimit, elem) then @@ -621,17 +620,17 @@ object CaptureSet: case _ => foldOver(b, t) find(false, binder) - private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match - case elem @ root.Fresh(_) => + private def levelOK(elem: Capability)(using Context): Boolean = elem match + case _: FreshCap => !level.isDefined || ccState.symLevel(elem.ccOwner) <= level || { capt.println(i"LEVEL ERROR $elem cannot be included in $this of $owner") false } - case elem @ root.Result(mt) => - rootLimit == null && (this.isInstanceOf[BiMapped] || isPartOf(mt.resType)) - case elem: TermRef if elem.isCap => + case elem @ ResultCap(binder) => + rootLimit == null && (this.isInstanceOf[BiMapped] || isPartOf(binder.resType)) + case GlobalCap => rootLimit == null case elem: TermRef if level.isDefined => elem.prefix match @@ -649,8 +648,8 @@ object CaptureSet: |elem binder = ${elem.binder}""") false } - case QualifiedCapability(elem1) => - levelOK(elem1) + case elem: DerivedCapability => + levelOK(elem.underlying) case _ => true @@ -689,10 +688,7 @@ object CaptureSet: computingApprox = true try val approx = computeApprox(origin).ensuring(_.isConst) - if approx.elems.exists: - case root.Result(_) => true - case _ => false - then + if approx.elems.exists(_.isInstanceOf[ResultCap]) then ccState.approxWarnings += em"""Capture set variable $this gets upper-approximated |to existential variable from $approx, using {cap} instead.""" @@ -702,7 +698,7 @@ object CaptureSet: /** The intersection of all upper approximations of dependent sets */ protected def computeApprox(origin: CaptureSet)(using Context): CaptureSet = - (universal /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } + ((universal: CaptureSet) /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } /** Widen the variable's elements to its upper approximation and * mark it as constant from now on. This is used for contra-variant type variables @@ -947,28 +943,28 @@ object CaptureSet: */ class HiddenSet(initialOwner: Symbol)(using @constructorOnly ictx: Context) extends Var(initialOwner): - var owningCap: AnnotatedType = uninitialized + var owningCap: FreshCap = uninitialized // initialized when owning FreshCap is created var givenOwner: Symbol = initialOwner override def owner = givenOwner //assert(id != 4) - private def aliasRef: AnnotatedType | Null = + private def aliasRef: FreshCap | Null = if myElems.size == 1 then myElems.nth(0) match - case al @ root.Fresh(hidden) if deps.contains(hidden) => al + case alias: FreshCap if deps.contains(alias.hiddenSet) => alias case _ => null else null private def aliasSet: HiddenSet = if myElems.size == 1 then myElems.nth(0) match - case root.Fresh(hidden) if deps.contains(hidden) => hidden + case alias: FreshCap if deps.contains(alias.hiddenSet) => alias.hiddenSet case _ => this else this - def superCaps: List[AnnotatedType] = + def superCaps: List[FreshCap] = deps.toList.map(_.asInstanceOf[HiddenSet].owningCap) override def elems: Refs = @@ -995,18 +991,18 @@ object CaptureSet: assert(dep != this) vs.addHidden(dep.asInstanceOf[HiddenSet], elem) elem match - case root.Fresh(hidden) => - if this ne hidden then - val alias = hidden.aliasRef + case elem: FreshCap => + if this ne elem.hiddenSet then + val alias = elem.hiddenSet.aliasRef if alias != null then add(alias) - else if deps.contains(hidden) then // make this an alias of elem - capt.println(i"Alias $this to $hidden") + else if deps.contains(elem.hiddenSet) then // make this an alias of elem + capt.println(i"Alias $this to ${elem.hiddenSet}") elems = SimpleIdentitySet(elem) - deps = SimpleIdentitySet(hidden) + deps = SimpleIdentitySet(elem.hiddenSet) else addToElems() - hidden.deps += this + elem.hiddenSet.deps += this case _ => addToElems() @@ -1029,9 +1025,10 @@ object CaptureSet: * - if the variance is contravariant, return {} * - Otherwise assertion failure */ - def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = + final def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = tm.mapCapability(r) match - case c: CaptureRef => c.captureSet + case c: CoreCapability => c.captureSet + case c: Capability => c.singletonCaptureSet case (cs: CaptureSet, exact) => if cs.isAlwaysEmpty || exact || variance > 0 then cs else if variance < 0 then CaptureSet.EmptyWithProvenance(r, cs) @@ -1066,7 +1063,7 @@ object CaptureSet: * when a subsumes check decides that an existential variable `ex` cannot be * instantiated to the other capability `other`. */ - case class ExistentialSubsumesFailure(val ex: root.Result, val other: CaptureRef) extends ErrorNote + case class ExistentialSubsumesFailure(val ex: ResultCap, val other: CaptureRef) extends ErrorNote trait CompareFailure: private var myErrorNotes: List[ErrorNote] = Nil @@ -1132,10 +1129,10 @@ object CaptureSet: /** A map from root.Result values to other such values. If two result values * `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`. */ - private var eqResultMap: util.SimpleIdentityMap[root.Result, root.Result] = util.SimpleIdentityMap.empty + private var eqResultMap: util.SimpleIdentityMap[ResultCap, ResultCap] = util.SimpleIdentityMap.empty /** A snapshot of the `eqResultMap` value at the start of a VarState transaction */ - private var eqResultSnapshot: util.SimpleIdentityMap[root.Result, root.Result] | Null = null + private var eqResultSnapshot: util.SimpleIdentityMap[ResultCap, ResultCap] | Null = null /** The recorded elements of `v` (it's required that a recording was made) */ def elems(v: Var): Refs = elemsMap(v) @@ -1188,21 +1185,18 @@ object CaptureSet: * no problem with confusing results at different levels. * See pos-customargs/captures/overrides.scala for a test case. */ - def unify(root1: root.Result, root2: root.Result)(using Context): Boolean = - (root1, root2) match - case (root1 @ root.Result(binder1), root2 @ root.Result(binder2)) - if ((binder1 eq binder2) - || binder1.isInstanceOf[ExprType] && binder2.isInstanceOf[ExprType] // (**) - ) - && (root1.rootAnnot.originalBinder ne root2.rootAnnot.originalBinder) - && eqResultMap(root1) == null - && eqResultMap(root2) == null - => - if eqResultSnapshot == null then eqResultSnapshot = eqResultMap - eqResultMap = eqResultMap.updated(root1, root2).updated(root2, root1) - true - case _ => - false + def unify(c1: ResultCap, c2: ResultCap)(using Context): Boolean = + ((c1.binder eq c2.binder) + || c1.binder.isInstanceOf[ExprType] && c2.binder.isInstanceOf[ExprType] // (**) + ) + && (c1.originalBinder ne c2.originalBinder) + && eqResultMap(c1) == null + && eqResultMap(c2) == null + && { + if eqResultSnapshot == null then eqResultSnapshot = eqResultMap + eqResultMap = eqResultMap.updated(c1, c2).updated(c2, c1) + true + } /** Roll back global state to what was recorded in this VarState */ def rollBack(): Unit = @@ -1283,14 +1277,8 @@ object CaptureSet: /** A template for maps on capabilities where f(c) <: c and f(f(c)) = c */ private abstract class NarrowingCapabilityMap(using Context) extends BiTypeMap: - def mapRef(ref: CaptureRef): CaptureRef - def apply(t: Type) = mapOver(t) - override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match - case c: CaptureRef if c.isTrackableRef => mapRef(c) - case _ => super.mapCapability(c, deep) - override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) case next: NarrowingCapabilityMap if next.getClass == getClass => assert(false) @@ -1298,6 +1286,7 @@ object CaptureSet: class Inverse extends BiTypeMap: def apply(t: Type) = t // since f(c) <: c, this is the best inverse + override def mapCapability(c: Capability, deep: Boolean): Capability = c def inverse = NarrowingCapabilityMap.this override def toString = NarrowingCapabilityMap.this.toString ++ ".inverse" override def fuse(next: BiTypeMap)(using Context) = next match @@ -1310,12 +1299,12 @@ object CaptureSet: /** Maps `x` to `x?` */ private class MaybeMap(using Context) extends NarrowingCapabilityMap: - def mapRef(ref: CaptureRef): CaptureRef = ref.maybe + override def mapCapability(c: Capability, deep: Boolean) = c.maybe override def toString = "Maybe" /** Maps `x` to `x.rd` */ private class ReadOnlyMap(using Context) extends NarrowingCapabilityMap: - def mapRef(ref: CaptureRef): CaptureRef = ref.readOnly + override def mapCapability(c: Capability, deep: Boolean) = c.readOnly override def toString = "ReadOnly" /* Not needed: @@ -1341,19 +1330,22 @@ object CaptureSet: */ /** The capture set of the type underlying CaptureRef */ - def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match - case ReachCapability(ref1) => - ref1.widen.deepCaptureSet(includeTypevars = true) - .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) - case ReadOnlyCapability(ref1) => - ref1.captureSetOfInfo.map(ReadOnlyMap()) - case ref: ParamRef if !ref.underlying.exists => + def ofInfo(c: Capability)(using Context): CaptureSet = c match + case Reach(c1) => + c1.widen.deepCaptureSet(includeTypevars = true) + .showing(i"Deep capture set of $c: ${c1.widen} = ${result}", capt) + case ReadOnly(c1) => + c1.captureSetOfInfo.readOnly + case Maybe(c1) => + c1.captureSetOfInfo.maybe + case c: RootCapability => + c.singletonCaptureSet + case c: ParamRef if !c.underlying.exists => // might happen during construction of lambdas, assume `{cap}` in this case so that // `ref` will not seem subsumed by other capabilities in a `++`. universal - case _ => - if ref.isRootCapability then ref.singletonCaptureSet - else ofType(ref.underlying, followResult = false) + case c: CoreCapability => + ofType(c.underlying, followResult = false) /** Capture set of a type * @param followResult If true, also include capture sets of function results. @@ -1372,25 +1364,14 @@ object CaptureSet: case tp: (TypeRef | TypeParamRef) => if tp.derivesFrom(defn.Caps_CapSet) then tp.captureSet else empty - case tp @ root.Result(_) => - tp.captureSet case CapturingType(parent, refs) => recur(parent) ++ refs - case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) => - // Note: we don't use the `ReachCapability(parent)` extractor here since that - // only works if `parent` is a CaptureRef, but in illegal programs it might not be. - // And then we do not want to fall back to empty. - parent match - case parent: SingletonCaptureRef if parent.isTrackableRef => - tp.singletonCaptureSet - case _ => - CaptureSet.ofTypeDeeply(parent.widen) case tpd @ defn.RefinedFunctionOf(rinfo: MethodType) if followResult => - ofType(tpd.parent, followResult = false) // pick up capture set from parent type + ofType(tpd.parent, followResult = false) // pick up capture set from parent type ++ recur(rinfo.resType) // add capture set of result .filter: case TermParamRef(binder, _) => binder ne rinfo - case root.Result(binder) => binder ne rinfo + case ResultCap(binder) => binder ne rinfo case _ => true case tpd @ AppliedType(tycon, args) => if followResult && defn.isNonRefinedFunction(tpd) then diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 04cacac06a64..2797dc80808b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -24,6 +24,7 @@ import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} import reporting.{trace, Message, OverrideError} import Annotations.Annotation +import Capabilities.* /** The capture checker */ object CheckCaptures: @@ -101,7 +102,7 @@ object CheckCaptures: def checkWellformed(parent: Tree, ann: Tree)(using Context): Unit = def check(elem: Tree, pos: SrcPos): Unit = elem.tpe match case ref: CaptureRef => - if !ref.isTrackableRef then + if !ref.isTrackableRef && !ref.isCapRef then report.error(em"$elem cannot be tracked since it is not a parameter or local value", pos) case tpe => report.error(em"$elem: $tpe is not a legal element of a capture set", pos) @@ -325,8 +326,8 @@ class CheckCaptures extends Recheck, SymTransformer: case t @ CapturingType(parent, refs) => for ref <- refs.elems do ref match - case root.Fresh(hidden) if !hidden.givenOwner.exists => - hidden.givenOwner = sym + case ref: FreshCap if !ref.hiddenSet.givenOwner.exists => + ref.hiddenSet.givenOwner = sym case _ => traverse(parent) case t @ defn.RefinedFunctionOf(rinfo) => @@ -388,7 +389,7 @@ class CheckCaptures extends Recheck, SymTransformer: provenance: => String = "", cs1description: String = "")(using Context) = checkOK( ccState.test(cs1.subCaptures(cs2)), - if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not" + if cs1.elems.size == 1 then i"reference ${cs1.elems.nth(0)}$cs1description is not" else i"references $cs1$cs1description are not all", cs1, cs2, pos, provenance) @@ -478,16 +479,17 @@ class CheckCaptures extends Recheck, SymTransformer: def avoidLocalCapability(c: CaptureRef, env: Env, lastEnv: Env | Null): Unit = if c.isParamPath then c match - case ReachCapability(_) | _: TypeRef => + case Reach(_) | _: TypeRef => checkUseDeclared(c, env, lastEnv) case _ => else val underlying = c match - case ReachCapability(c1) => - CaptureSet.ofTypeDeeply(c1.widen) - case _ => - CaptureSet.ofType(c.widen, followResult = false) - capt.println(i"Widen reach $c to $underlying in ${env.owner}") + case Reach(c1) => CaptureSet.ofTypeDeeply(c1.widen) + case _ => c.core match + case c1: RootCapability => c1.singletonCaptureSet + case c1: CoreCapability => + CaptureSet.ofType(c1.widen, followResult = false) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") underlying.disallowRootCapability(NoSymbol): () => report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", tree.srcPos) recur(underlying, env, lastEnv) @@ -496,7 +498,7 @@ class CheckCaptures extends Recheck, SymTransformer: * parameter. This is the default. */ def avoidLocalReachCapability(c: CaptureRef, env: Env): Unit = c match - case ReachCapability(c1) => + case Reach(c1) => if c1.isParamPath then checkUseDeclared(c, env, null) else @@ -512,7 +514,7 @@ class CheckCaptures extends Recheck, SymTransformer: val underlying = CaptureSet.ofTypeDeeply(c1.widen) capt.println(i"Widen reach $c to $underlying in ${env.owner}") if ccConfig.useSepChecks then - recur(underlying.filter(!_.isRootCapability), env, null) + recur(underlying.filter(!_.isTerminalCapability), env, null) // we don't want to disallow underlying Fresh instances, since these are typically locally created // fresh capabilities. We don't need to also follow the hidden set since separation // checking makes ure that locally hidden references need to go to @consume parameters. @@ -625,7 +627,7 @@ class CheckCaptures extends Recheck, SymTransformer: addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref var pathRef: CaptureRef = addSelects(sym.termRef, pt) - if pathRef.derivesFrom(defn.Caps_Mutable) && pt.isValueType && !pt.isMutableType then + if pathRef.derivesFromMutable && pt.isValueType && !pt.isMutableType then pathRef = pathRef.readOnly markFree(sym, pathRef, tree) mapResultRoots(super.recheckIdent(tree, pt), tree.symbol) @@ -724,7 +726,7 @@ class CheckCaptures extends Recheck, SymTransformer: val meth = tree.fun.symbol if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.stripCapturing.capturing(root.Fresh(root.Origin.UnsafeAssumePure))) + val argType0 = recheck(arg, pt.stripCapturing.capturing(FreshCap(root.Origin.UnsafeAssumePure))) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -836,9 +838,9 @@ class CheckCaptures extends Recheck, SymTransformer: var refined: Type = core var allCaptures: CaptureSet = if core.derivesFromMutable then - initCs ++ root.Fresh(root.Origin.NewMutable(core)).singletonCaptureSet + initCs ++ FreshCap(root.Origin.NewMutable(core)).singletonCaptureSet else if core.derivesFromCapability then - initCs ++ root.Fresh(root.Origin.NewCapability(core)).readOnly.singletonCaptureSet + initCs ++ FreshCap(root.Origin.NewCapability(core)).readOnly.singletonCaptureSet else initCs for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol @@ -1276,7 +1278,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def toAdd(using Context) = notes.map: note => val msg = note match case CompareResult.LevelError(cs, ref) => - if ref.stripReadOnly.isCapOrFresh then + if ref.core.isCapOrFresh then i"""the universal capability $ref |cannot be included in capture set $cs""" else @@ -1284,12 +1286,12 @@ class CheckCaptures extends Recheck, SymTransformer: case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}" case _ => "" i"""reference ${ref}$levelStr - |cannot be included in outer capture set $cs""" + |cannot be included in outer capture set $cs""" case ExistentialSubsumesFailure(ex, other) => def since = - if other.isRootCapability then "" + if other.isTerminalCapability then "" else " since that capability is not a SharedCapability" - i"""the existential capture root in ${ex.rootAnnot.originalBinder.resType} + i"""the existential capture root in ${ex.originalBinder.resType} |cannot subsume the capability $other$since""" i""" | diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index af3c70a0a491..514ad1fbc6ce 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -14,6 +14,7 @@ import util.{SimpleIdentitySet, EqHashMap, SrcPos} import tpd.* import reflect.ClassTag import reporting.trace +import Capabilities.* /** The separation checker is a tree traverser that is run after capture checking. * It checks tree nodes for various separation conditions, explained in the @@ -169,7 +170,7 @@ object SepCheck: * 3. if `f in F` then the footprint of `f`'s info is also in `F`. */ private def footprint(includeMax: Boolean = false)(using Context): Refs = - def retain(ref: CaptureRef) = includeMax || !ref.isRootCapability + def retain(ref: CaptureRef) = includeMax || !ref.isTerminalCapability def recur(elems: Refs, newElems: List[CaptureRef]): Refs = newElems match case newElem :: newElems1 => val superElems = newElem.captureSetOfInfo.elems.filter: superElem => @@ -186,15 +187,15 @@ object SepCheck: if seen.contains(newElem) then recur(seen, acc, newElems1) else newElem.stripReadOnly match - case root.Fresh(hidden) => - if hidden.deps.isEmpty then recur(seen + newElem, acc + newElem, newElems1) + case elem: FreshCap => + if elem.hiddenSet.deps.isEmpty then recur(seen + newElem, acc + newElem, newElems1) else val superCaps = - if newElem.isReadOnly then hidden.superCaps.map(_.readOnly) - else hidden.superCaps + if newElem.isReadOnly then elem.hiddenSet.superCaps.map(_.readOnly) + else elem.hiddenSet.superCaps recur(seen + newElem, acc, superCaps ++ newElems) case _ => - if newElem.isRootCapability + if newElem.isTerminalCapability //|| newElem.isInstanceOf[TypeRef | TypeParamRef] then recur(seen + newElem, acc, newElems1) else recur(seen + newElem, acc, newElem.captureSetOfInfo.elems.toList ++ newElems1) @@ -235,11 +236,11 @@ object SepCheck: ++ refs1 .filter: - case ReadOnlyCapability(ref @ TermRef(prefix: CaptureRef, _)) => + case ReadOnly(ref @ TermRef(prefix: CoreCapability, _)) => // We can get away testing only references with at least one field selection // here since stripped readOnly references that equal a reference in refs2 // are added by the first clause of the symmetric call to common. - !ref.isCap && refs2.exists(_.covers(prefix)) + refs2.exists(_.covers(prefix)) case _ => false .map(_.stripReadOnly) @@ -255,8 +256,8 @@ object SepCheck: val seen: util.EqHashSet[CaptureRef] = new util.EqHashSet def hiddenByElem(elem: CaptureRef): Refs = elem match - case root.Fresh(hcs) => hcs.elems ++ recur(hcs.elems) - case ReadOnlyCapability(ref1) => hiddenByElem(ref1).map(_.readOnly) + case elem: FreshCap => elem.hiddenSet.elems ++ recur(elem.hiddenSet.elems) + case ReadOnly(elem1) => hiddenByElem(elem1).map(_.readOnly) case _ => emptyRefs def recur(refs: Refs): Refs = @@ -319,8 +320,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def sharedPeaksStr(shared: Refs)(using Context): String = shared.nth(0) match - case fresh @ root.Fresh(hidden) => - if hidden.owner.exists then i"$fresh of ${hidden.owner}" else i"$fresh" + case fresh: FreshCap => + if fresh.hiddenSet.owner.exists then i"$fresh of ${fresh.hiddenSet.owner}" else i"$fresh" case other => i"$other" @@ -756,7 +757,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: c.add(c1) case t @ CapturingType(parent, cs) => val c1 = this(c, parent) - if cs.elems.exists(_.stripReadOnly.isFresh) then c1.add(Captures.Hidden) + if cs.elems.exists(_.core.isInstanceOf[FreshCap]) then c1.add(Captures.Hidden) else if !cs.elems.isEmpty then c1.add(Captures.Explicit) else c1 case t: TypeRef if t.symbol.isAbstractOrParamType => diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 6d812eb1f8c8..601db08ca082 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -22,6 +22,7 @@ import dotty.tools.dotc.util.NoSourcePosition import CheckCaptures.CheckerAPI import NamerOps.methodType import NameKinds.{CanThrowEvidenceName, TryOwnerName} +import Capabilities.* /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -366,7 +367,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: */ def stripImpliedCaptureSet(tp: Type): Type = tp match case tp @ CapturingType(parent, refs) - if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => + if (refs eq CaptureSet.csImpliedByCapability) && !tp.isBoxedCapturing => parent case tp: AliasingBounds => tp.derivedAlias(stripImpliedCaptureSet(tp.alias)) @@ -429,7 +430,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: && !t.isSingleton && (!sym.isConstructor || (t ne tp.finalResultType)) // Don't add ^ to result types of class constructors deriving from Capability - then CapturingType(t, CaptureSet.universalImpliedByCapability, boxed = false) + then CapturingType(t, CaptureSet.csImpliedByCapability, boxed = false) else normalizeCaptures(mapFollowingAliases(t)) def innerApply(t: Type) = @@ -512,9 +513,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: reach |= t.isBoxed try traverse(parent) - for case root.Fresh(hidden) <- refs.elems.iterator do - if reach then hidden.elems += ref.reach - else if ref.isTracked then hidden.elems += ref + for case fresh: FreshCap <- refs.elems.iterator do // TODO: what about fresh.rd elems? + if reach then fresh.hiddenSet.elems += ref.reach + else if ref.isTracked then fresh.hiddenSet.elems += ref finally reach = saved case _ => traverseChildren(t) @@ -847,7 +848,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case RetainingType(parent, refs) => needsVariable(parent) && !refs.tpes.exists: - case ref: TermRef => ref.isCap + case ref: TermRef => ref.isCapRef case _ => false case AnnotatedType(parent, _) => needsVariable(parent) @@ -915,7 +916,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def apply(t: Type) = t match case t @ CapturingType(parent, refs) => val parent1 = this(parent) - if refs.containsRootCapability then t.derivedCapturingType(parent1, CaptureSet.Fluid) + if refs.containsTerminalCapability then t.derivedCapturingType(parent1, CaptureSet.Fluid) else t case _ => mapFollowingAliases(t) @@ -961,8 +962,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: report.warning(em"redundant capture: $dom already accounts for $ref", pos) if ref.captureSetOfInfo.elems.isEmpty - && !ref.derivesFrom(defn.Caps_Capability) - && !ref.derivesFrom(defn.Caps_CapSet) then + && !ref.coreType.derivesFrom(defn.Caps_Capability) + && !ref.coreType.derivesFrom(defn.Caps_CapSet) then val deepStr = if ref.isReach then " deep" else "" report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos) check(parent.captureSet, parent) @@ -971,7 +972,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for j <- 0 until retained.length if j != i r <- retained(j).toCaptureRefs - if !r.isRootCapability + if !r.isTerminalCapability yield r val remaining = CaptureSet(others*) check(remaining, remaining) diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 0f7a3a3ee022..bb2228932cb8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -8,6 +8,7 @@ import StdNames.nme import Names.Name import NameKinds.DefaultGetterName import config.Printers.capt +import Capabilities.* /** Classification and transformation methods for function methods and * synthetic case class methods that need to be treated specially. @@ -131,7 +132,7 @@ object Synthetics: val (pt: PolyType) = info: @unchecked val (mt: MethodType) = pt.resType: @unchecked val (enclThis: ThisType) = owner.thisType: @unchecked - val paramCaptures = CaptureSet(enclThis, root.cap) + val paramCaptures = CaptureSet(enclThis, GlobalCap) pt.derivedLambdaType(resType = MethodType(mt.paramNames)( mt1 => mt.paramInfos.map(_.capturing(paramCaptures)), mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head)))) @@ -149,7 +150,7 @@ object Synthetics: def transformCompareCaptures = val (enclThis: ThisType) = symd.owner.thisType: @unchecked MethodType( - defn.ObjectType.capturing(CaptureSet(root.cap, enclThis)) :: Nil, + defn.ObjectType.capturing(CaptureSet(GlobalCap, enclThis)) :: Nil, defn.BooleanType) symd.copySymDenotation(info = symd.name match diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala index 374d7d13f279..5b32dfedc66d 100644 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ b/compiler/src/dotty/tools/dotc/cc/root.scala @@ -15,6 +15,7 @@ import reporting.Message import util.{SimpleIdentitySet, EqHashMap} import ast.tpd import annotation.constructorOnly +import Capabilities.* /** A module defining three kinds of root capabilities * - `cap` of kind `Global`: This is the global root capability. Among others it is @@ -107,96 +108,7 @@ object root: i" when computing deep capture set of $ref" case Unknown => "" - - enum Kind: - case Result(binder: MethodicType) - case Fresh(hidden: CaptureSet.HiddenSet)(val origin: Origin) - case Global - - override def equals(other: Any): Boolean = - (this eq other.asInstanceOf[AnyRef]) || this.match - case Kind.Result(b1) => other match - case Kind.Result(b2) => b1 eq b2 - case _ => false - case Kind.Fresh(h1) => other match - case Kind.Fresh(h2) => h1 eq h2 - case _ => false - case Kind.Global => false - end Kind - - /** The annotation of a root instance */ - case class Annot(kind: Kind)(using @constructorOnly ictx: Context) extends Annotation: - - /** id printed under -uniqid, for debugging */ - val id = - val ccs = ccState - ccs.rootId += 1 - ccs.rootId - - //assert(id != 4, kind) - - override def symbol(using Context) = defn.RootCapabilityAnnot - override def tree(using Context) = New(symbol.typeRef, Nil) - override def derivedAnnotation(tree: Tree)(using Context): Annotation = this - - private var myOriginalKind = kind - def originalBinder: MethodicType = myOriginalKind.asInstanceOf[Kind.Result].binder - - def derivedAnnotation(binder: MethodType)(using Context): Annotation = kind match - case Kind.Result(b) if b ne binder => - val ann = Annot(Kind.Result(binder)) - ann.myOriginalKind = myOriginalKind - ann - case _ => - this - - override def hash: Int = kind.hashCode - override def eql(that: Annotation) = that match - case Annot(kind) => this.kind eq kind - case _ => false - end Annot - - def cap(using Context): TermRef = defn.captureRoot.termRef - - /** The type of fresh references */ - type Fresh = AnnotatedType - - /** Constructor and extractor methods for "fresh" capabilities */ - object Fresh: - def apply(using Context)(origin: Origin, owner: Symbol = ctx.owner): CaptureRef = - if ccConfig.useSepChecks then - val hiddenSet = CaptureSet.HiddenSet(owner) - val res = AnnotatedType(cap, Annot(Kind.Fresh(hiddenSet)(origin))) - hiddenSet.owningCap = res - //assert(hiddenSet.id != 3) - res - else - cap - - def unapply(tp: AnnotatedType): Option[CaptureSet.HiddenSet] = tp.annot match - case Annot(Kind.Fresh(hidden)) => Some(hidden) - case _ => None - end Fresh - - /** The type of existentially bound references */ - type Result = AnnotatedType - - object Result: - def apply(binder: MethodicType)(using Context): Result = - val hiddenSet = CaptureSet.HiddenSet(NoSymbol) - val res = AnnotatedType(cap, Annot(Kind.Result(binder))) - hiddenSet.owningCap = res - res - - def unapply(tp: Result)(using Context): Option[MethodicType] = tp.annot match - case Annot(Kind.Result(binder)) => Some(binder) - case _ => None - end Result - - def unapply(root: CaptureRef)(using Context): Option[Kind] = root match - case root @ AnnotatedType(_, ann: Annot) => Some(ann.kind) - case _ if root.isCap => Some(Kind.Global) - case _ => None + end Origin /** Map each occurrence of cap to a different Fresh instance * Exception: CapSet^ stays as it is. @@ -207,7 +119,6 @@ object root: override def apply(t: Type) = if variance <= 0 then t else t match - case root(_) => assert(false) case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => t case t @ CapturingType(_, _) => @@ -222,8 +133,7 @@ object root: mapFollowingAliases(t) override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match - case c: CaptureRef if c.isCap => Fresh(origin) - case root(_) => c + case GlobalCap => FreshCap(origin) case _ => super.mapCapability(c, deep) override def fuse(next: BiTypeMap)(using Context) = next match @@ -234,13 +144,11 @@ object root: class Inverse extends BiTypeMap, FollowAliasesMap: def apply(t: Type): Type = t match - case root(_) => assert(false) case t @ CapturingType(_, refs) => mapOver(t) case _ => mapFollowingAliases(t) override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match - case c @ Fresh(_) => cap - case root(_) => c + case _: FreshCap => GlobalCap case _ => super.mapCapability(c, deep) def inverse = thisMap @@ -269,11 +177,10 @@ object root: /** Map top-level free existential variables one-to-one to Fresh instances */ def resultToFresh(tp: Type, origin: Origin)(using Context): Type = val subst = new TypeMap: - val seen = EqHashMap[Annotation, CaptureRef]() + val seen = EqHashMap[ResultCap, FreshCap | GlobalCap.type]() var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty def apply(t: Type): Type = t match - case root(_) => assert(false) case t: MethodType => // skip parameters val saved = localBinders @@ -287,10 +194,9 @@ object root: mapOver(t) override def mapCapability(c: CaptureRef, deep: Boolean) = c match - case t @ Result(binder) => - if localBinders.contains(binder) then t // keep bound references - else seen.getOrElseUpdate(t.annot, Fresh(origin)) // map free references to Fresh() - case root(_) => c + case c @ ResultCap(binder) => + if localBinders.contains(binder) then c // keep bound references + else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap case _ => super.mapCapability(c, deep) end subst @@ -313,30 +219,28 @@ object root: super.mapOver(t) object toVar extends CapMap: - private val seen = EqHashMap[CaptureRef, Result]() + private val seen = EqHashMap[RootCapability, ResultCap]() def apply(t: Type) = t match - case root(_) => assert(false) case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => if variance > 0 then super.mapOver: defn.FunctionNOf(args, res, contextual) - .capturing(Result(mt).singletonCaptureSet) + .capturing(ResultCap(mt).singletonCaptureSet) else mapOver(t) case _ => mapOver(t) override def mapCapability(c: CaptureRef, deep: Boolean) = c match - case c: CaptureRef if c.isCapOrFresh => + case c: (FreshCap | GlobalCap.type) => if variance > 0 then - seen.getOrElseUpdate(c, Result(mt)) + seen.getOrElseUpdate(c, ResultCap(mt)) else if variance == 0 then fail(em"""$tp captures the root capability `cap` in invariant position. |This capability cannot be converted to an existential in the result type of a function.""") // we accept variance < 0, and leave the cap as it is c - case root(_) => c case _ => super.mapCapability(c, deep) @@ -344,28 +248,26 @@ object root: override def toString = "toVar" object inverse extends BiTypeMap: - def apply(t: Type) = t match - case root(_) => assert(false) - case _ => mapOver(t) - def inverse = toVar.this - override def toString = "toVar.inverse" + def apply(t: Type) = mapOver(t) override def mapCapability(c: CaptureRef, deep: Boolean) = c match - case c @ Result(`mt`) => + case c @ ResultCap(`mt`) => // do a reverse getOrElseUpdate on `seen` to produce the // `Fresh` assosicated with `t` val it = seen.iterator - var ref: CaptureRef | Null = null + var ref: RootCapability | Null = null while it.hasNext && ref == null do val (k, v) = it.next if v eq c then ref = k if ref == null then - ref = Fresh(Origin.Unknown) + ref = FreshCap(Origin.Unknown) seen(ref) = c ref - case root(_) => c case _ => super.mapCapability(c, deep) + + def inverse = toVar.this + override def toString = "toVar.inverse" end inverse end toVar diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ed231c1e9ca9..f493aa3d0e37 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -15,7 +15,7 @@ import Comments.{Comment, docCtx} import util.Spans.NoSpan import config.Feature import Symbols.requiredModuleRef -import cc.{CaptureSet, RetainingType, readOnly} +import cc.{CaptureSet, RetainingType} import ast.tpd.ref import scala.annotation.tailrec @@ -1013,9 +1013,6 @@ class Definitions { @tu lazy val Caps_Mutable: ClassSymbol = requiredClass("scala.caps.Mutable") @tu lazy val Caps_SharedCapability: ClassSymbol = requiredClass("scala.caps.SharedCapability") - /** The same as CaptureSet.universal but generated implicitly for references of Capability subtypes */ - @tu lazy val universalCSImpliedByCapability = CaptureSet(captureRoot.termRef.readOnly) - @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") // Annotation base classes diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index bf0a4146182f..425b6193f3cd 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc package core import Types.*, Symbols.*, Contexts.* -import cc.{root, rootAnnot, CaptureRef} +import cc.Capabilities.{Capability, ResultCap} /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -165,9 +165,9 @@ object Substituters: final class SubstBindingMap[BT <: BindingType](val from: BT, val to: BT)(using Context) extends DeepTypeMap, BiTypeMap { def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) - override def mapCapability(c: CaptureRef, deep: Boolean = false) = c match - case c @ root.Result(binder: MethodType) if binder eq from => - c.derivedAnnotatedType(c.parent, c.rootAnnot.derivedAnnotation(to.asInstanceOf[MethodType])) + override def mapCapability(c: Capability, deep: Boolean = false) = c match + case c @ ResultCap(binder: MethodType) if binder eq from => + c.derivedResult(to.asInstanceOf[MethodType]) case _ => super.mapCapability(c, deep) @@ -189,13 +189,11 @@ object Substituters: case _ => mapOver(tp) - override def mapCapability(c: CaptureRef, deep: Boolean = false) = c match - case c @ root.Result(binder: MethodType) => + override def mapCapability(c: Capability, deep: Boolean = false) = c match + case c @ ResultCap(binder: MethodType) => var i = 0 while i < from.length && (from(i) ne binder) do i += 1 - if i < from.length - then c.derivedAnnotatedType(c.parent, c.rootAnnot.derivedAnnotation(to(i).asInstanceOf[MethodType])) - else c + if i < from.length then c.derivedResult(to(i).asInstanceOf[MethodType]) else c case _ => super.mapCapability(c, deep) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 62a4f2e9e804..5cd38d269bd9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -40,6 +40,7 @@ import java.lang.ref.WeakReference import compiletime.uninitialized import cc.* import CaptureSet.{CompareResult, IdentityCaptRefMap} +import Capabilities.* import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -406,7 +407,7 @@ object Types extends TypeUtils { case tp: AndOrType => tp.tp1.unusableForInference || tp.tp2.unusableForInference case tp: LambdaType => tp.resultType.unusableForInference || tp.paramInfos.exists(_.unusableForInference) case WildcardType(optBounds) => optBounds.unusableForInference - case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.unusableForInference) + case CapturingType(parent, refs) => parent.unusableForInference || refs.elems.exists(_.coreType.unusableForInference) case _: ErrorType => true case _ => false catch case ex: Throwable => handleRecursive("unusableForInference", show, ex) @@ -2912,7 +2913,7 @@ object Types extends TypeUtils { */ abstract case class TermRef(override val prefix: Type, private var myDesignator: Designator) - extends NamedType, ImplicitRef, SingletonCaptureRef { + extends NamedType, ImplicitRef, SingletonType, ObjectCapability { type ThisType = TermRef type ThisName = TermName @@ -2941,7 +2942,7 @@ object Types extends TypeUtils { abstract case class TypeRef(override val prefix: Type, private var myDesignator: Designator) - extends NamedType, CaptureRef { + extends NamedType, SetCapability { type ThisType = TypeRef type ThisName = TypeName @@ -3077,7 +3078,7 @@ object Types extends TypeUtils { * do not survive runs whereas typerefs do. */ abstract case class ThisType(tref: TypeRef) - extends CachedProxyType, SingletonCaptureRef { + extends CachedProxyType, SingletonType, ObjectCapability { def cls(using Context): ClassSymbol = tref.stableInRunSymbol match { case cls: ClassSymbol => cls case _ if ctx.mode.is(Mode.Interactive) => defn.AnyClass // was observed to happen in IDE mode @@ -4043,7 +4044,7 @@ object Types extends TypeUtils { val status1 = (compute(status, parent, theAcc) /: refs.elems): (s, ref) => ref.stripReach match case tp: TermParamRef if tp.binder eq thisLambdaType => combine(s, TrueDeps) - case tp => combine(s, compute(status, tp, theAcc)) + case tp => combine(s, compute(status, tp.coreType, theAcc)) if refs.isConst || forParams // We assume capture set variables in parameters don't generate param dependencies then status1 else combine(status1, Provisional) @@ -4119,10 +4120,6 @@ object Types extends TypeUtils { range(defn.NothingType, atVariance(1)(apply(tp.underlying))) case CapturingType(_, _) => mapOver(tp) - case ReachCapability(tp1) => - apply(tp1) match - case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach - case _ => root.cap case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) => val parent1 = mapOver(parent) if ann.symbol.isRetainsLike then @@ -4134,10 +4131,10 @@ object Types extends TypeUtils { case _ => mapOver(tp) } override def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match - case ReachCapability(tp1) => - apply(tp1) match - case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach - case _ => root.cap + case Reach(c1) => + apply(c1) match + case tp1a: ObjectCapability if tp1a.isTrackableRef => tp1a.reach + case _ => GlobalCap case _ => super.mapCapability(c, deep) } dropDependencies(resultType) @@ -4287,7 +4284,7 @@ object Types extends TypeUtils { ps.get(elemName) match case Some(elemRef) => assert(elemRef eq elem, i"bad $mt") case _ => - case root.Result(binder: MethodType) if binder ne mt => + case ResultCap(binder: MethodType) if binder ne mt => assert(binder.paramNames.toList != mt.paramNames.toList, i"bad $mt") case _ => checkRefs(refs) @@ -4793,7 +4790,7 @@ object Types extends TypeUtils { override def hashIsStable: Boolean = false } - abstract class ParamRef extends BoundType, CaptureRef { + abstract class ParamRef extends BoundType, CoreCapability { type BT <: LambdaType def paramNum: Int def paramName: binder.ThisName = binder.paramNames(paramNum) @@ -4828,7 +4825,7 @@ object Types extends TypeUtils { * refer to `TermParamRef(binder, paramNum)`. */ abstract case class TermParamRef(binder: TermLambda, paramNum: Int) - extends ParamRef, SingletonCaptureRef { + extends ParamRef, SingletonType, ObjectCapability { type BT = TermLambda def kindString: String = "Term" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -4840,7 +4837,7 @@ object Types extends TypeUtils { * refer to `TypeParamRef(binder, paramNum)`. */ abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) - extends ParamRef { + extends ParamRef, SetCapability { type BT = TypeLambda def kindString: String = "Type" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -5797,7 +5794,7 @@ object Types extends TypeUtils { // ----- Annotated and Import types ----------------------------------------------- /** An annotated type tpe @ annot */ - abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, CaptureRef { + abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, ValueType { override def underlying(using Context): Type = parent @@ -6257,7 +6254,7 @@ object Types extends TypeUtils { def toTrackableRef(tp: Type): CaptureRef | Null = tp match case CapturingType(_) => null - case tp: CaptureRef => + case tp: CoreCapability => if tp.isTrackableRef then tp else toTrackableRef(tp.underlying) case tp: TypeAlias => @@ -6265,22 +6262,22 @@ object Types extends TypeUtils { case _ => null - def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match - case root(_) => c - case ReachCapability(c1) => + def mapCapability(c: Capability, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + case c: RootCapability => c + case Reach(c1) => mapCapability(c1, deep = true) - case ReadOnlyCapability(c1) => + case ReadOnly(c1) => assert(!deep) mapCapability(c1) match case c2: CaptureRef => c2.readOnly case (cs: CaptureSet, exact) => (cs.readOnly, exact) - case MaybeCapability(c1) => + case Maybe(c1) => assert(!deep) mapCapability(c1) match case c2: CaptureRef => c2.maybe case (cs: CaptureSet, exact) => (cs.maybe, exact) - case ref => - val tp1 = apply(c) + case ref: CoreCapability => + val tp1 = apply(ref) val ref1 = toTrackableRef(tp1) if ref1 != null then if deep then ref1.reach @@ -6865,7 +6862,8 @@ object Types extends TypeUtils { foldOver(x2, tp.cases) case CapturingType(parent, refs) => - (this(x, parent) /: refs.elems)(this) + (this(x, parent) /: refs.elems): (x, elem) => + this(x, elem.coreType) case AnnotatedType(underlying, annot) => this(applyToAnnot(x, annot), underlying) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 9a5efbd10065..e1892dc2de46 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -17,6 +17,7 @@ import scala.annotation.switch import config.{Config, Feature} import ast.tpd import cc.* +import Capabilities.* class PlainPrinter(_ctx: Context) extends Printer { @@ -174,8 +175,10 @@ class PlainPrinter(_ctx: Context) extends Printer { core ~ cs.optionalInfo private def toTextRetainedElem[T <: Untyped](ref: Tree[T]): Text = ref match - case ref: RefTree[?] if ref.typeOpt.exists => - toTextCaptureRef(ref.typeOpt) + case ref: RefTree[?] => + ref.typeOpt match + case c: Capability => toTextCaptureRef(c) + case _ => toText(ref) case TypeApply(fn, arg :: Nil) if fn.symbol == defn.Caps_capsOf => toTextRetainedElem(arg) case ReachCapabilityApply(ref1) => toTextRetainedElem(ref1) ~ "*" @@ -195,7 +198,7 @@ class PlainPrinter(_ctx: Context) extends Printer { refs.elems.size == 1 && (refs.isUniversal || !printDebug && !ccVerbose && !showUniqueIds && refs.elems.nth(0).match - case root.Result(binder) => + case ResultCap(binder) => CCState.openExistentialScopes match case b :: _ => binder eq b case _ => false @@ -225,8 +228,6 @@ class PlainPrinter(_ctx: Context) extends Printer { homogenize(tp) match { case tp: TypeType => toTextRHS(tp) - case tp: TermRef if tp.isCap => - toTextCaptureRef(tp) case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView // always print underlying when testing picklers @@ -283,7 +284,7 @@ class PlainPrinter(_ctx: Context) extends Printer { val boxText: Text = Str("box ") provided tp.isBoxed //&& ctx.settings.YccDebug.value if elideCapabilityCaps && parent.derivesFrom(defn.Caps_Capability) - && refs.containsRootCapability + && refs.containsTerminalCapability && refs.isReadOnly then toText(parent) else toTextCapturing(parent, refs, boxText) @@ -458,27 +459,27 @@ class PlainPrinter(_ctx: Context) extends Printer { } } - def toTextCaptureRef(tp: Type): Text = - homogenize(tp) match - case tp: TermRef if tp.symbol == defn.captureRoot => "cap" - case tp: SingletonType => toTextRef(tp) - case tp: (TypeRef | TypeParamRef) => toText(tp) - case ReadOnlyCapability(tp1) => toTextCaptureRef(tp1) ~ ".rd" - case ReachCapability(tp1) => toTextCaptureRef(tp1) ~ "*" - case MaybeCapability(tp1) => toTextCaptureRef(tp1) ~ "?" - case tp @ root.Result(binder) => - val idStr = s"##${tp.rootAnnot.id}" - // TODO: Better printing? USe a mode where we print more detailed - val vbleText: Text = CCState.openExistentialScopes.indexOf(binder) match - case -1 => - "" - case n => "outer_" * n ++ (if ccVerbose then "localcap" else "cap") - vbleText ~ hashStr(binder) ~ Str(idStr).provided(showUniqueIds) - case tp @ root.Fresh(hidden) => - val idStr = if showUniqueIds then s"#${tp.rootAnnot.id}" else "" - if ccVerbose then s"" - else "cap" - case tp => toText(tp) + def toTextCaptureRef(c: Capability): Text = c match + case ReadOnly(c1) => toTextCaptureRef(c1) ~ ".rd" + case Reach(c1) => toTextCaptureRef(c1) ~ "*" + case Maybe(c1) => toTextCaptureRef(c1) ~ "?" + case GlobalCap => "cap" + case c: ResultCap => + def idStr = s"##${c.rootId}" + // TODO: Better printing? USe a mode where we print more detailed + val vbleText: Text = CCState.openExistentialScopes.indexOf(c.binder) match + case -1 => + "" + case n => "outer_" * n ++ (if ccVerbose then "localcap" else "cap") + vbleText ~ Str(hashStr(c.binder)).provided(printDebug) ~ Str(idStr).provided(showUniqueIds) + case c: FreshCap => + val idStr = if showUniqueIds then s"#${c.rootId}" else "" + if ccVerbose then s"" + else "cap" + case tp: TypeProxy => + homogenize(tp) match + case tp: SingletonType => toTextRef(tp) + case tp => toText(tp) protected def isOmittablePrefix(sym: Symbol): Boolean = defn.unqualifiedOwnerTypes.exists(_.symbol == sym) || isEmptyPrefix(sym) diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 9f485ee84cda..f42a530ca8e6 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -11,6 +11,7 @@ import typer.Implicits.* import util.SourcePosition import typer.ImportInfo import cc.CaptureSet +import cc.Capabilities.Capability import scala.annotation.internal.sharable @@ -108,7 +109,7 @@ abstract class Printer { def toTextRefinement(rt: RefinedType): Text /** Textual representation of a reference in a capture set */ - def toTextCaptureRef(tp: Type): Text + def toTextCaptureRef(ref: Capability): Text /** Textual representation of a reference in a capture set */ def toTextCaptureSet(cs: CaptureSet): Text diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ecc1250cbe7a..addfd4adf112 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -351,8 +351,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided printDebug) case tp @ PolyProto(targs, resType) => "[applied to [" ~ toTextGlobal(targs, ", ") ~ "] returning " ~ toText(resType) - case tp: AnnotatedType if tp.isTrackableRef => - toTextCaptureRef(tp) case _ => super.toText(tp) } diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 45260576f447..c400f83836dd 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -8,7 +8,8 @@ import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter} import printing.Texts.Text import printing.Formatting.hl import config.SourceVersion -import cc.{CaptureRef, CaptureSet, root, rootAnnot} +import cc.{CaptureSet, CaptureRef, root} +import cc.Capabilities.* import scala.language.unsafeNulls import scala.annotation.threadUnsafe @@ -196,17 +197,16 @@ object Message: else owner.show val descr = - if ref.isCap then "the universal root capability" - else ref match - case ref @ root.Fresh(hidden) => - val (kind: root.Kind.Fresh) = ref.rootAnnot.kind: @unchecked - val descr = kind.origin match + ref match + case GlobalCap => "the universal root capability" + case ref: FreshCap => + val descr = ref.origin match case origin @ root.Origin.InDecl(sym) if sym.exists => origin.explanation case origin => - i" created in ${ownerStr(hidden.owner)}${origin.explanation}" + i" created in ${ownerStr(ref.hiddenSet.owner)}${origin.explanation}" i"a fresh root capability$descr" - case root.Result(binder) => i"a root capability associated with the result type of $binder" + case ResultCap(binder) => i"a root capability associated with the result type of $binder" s"$relation $descr" end explanation @@ -218,7 +218,7 @@ object Message: case param: ParamRef => false case skolem: SkolemType => true case sym: Symbol => ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty - case ref: CaptureRef => ref.isRootCapability + case ref: CaptureRef => ref.isTerminalCapability } val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs => @@ -267,10 +267,9 @@ object Message: case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp) case _ => super.toTextRef(tp) - override def toTextCaptureRef(tp: Type): Text = tp match - case tp: CaptureRef if tp.isRootCapability && !tp.isReadOnly && seen.isActive => - seen.record("cap", isType = false, tp) - case _ => super.toTextCaptureRef(tp) + override def toTextCaptureRef(c: Capability): Text = c match + case c: RootCapability if seen.isActive => seen.record("cap", isType = false, c) + case _ => super.toTextCaptureRef(c) override def toTextCapturing(parent: Type, refs: GeneralCaptureSet, boxText: Text) = refs match case refs: CaptureSet diff --git a/tests/neg-custom-args/captures/ho-ref.scala b/tests/neg-custom-args/captures/ho-ref.scala new file mode 100644 index 000000000000..f465cb4439a8 --- /dev/null +++ b/tests/neg-custom-args/captures/ho-ref.scala @@ -0,0 +1,7 @@ +class Ref + +def test(a: Object^) = + val r: Ref^{a} = ??? + def mk1(op: (z: Ref^) -> Ref^{a} ->{z} Unit) = op(r) // error + def bad(x: Ref^)(y: Ref^{a}) = ??? + mk1(bad) diff --git a/tests/run-custom-args/captures/minicheck.scala b/tests/run-custom-args/captures/minicheck.scala index a6aca38ae704..2824f9ff6e24 100644 --- a/tests/run-custom-args/captures/minicheck.scala +++ b/tests/run-custom-args/captures/minicheck.scala @@ -1,9 +1,7 @@ - // A mini typechecker to experiment with arena allocated contexts import compiletime.uninitialized import annotation.{experimental, tailrec, constructorOnly} import collection.mutable -import language.`3.3` case class Symbol(name: String, initOwner: Symbol | Null) extends Pure: def owner = initOwner.nn From e47916ad178c6e36d45e73b4c4f885e40c4507e6 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 18 May 2025 12:59:23 +0200 Subject: [PATCH 6/7] Rename CaptureRef -> Capability --- .../cc/{CaptureRef.scala => Capability.scala} | 8 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 36 +++---- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 96 +++++++++---------- .../dotty/tools/dotc/cc/CheckCaptures.scala | 38 ++++---- .../src/dotty/tools/dotc/cc/SepCheck.scala | 32 +++---- compiler/src/dotty/tools/dotc/cc/Setup.scala | 6 +- compiler/src/dotty/tools/dotc/cc/root.scala | 10 +- .../dotty/tools/dotc/core/TypeComparer.scala | 3 +- .../src/dotty/tools/dotc/core/Types.scala | 14 +-- .../tools/dotc/printing/PlainPrinter.scala | 12 +-- .../dotty/tools/dotc/printing/Printer.scala | 2 +- .../dotty/tools/dotc/reporting/Message.scala | 12 +-- 12 files changed, 134 insertions(+), 135 deletions(-) rename compiler/src/dotty/tools/dotc/cc/{CaptureRef.scala => Capability.scala} (98%) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala similarity index 98% rename from compiler/src/dotty/tools/dotc/cc/CaptureRef.scala rename to compiler/src/dotty/tools/dotc/cc/Capability.scala index 1ae2feda972e..8918d182d405 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -23,8 +23,6 @@ import printing.{Printer, Showable} import printing.Texts.Text import annotation.internal.sharable -type CaptureRef = Capabilities.Capability - /** Capability --+-- RootCapabilty -----+-- GlobalCap * | +-- FreshCap * | +-- ResultCap @@ -67,7 +65,7 @@ object Capabilities: trait DerivedCapability extends Capability: def underlying: Capability - /** If `x` is a capture ref, its maybe capability `x?`. `x?` stands for a capability + /** If `x` is a capability, its maybe capability `x?`. `x?` stands for a capability * `x` that might or might not be part of a capture set. We have `{} <: {x?} <: {x}`. * Maybe capabilities cannot be propagated between sets. If `a <: b` and `a` * acquires `x?` then `x` is propagated to `b` as a conservative approximation. @@ -96,7 +94,7 @@ object Capabilities: extends DerivedCapability: assert(!underlying.isInstanceOf[Maybe]) - /** If `x` is a capture ref, its reach capability `x*`. `x*` stands for all + /** If `x` is a capability, its reach capability `x*`. `x*` stands for all * capabilities reachable through `x`. * We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x` * is the union of all capture sets that appear in covariant position in the @@ -521,6 +519,6 @@ object Capabilities: def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[Capability] = CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) - def toText(printer: Printer): Text = printer.toTextCaptureRef(this) + def toText(printer: Printer): Text = printer.toTextCapability(this) end Capability end Capabilities \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 832cfc51590c..1be2542cc551 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -38,7 +38,7 @@ def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames else make(args, resultType) mt.toFunctionType(alwaysDependent = true) -/** An exception thrown if a @retains argument is not syntactically a CaptureRef */ +/** An exception thrown if a @retains argument is not syntactically a Capability */ class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show) /** A base trait for data producing addenda to error messages */ @@ -50,21 +50,21 @@ def ccState(using Context): CCState = extension (tree: Tree) - /** Map tree with CaptureRef type to its type, + /** Map tree with a Capability type to the corresponding capability, * map CapSet^{refs} to the `refs` references, * throw IllegalCaptureRef otherwise */ - def toCaptureRefs(using Context): List[CaptureRef] = tree match + def toCapabilities(using Context): List[Capability] = tree match case ReachCapabilityApply(arg) => - arg.toCaptureRefs.map(_.reach) + arg.toCapabilities.map(_.reach) case ReadOnlyCapabilityApply(arg) => - arg.toCaptureRefs.map(_.readOnly) + arg.toCapabilities.map(_.readOnly) case CapsOfApply(arg) => - arg.toCaptureRefs + arg.toCapabilities case _ => tree.tpe.dealiasKeepAnnots match case ref: TermRef if ref.isCapRef => GlobalCap :: Nil - case ref: CaptureRef if ref.isTrackableRef => + case ref: Capability if ref.isTrackableRef => ref :: Nil case AnnotatedType(parent, ann) if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => @@ -79,7 +79,7 @@ extension (tree: Tree) tree.getAttachment(Captures) match case Some(refs) => refs case None => - val refs = CaptureSet(tree.retainedElems.flatMap(_.toCaptureRefs)*) + val refs = CaptureSet(tree.retainedElems.flatMap(_.toCapabilities)*) //.showing(i"toCaptureSet $tree --> $result", capt) tree.putAttachment(Captures, refs) refs @@ -95,7 +95,7 @@ extension (tree: Tree) extension (tp: Type) - /** Is this type a CaptureRef that can be tracked? + /** Is this type a Capability that can be tracked? * This is true for * - all ThisTypes and all TermParamRef, * - stable TermRefs with NoPrefix or ThisTypes as prefixes, @@ -120,9 +120,9 @@ extension (tp: Type) false /** The capture set of a type. This is: - * - For trackable capture references: The singleton capture set consisting of + * - For trackable capabilities: The singleton capture set consisting of * just the reference, provided the underlying capture set of their info is not empty. - * - For other capture references: The capture set of their info + * - For other capabilities: The capture set of their info * - For all other types: The result of CaptureSet.ofType */ final def captureSet(using Context): CaptureSet = tp match @@ -149,7 +149,7 @@ extension (tp: Type) deepCaptureSet(includeTypevars = false) /** A type capturing `ref` */ - def capturing(ref: CaptureRef)(using Context): Type = + def capturing(ref: Capability)(using Context): Type = if tp.captureSet.accountsFor(ref) then tp else CapturingType(tp, ref.singletonCaptureSet) @@ -200,7 +200,7 @@ extension (tp: Type) /** The capture set consisting of all top-level captures of `tp` that appear under a box. * Unlike for `boxed` this also considers parents of capture types, unions and * intersections, and type proxies other than abstract types. - * Furthermore, if the original type is a capture ref `x`, it replaces boxed universal sets + * Furthermore, if the original type is a capability `x`, it replaces boxed universal sets * on the fly with x*. */ def boxedCaptureSet(using Context): CaptureSet = @@ -215,7 +215,7 @@ extension (tp: Type) pcs ++ reachRef.singletonCaptureSet case _ => pcs ++ refs - case ref: CaptureRef if ref.isTracked && !pre.exists => getBoxed(ref, ref) + case ref: Capability if ref.isTracked && !pre.exists => getBoxed(ref, ref) case tp: TypeRef if tp.symbol.isAbstractOrParamType => CaptureSet.empty case tp: TypeProxy => getBoxed(tp.superType, pre) case tp: AndType => getBoxed(tp.tp1, pre) ** getBoxed(tp.tp2, pre) @@ -249,7 +249,7 @@ extension (tp: Type) def forceBoxStatus(boxed: Boolean)(using Context): Type = tp.widenDealias match case tp @ CapturingType(parent, refs) if tp.isBoxed != boxed => val refs1 = tp match - case ref: CaptureRef if ref.isTracked || ref.isReach || ref.isReadOnly => + case ref: Capability if ref.isTracked || ref.isReach || ref.isReadOnly => ref.singletonCaptureSet case _ => refs CapturingType(parent, refs1, boxed) @@ -347,7 +347,7 @@ extension (tp: Type) mapOver(t) tm(tp) - /** If `x` is a capture ref, replace all no-flip covariant occurrences of `cap` + /** If `x` is a capability, replace all no-flip covariant occurrences of `cap` * in type `tp` with `x*`. */ def withReachCaptures(ref: Type)(using Context): Type = ref match @@ -620,9 +620,9 @@ object ContainsImpl: /** An extractor for a contains parameter */ object ContainsParam: - def unapply(sym: Symbol)(using Context): Option[(TypeRef, CaptureRef)] = + def unapply(sym: Symbol)(using Context): Option[(TypeRef, Capability)] = sym.info.dealias match - case AppliedType(tycon, (cs: TypeRef) :: (ref: CaptureRef) :: Nil) + case AppliedType(tycon, (cs: TypeRef) :: (ref: Capability) :: Nil) if tycon.typeSymbol == defn.Caps_ContainsTrait && cs.typeSymbol.isAbstractOrParamType => Some((cs, ref)) case _ => None diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index ce9af5876393..a107ebb0b10c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -32,14 +32,14 @@ import Capabilities.* * That is, constraints can be of the forms * * cs1 <:< cs2 - * cs1 = ∪ {f(x) | x ∈ cs2} where f is a function from capture references to capture sets. - * cs1 = ∪ {x | x ∈ cs2, p(x)} where p is a predicate on capture references + * cs1 = ∪ {f(x) | x ∈ cs2} where f is a function from capabilities to capture sets. + * cs1 = ∪ {x | x ∈ cs2, p(x)} where p is a predicate on capabilities * cs1 = cs2 ∩ cs2 * * We call the resulting constraint system "monadic set constraints". * To support capture propagation across maps, mappings are supported only * if the mapped function is either a bijection or if it is idempotent - * on capture references (c.f. doc comment on `map` below). + * on capabilities (c.f. doc comment on `map` below). */ sealed abstract class CaptureSet extends Showable: import CaptureSet.* @@ -129,7 +129,7 @@ sealed abstract class CaptureSet extends Showable: * element is not the root capability, try instead to include its underlying * capture set. */ - protected def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + protected def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): CompareResult = if accountsFor(elem) then CompareResult.OK else addNewElem(elem) @@ -145,7 +145,7 @@ sealed abstract class CaptureSet extends Showable: * element is not the root capability, try instead to include its underlying * capture set. */ - protected final def addNewElem(elem: CaptureRef)(using ctx: Context, vs: VarState): CompareResult = + protected final def addNewElem(elem: Capability)(using ctx: Context, vs: VarState): CompareResult = if elem.isTerminalCapability || !vs.isOpen then addThisElem(elem) else @@ -164,9 +164,9 @@ sealed abstract class CaptureSet extends Showable: * and omitting any mapping or filtering, without possibility to backtrack * to the underlying capture set. */ - protected def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult + protected def addThisElem(elem: Capability)(using Context, VarState): CompareResult - protected def addIfHiddenOrFail(elem: CaptureRef)(using ctx: Context, vs: VarState): CompareResult = + protected def addIfHiddenOrFail(elem: Capability)(using ctx: Context, vs: VarState): CompareResult = if elems.exists(_.maxSubsumes(elem, canAddHidden = true)) then CompareResult.OK else CompareResult.Fail(this :: Nil) @@ -182,7 +182,7 @@ sealed abstract class CaptureSet extends Showable: /** {x} <:< this where <:< is subcapturing, but treating all variables * as frozen. */ - def accountsFor(x: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = + def accountsFor(x: Capability)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = def debugInfo(using Context) = i"$this accountsFor $x" @@ -208,7 +208,7 @@ sealed abstract class CaptureSet extends Showable: * a set `cs` might account for `x` only if it subsumes `x` or it contains the * root capability `cap`. */ - def mightAccountFor(x: CaptureRef)(using Context): Boolean = + def mightAccountFor(x: Capability)(using Context): Boolean = reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true): CCState.withCapAsRoot: CCState.ignoringFreshLevels: // OK here since we opportunistically choose an alternative which gets checked later @@ -265,7 +265,7 @@ sealed abstract class CaptureSet extends Showable: /** The smallest superset (via <:<) of this capture set that also contains `ref`. */ - def + (ref: CaptureRef)(using Context): CaptureSet = + def + (ref: Capability)(using Context): CaptureSet = this ++ ref.singletonCaptureSet /** The largest capture set (via <:<) that is a subset of both `this` and `that` @@ -287,13 +287,13 @@ sealed abstract class CaptureSet extends Showable: if that.isAlwaysEmpty then this else Diff(asVar, that) /** The largest subset (via <:<) of this capture set that does not account for `ref` */ - def - (ref: CaptureRef)(using Context): CaptureSet = + def - (ref: Capability)(using Context): CaptureSet = this -- ref.singletonCaptureSet /** The largest subset (via <:<) of this capture set that only contains elements * for which `p` is true. */ - def filter(p: Context ?=> CaptureRef => Boolean)(using Context): CaptureSet = + def filter(p: Context ?=> Capability => Boolean)(using Context): CaptureSet = if this.isConst then val elems1 = elems.filter(p) if elems1 == elems then this @@ -360,7 +360,7 @@ sealed abstract class CaptureSet extends Showable: * If the limit is some other symbol, cap and Result roots are bad, as well as * all Fresh roots that are contained (via ccOwner) in `rootLimit`. */ - protected def isBadRoot(rootLimit: Symbol | Null, elem: CaptureRef)(using Context): Boolean = + protected def isBadRoot(rootLimit: Symbol | Null, elem: Capability)(using Context): Boolean = if rootLimit == null then false else elem.core match case GlobalCap | _: ResultCap => true @@ -378,7 +378,7 @@ sealed abstract class CaptureSet extends Showable: /** Invoke handler on the elements to ensure wellformedness of the capture set. * The handler might add additional elements to the capture set. */ - def ensureWellformed(handler: CaptureRef => Context ?=> Unit)(using Context): this.type = + def ensureWellformed(handler: Capability => Context ?=> Unit)(using Context): this.type = elems.foreach(handler(_)) this @@ -420,7 +420,7 @@ sealed abstract class CaptureSet extends Showable: def processElems[T](f: Refs => T): T = f(elems) object CaptureSet: - type Refs = SimpleIdentitySet[CaptureRef] + type Refs = SimpleIdentitySet[Capability] type Vars = SimpleIdentitySet[Var] type Deps = SimpleIdentitySet[CaptureSet] @@ -467,7 +467,7 @@ object CaptureSet: def isAlwaysEmpty(using Context) = elems.isEmpty def isProvisionallySolved(using Context) = false - def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = + def addThisElem(elem: Capability)(using Context, VarState): CompareResult = val res = addIfHiddenOrFail(elem) if !res.isOK && this.isProvisionallySolved then println(i"Cannot add $elem to provisionally solved $this") @@ -486,7 +486,7 @@ object CaptureSet: override def toString = elems.toString end Const - case class EmptyWithProvenance(ref: CaptureRef, mapped: CaptureSet) extends Const(SimpleIdentitySet.empty): + case class EmptyWithProvenance(ref: Capability, mapped: CaptureSet) extends Const(SimpleIdentitySet.empty): override def optionalInfo(using Context): String = if ctx.settings.YccDebug.value then i" under-approximating the result of mapping $ref to $mapped" @@ -500,9 +500,9 @@ object CaptureSet: */ object Fluid extends Const(emptyRefs): override def isAlwaysEmpty(using Context) = false - override def addThisElem(elem: CaptureRef)(using Context, VarState) = CompareResult.OK - override def accountsFor(x: CaptureRef)(using Context)(using VarState): Boolean = true - override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true + override def addThisElem(elem: Capability)(using Context, VarState) = CompareResult.OK + override def accountsFor(x: Capability)(using Context)(using VarState): Boolean = true + override def mightAccountFor(x: Capability)(using Context): Boolean = true override def toString = "" end Fluid @@ -553,7 +553,7 @@ object CaptureSet: private[CaptureSet] var rootLimit: Symbol | Null = null /** A handler to be invoked when new elems are added to this set */ - var newElemAddedHandler: CaptureRef => Context ?=> Unit = _ => () + var newElemAddedHandler: Capability => Context ?=> Unit = _ => () var description: String = "" @@ -584,13 +584,13 @@ object CaptureSet: /** Check that all maps recorded in skippedMaps map `elem` to itself * or something subsumed by it. */ - private def checkSkippedMaps(elem: CaptureRef)(using Context): Unit = + private def checkSkippedMaps(elem: Capability)(using Context): Unit = for tm <- skippedMaps do - for elem1 <- extrapolateCaptureRef(elem, tm, variance = 1).elems do + for elem1 <- mappedSet(elem, tm, variance = 1).elems do assert(elem.subsumes(elem1), i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this") - final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = + final def addThisElem(elem: Capability)(using Context, VarState): CompareResult = if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen addIfHiddenOrFail(elem) else if !levelOK(elem) then @@ -634,7 +634,7 @@ object CaptureSet: rootLimit == null case elem: TermRef if level.isDefined => elem.prefix match - case prefix: CaptureRef => + case prefix: Capability => levelOK(prefix) case _ => ccState.symLevel(elem.symbol) <= level @@ -667,7 +667,7 @@ object CaptureSet: rootAddedHandler = handler super.disallowRootCapability(upto)(handler) - override def ensureWellformed(handler: CaptureRef => (Context) ?=> Unit)(using Context): this.type = + override def ensureWellformed(handler: Capability => (Context) ?=> Unit)(using Context): this.type = newElemAddedHandler = handler super.ensureWellformed(handler) @@ -810,7 +810,7 @@ object CaptureSet: (val source: Var, val bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, initialElems): - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): CompareResult = if origin eq source then val mappedElem = bimap.mapCapability(elem) if accountsFor(mappedElem) then CompareResult.OK @@ -845,10 +845,10 @@ object CaptureSet: /** A variable with elements given at any time as { x <- source.elems | p(x) } */ class Filtered private[CaptureSet] - (val source: Var, val p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) + (val source: Var, val p: Context ?=> Capability => Boolean)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, source.elems.filter(p)): - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): CompareResult = if accountsFor(elem) then CompareResult.OK else if origin eq source then @@ -880,7 +880,7 @@ object CaptureSet: addAsDependentTo(cs1) addAsDependentTo(cs2) - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): CompareResult = if accountsFor(elem) then CompareResult.OK else val res = super.tryInclude(elem, origin) @@ -904,7 +904,7 @@ object CaptureSet: deps += cs1 deps += cs2 - override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): CompareResult = val present = if origin eq cs1 then cs2.accountsFor(elem) else if origin eq cs2 then cs1.accountsFor(elem) @@ -981,7 +981,7 @@ object CaptureSet: * hidden set H which is a superset of this set, then make this set an * alias of H. */ - def add(elem: CaptureRef)(using ctx: Context, vs: VarState): Unit = + def add(elem: Capability)(using ctx: Context, vs: VarState): Unit = val alias = aliasSet if alias ne this then alias.add(elem) else @@ -1018,14 +1018,14 @@ object CaptureSet: end HiddenSet /** Extrapolate tm(r) according to `variance`. Let r1 be the result of tm(r). - * - If r1 is a tracked CaptureRef, return {r1} + * - If r1 is a tracked capability, return {r1} * - If r1 has an empty capture set, return {} * - Otherwise, * - if the variance is covariant, return r1's capture set * - if the variance is contravariant, return {} * - Otherwise assertion failure */ - final def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = + final def mappedSet(r: Capability, tm: TypeMap, variance: Int)(using Context): CaptureSet = tm.mapCapability(r) match case c: CoreCapability => c.captureSet case c: Capability => c.singletonCaptureSet @@ -1035,12 +1035,12 @@ object CaptureSet: else cs.maybe /** Apply `f` to each element in `xs`, and join result sets with `++` */ - def mapRefs(xs: Refs, f: CaptureRef => CaptureSet)(using Context): CaptureSet = + def mapRefs(xs: Refs, f: Capability => CaptureSet)(using Context): CaptureSet = ((empty: CaptureSet) /: xs)((cs, x) => cs ++ f(x)) /** Apply extrapolated `tm` to each element in `xs`, and join result sets with `++` */ def mapRefs(xs: Refs, tm: TypeMap, variance: Int)(using Context): CaptureSet = - mapRefs(xs, extrapolateCaptureRef(_, tm, variance)) + mapRefs(xs, mappedSet(_, tm, variance)) /** Return true iff * - arg1 is a TypeBounds >: CL T <: CH T of two capturing types with equal parents. @@ -1056,14 +1056,14 @@ object CaptureSet: case _ => false - /** A TypeMap that is the identity on capture references */ + /** A TypeMap that is the identity on capabilities */ trait IdentityCaptRefMap extends TypeMap /** A value of this class is produced and added as a note to ccState * when a subsumes check decides that an existential variable `ex` cannot be * instantiated to the other capability `other`. */ - case class ExistentialSubsumesFailure(val ex: ResultCap, val other: CaptureRef) extends ErrorNote + case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends ErrorNote trait CompareFailure: private var myErrorNotes: List[ErrorNote] = Nil @@ -1075,7 +1075,7 @@ object CaptureSet: enum CompareResult extends Showable: case OK case Fail(trace: List[CaptureSet]) extends CompareResult, CompareFailure - case LevelError(cs: CaptureSet, elem: CaptureRef) extends CompareResult, CompareFailure, ErrorNote + case LevelError(cs: CaptureSet, elem: Capability) extends CompareResult, CompareFailure, ErrorNote override def toText(printer: Printer): Text = inContext(printer.printerContext): @@ -1084,7 +1084,7 @@ object CaptureSet: case Fail(trace) => if ctx.settings.YccDebug.value then printer.toText(trace, ", ") else blocking.show - case LevelError(cs: CaptureSet, elem: CaptureRef) => + case LevelError(cs: CaptureSet, elem: Capability) => Str(i"($elem at wrong level for $cs at level ${cs.level.toString})") /** The result is OK */ @@ -1164,7 +1164,7 @@ object CaptureSet: * return whether this was allowed. By default, recording is allowed * but the special state VarState.Separate overrides this. */ - def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = + def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = elemsMap.get(hidden) match case None => elemsMap(hidden) = hidden.elems @@ -1204,10 +1204,10 @@ object CaptureSet: depsMap.keysIterator.foreach(_.resetDeps()(using this)) if eqResultSnapshot != null then eqResultMap = eqResultSnapshot.nn - private var seen: util.EqHashSet[CaptureRef] = new util.EqHashSet + private var seen: util.EqHashSet[Capability] = new util.EqHashSet /** Run test `pred` unless `ref` was seen in an enclosing `ifNotSeen` operation */ - def ifNotSeen(ref: CaptureRef)(pred: => Boolean): Boolean = + def ifNotSeen(ref: Capability)(pred: => Boolean): Boolean = if seen.add(ref) then try pred finally seen -= ref else false @@ -1233,7 +1233,7 @@ object CaptureSet: * No new references can be added. */ class Separating extends Closed: - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = false + override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = false override def toString = "separating varState" override def isSeparating = true @@ -1255,7 +1255,7 @@ object CaptureSet: override def putElems(v: Var, refs: Refs) = true override def putDeps(v: Var, deps: Deps) = true override def rollBack(): Unit = () - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true + override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true override def toString = "unrecorded varState" def Unrecorded(using Context): Unrecorded = ccState.Unrecorded @@ -1264,7 +1264,7 @@ object CaptureSet: * adding them). Used in `mightAccountFor`. Instantiated in ccState.ClosedUnrecorded. */ class ClosedUnrecorded extends Closed: - override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true + override def addHidden(hidden: HiddenSet, elem: Capability)(using Context): Boolean = true override def toString = "closed unrecorded varState" def ClosedUnrecorded(using Context): ClosedUnrecorded = ccState.ClosedUnrecorded @@ -1329,7 +1329,7 @@ object CaptureSet: css.foldLeft(empty)(_ ++ _) */ - /** The capture set of the type underlying CaptureRef */ + /** The capture set of the type underlying the capability `c` */ def ofInfo(c: Capability)(using Context): CaptureSet = c match case Reach(c1) => c1.widen.deepCaptureSet(includeTypevars = true) @@ -1406,7 +1406,7 @@ object CaptureSet: else this(acc, upperBound) collect(CaptureSet.empty, tp) - type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[CaptureRef]] + type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[Capability]] val AssumedContains: Property.Key[AssumedContains] = Property.Key() def assumedContains(using Context): AssumedContains = diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 2797dc80808b..64c5ced0c763 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -101,7 +101,7 @@ object CheckCaptures: */ def checkWellformed(parent: Tree, ann: Tree)(using Context): Unit = def check(elem: Tree, pos: SrcPos): Unit = elem.tpe match - case ref: CaptureRef => + case ref: Capability => if !ref.isTrackableRef && !ref.isCapRef then report.error(em"$elem cannot be tracked since it is not a parameter or local value", pos) case tpe => @@ -352,7 +352,7 @@ class CheckCaptures extends Recheck, SymTransformer: assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2") /** If `res` is not CompareResult.OK, report an error */ - def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = + def checkOK(res: CompareResult, prefix: => String, added: Capability | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = res match case res: CompareFailure => def msg(provisional: Boolean) = @@ -371,14 +371,14 @@ class CheckCaptures extends Recheck, SymTransformer: pos) needAnotherRun = true added match - case added: CaptureRef => target.elems += added + case added: Capability => target.elems += added case added: CaptureSet => target.elems ++= added.elems case _ => report.error(msg(provisional = false), pos) case _ => /** Check subcapturing `{elem} <: cs`, report error on failure */ - def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = + def checkElem(elem: Capability, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = checkOK( ccState.test(elem.singletonCaptureSet.subCaptures(cs)), i"$elem cannot be referenced here; it is not", @@ -441,7 +441,7 @@ class CheckCaptures extends Recheck, SymTransformer: def markFree(sym: Symbol, tree: Tree)(using Context): Unit = markFree(sym, sym.termRef, tree) - def markFree(sym: Symbol, ref: CaptureRef, tree: Tree)(using Context): Unit = + def markFree(sym: Symbol, ref: Capability, tree: Tree)(using Context): Unit = if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree) /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing @@ -459,10 +459,10 @@ class CheckCaptures extends Recheck, SymTransformer: !sym.isContainedIn(env.owner) } - /** If captureRef `c` refers to a parameter that is not @use declared, report an error. + /** If capability `c` refers to a parameter that is not @use declared, report an error. * Exception under deferredReaches: If use comes from a nested closure, accept it. */ - def checkUseDeclared(c: CaptureRef, env: Env, lastEnv: Env | Null) = + def checkUseDeclared(c: Capability, env: Env, lastEnv: Env | Null) = if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK else c.pathRoot match @@ -476,7 +476,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** Avoid locally defined capability by charging the underlying type * (which may not be cap). This scheme applies only under the deferredReaches setting. */ - def avoidLocalCapability(c: CaptureRef, env: Env, lastEnv: Env | Null): Unit = + def avoidLocalCapability(c: Capability, env: Env, lastEnv: Env | Null): Unit = if c.isParamPath then c match case Reach(_) | _: TypeRef => @@ -497,7 +497,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** Avoid locally defined capability if it is a reach capability or capture set * parameter. This is the default. */ - def avoidLocalReachCapability(c: CaptureRef, env: Env): Unit = c match + def avoidLocalReachCapability(c: Capability, env: Env): Unit = c match case Reach(c1) => if c1.isParamPath then checkUseDeclared(c, env, null) @@ -552,7 +552,7 @@ class CheckCaptures extends Recheck, SymTransformer: def includeCallCaptures(sym: Symbol, resType: Type, tree: Tree)(using Context): Unit = resType match case _: MethodOrPoly => // wait until method is fully applied case _ => - def isRetained(ref: CaptureRef): Boolean = ref.pathRoot match + def isRetained(ref: Capability): Boolean = ref.pathRoot match case root: ThisType => ctx.owner.isContainedIn(root.cls) case _ => true if sym.exists && curEnv.isOpen then @@ -617,7 +617,7 @@ class CheckCaptures extends Recheck, SymTransformer: // modifier implied by the expected type `pt`. // Example: If we have `x` and the expected type says we select that with `.a.b` // where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`. - def addSelects(ref: TermRef, pt: Type): CaptureRef = pt match + def addSelects(ref: TermRef, pt: Type): Capability = pt match case pt: PathSelectionProto if ref.isTracked => if pt.sym.isReadOnlyMethod then ref.readOnly @@ -626,7 +626,7 @@ class CheckCaptures extends Recheck, SymTransformer: // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - var pathRef: CaptureRef = addSelects(sym.termRef, pt) + var pathRef: Capability = addSelects(sym.termRef, pt) if pathRef.derivesFromMutable && pt.isValueType && !pt.isMutableType then pathRef = pathRef.readOnly markFree(sym, pathRef, tree) @@ -683,7 +683,7 @@ class CheckCaptures extends Recheck, SymTransformer: // Don't apply the rule // - on the LHS of assignments, or // - if the qualifier or selection type is boxed, or - // - the selection is either a trackable capture ref or a pure type + // - the selection is either a trackable capture reference or a pure type if noWiden(selType, pt) || qualType.isBoxedCapturing || selWiden.isBoxedCapturing @@ -897,7 +897,7 @@ class CheckCaptures extends Recheck, SymTransformer: val ref = refArg.nuType capt.println(i"check contains $cs , $ref") ref match - case ref: CaptureRef if ref.isTracked => + case ref: Capability if ref.isTracked => checkElem(ref, cs, tree.srcPos) case _ => report.error(em"$refArg is not a tracked capability", refArg.srcPos) @@ -1299,8 +1299,8 @@ class CheckCaptures extends Recheck, SymTransformer: /** Addendas for error messages that show where we have under-approximated by - * mapping a a capture ref in contravariant position to the empty set because - * the original result type of the map was not itself a capture ref. + * mapping a a capability in contravariant position to the empty set because + * the original result type of the map was not itself a capability. */ private def addApproxAddenda(using Context) = new TypeAccumulator[Addenda]: @@ -1406,7 +1406,7 @@ class CheckCaptures extends Recheck, SymTransformer: // set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing // class `C`. If an added reference is not a ThisType itself, add it to the capture set // (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed - // by `C.this` becomes a capture reference of every instance of `C`. + // by `C.this` becomes a capability of every instance of `C`. def augment(erefs: CaptureSet, arefs: CaptureSet): CaptureSet = (erefs /: erefs.elems): (erefs, eref) => eref match @@ -1546,7 +1546,7 @@ class CheckCaptures extends Recheck, SymTransformer: recur(actual, expected, covariant) end adaptBoxed - /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C, + /** If actual is a tracked Capability `a` and widened is a capturing type T^C, * improve `T^C` to `T^{a}`, following the VAR rule of CC. * TODO: We probably should do this also for other top-level occurrences of captures * E.g. @@ -1556,7 +1556,7 @@ class CheckCaptures extends Recheck, SymTransformer: * foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo} */ private def improveCaptures(widened: Type, prefix: Type)(using Context): Type = prefix match - case ref: CaptureRef if ref.isTracked => + case ref: Capability if ref.isTracked => widened match case widened @ CapturingType(p, refs) if ref.singletonCaptureSet.mightSubcapture(refs) => val improvedCs = diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index 514ad1fbc6ce..6dad0e9a2ff7 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -73,7 +73,7 @@ object SepCheck: */ abstract class ConsumedSet: /** The references in the set. The array should be treated as immutable in client code */ - def refs: Array[CaptureRef] + def refs: Array[Capability] /** The associated source positoons. The array should be treated as immutable in client code */ def locs: Array[SrcPos] @@ -81,7 +81,7 @@ object SepCheck: /** The number of references in the set */ def size: Int - def toMap: Map[CaptureRef, SrcPos] = refs.take(size).zip(locs).toMap + def toMap: Map[Capability, SrcPos] = refs.take(size).zip(locs).toMap def show(using Context) = s"[${toMap.map((ref, loc) => i"$ref -> $loc").toList}]" @@ -90,12 +90,12 @@ object SepCheck: /** A fixed consumed set consisting of the given references `refs` and * associated source positions `locs` */ - class ConstConsumedSet(val refs: Array[CaptureRef], val locs: Array[SrcPos]) extends ConsumedSet: + class ConstConsumedSet(val refs: Array[Capability], val locs: Array[SrcPos]) extends ConsumedSet: def size = refs.size /** A mutable consumed set, which is initially empty */ class MutConsumedSet extends ConsumedSet: - var refs: Array[CaptureRef] = new Array(4) + var refs: Array[Capability] = new Array(4) var locs: Array[SrcPos] = new Array(4) var size = 0 var peaks: Refs = emptyRefs @@ -111,12 +111,12 @@ object SepCheck: locs = double(locs) /** If `ref` is in the set, its associated source position, otherwise `null` */ - def get(ref: CaptureRef): SrcPos | Null = + def get(ref: Capability): SrcPos | Null = var i = 0 while i < size && (refs(i) ne ref) do i += 1 if i < size then locs(i) else null - def clashing(ref: CaptureRef)(using Context): SrcPos | Null = + def clashing(ref: Capability)(using Context): SrcPos | Null = val refPeaks = ref.peaks if !peaks.sharedWith(refPeaks).isEmpty then var i = 0 @@ -127,7 +127,7 @@ object SepCheck: else null /** If `ref` is not yet in the set, add it with given source position */ - def put(ref: CaptureRef, loc: SrcPos)(using Context): Unit = + def put(ref: Capability, loc: SrcPos)(using Context): Unit = if get(ref) == null then ensureCapacity(1) refs(size) = ref @@ -170,8 +170,8 @@ object SepCheck: * 3. if `f in F` then the footprint of `f`'s info is also in `F`. */ private def footprint(includeMax: Boolean = false)(using Context): Refs = - def retain(ref: CaptureRef) = includeMax || !ref.isTerminalCapability - def recur(elems: Refs, newElems: List[CaptureRef]): Refs = newElems match + def retain(ref: Capability) = includeMax || !ref.isTerminalCapability + def recur(elems: Refs, newElems: List[Capability]): Refs = newElems match case newElem :: newElems1 => val superElems = newElem.captureSetOfInfo.elems.filter: superElem => retain(superElem) && !elems.contains(superElem) @@ -181,7 +181,7 @@ object SepCheck: recur(elems, elems.toList) private def peaks(using Context): Refs = - def recur(seen: Refs, acc: Refs, newElems: List[CaptureRef]): Refs = trace(i"peaks $acc, $newElems = "): + def recur(seen: Refs, acc: Refs, newElems: List[Capability]): Refs = trace(i"peaks $acc, $newElems = "): newElems match case newElem :: newElems1 => if seen.contains(newElem) then @@ -253,9 +253,9 @@ object SepCheck: * its hidden set is `{y, z}`. */ private def hiddenSet(using Context): Refs = - val seen: util.EqHashSet[CaptureRef] = new util.EqHashSet + val seen: util.EqHashSet[Capability] = new util.EqHashSet - def hiddenByElem(elem: CaptureRef): Refs = elem match + def hiddenByElem(elem: Capability): Refs = elem match case elem: FreshCap => elem.hiddenSet.elems ++ recur(elem.hiddenSet.elems) case ReadOnly(elem1) => hiddenByElem(elem1).map(_.readOnly) case _ => emptyRefs @@ -280,7 +280,7 @@ object SepCheck: end extension - extension (ref: CaptureRef) + extension (ref: Capability) def peaks(using Context): Refs = SimpleIdentitySet(ref).peaks class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: @@ -420,7 +420,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * @param loc the position where the capability was consumed * @param pos the position where the capability was used again */ - def consumeError(ref: CaptureRef, loc: SrcPos, pos: SrcPos)(using Context): Unit = + def consumeError(ref: Capability, loc: SrcPos, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: Illegal access to $ref, which was passed to a |@consume parameter or was used as a prefix to a @consume method on line ${loc.line + 1} @@ -431,7 +431,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * @param ref the capability * @param loc the position where the capability was consumed */ - def consumeInLoopError(ref: CaptureRef, pos: SrcPos)(using Context): Unit = + def consumeInLoopError(ref: Capability, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: $ref appears in a loop, therefore it cannot |be passed to a @consume parameter or be used as a prefix of a @consume method call.""", @@ -576,7 +576,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: if pos != null then consumeError(ref, pos, tree.srcPos) end checkUse - /** If `tp` denotes some version of a singleton capture ref `x.type` the set `{x, x*}` + /** If `tp` denotes some version of a singleton capability `x.type` the set `{x, x*}` * otherwise the empty set. */ def explicitRefs(tp: Type)(using Context): Refs = tp match diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 601db08ca082..443252e396e0 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -947,7 +947,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for i <- 0 until retained.length do val refTree = retained(i) val refs = - try refTree.toCaptureRefs + try refTree.toCapabilities catch case ex: IllegalCaptureRef => report.error(em"Illegal capture reference: ${ex.getMessage}", refTree.srcPos) Nil @@ -971,7 +971,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val others = for j <- 0 until retained.length if j != i - r <- retained(j).toCaptureRefs + r <- retained(j).toCapabilities if !r.isTerminalCapability yield r val remaining = CaptureSet(others*) @@ -982,7 +982,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Check well formed at post check time. We need to wait until after * recheck because we find out only then whether capture sets are empty or - * capture references are redundant. + * capabilities are redundant. */ private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = if !tpt.span.isZeroExtent && enclosingInlineds.isEmpty then diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala index 5b32dfedc66d..69bdfe26a698 100644 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ b/compiler/src/dotty/tools/dotc/cc/root.scala @@ -132,7 +132,7 @@ object root: case _ => mapFollowingAliases(t) - override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + override def mapCapability(c: Capability, deep: Boolean): Capability = c match case GlobalCap => FreshCap(origin) case _ => super.mapCapability(c, deep) @@ -147,7 +147,7 @@ object root: case t @ CapturingType(_, refs) => mapOver(t) case _ => mapFollowingAliases(t) - override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + override def mapCapability(c: Capability, deep: Boolean): Capability = c match case _: FreshCap => GlobalCap case _ => super.mapCapability(c, deep) @@ -193,7 +193,7 @@ object root: case _ => mapOver(t) - override def mapCapability(c: CaptureRef, deep: Boolean) = c match + 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 @@ -231,7 +231,7 @@ object root: case _ => mapOver(t) - override def mapCapability(c: CaptureRef, deep: Boolean) = c match + override def mapCapability(c: Capability, deep: Boolean) = c match case c: (FreshCap | GlobalCap.type) => if variance > 0 then seen.getOrElseUpdate(c, ResultCap(mt)) @@ -250,7 +250,7 @@ object root: object inverse extends BiTypeMap: def apply(t: Type) = mapOver(t) - override def mapCapability(c: CaptureRef, deep: Boolean) = c match + override def mapCapability(c: Capability, deep: Boolean) = c match case c @ ResultCap(`mt`) => // do a reverse getOrElseUpdate on `seen` to produce the // `Fresh` assosicated with `t` diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b8a8be57ca05..1e17512b5609 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -23,6 +23,7 @@ import typer.Applications.productSelectorTypes import reporting.trace import annotation.constructorOnly import cc.* +import Capabilities.Capability import NameKinds.WildcardParamName import MatchTypes.isConcrete @@ -1003,7 +1004,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if isCaptureCheckingOrSetup then tp1 .match - case tp1: CaptureRef if tp1.isTracked => + case tp1: Capability if tp1.isTracked => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5cd38d269bd9..957bfbb5ec7a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4130,7 +4130,7 @@ object Types extends TypeUtils { parent1 case _ => mapOver(tp) } - override def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + override def mapCapability(c: Capability, deep: Boolean = false): Capability | (CaptureSet, Boolean) = c match case Reach(c1) => apply(c1) match case tp1a: ObjectCapability if tp1a.isTrackableRef => tp1a.reach @@ -6163,9 +6163,9 @@ object Types extends TypeUtils { def inverse: BiTypeMap /** A restriction of this map to a function on tracked Capabilities */ - override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = + override def mapCapability(c: Capability, deep: Boolean): Capability = super.mapCapability(c, deep) match - case c1: CaptureRef => c1 + case c1: Capability => c1 case (cs, _) => assert(false, i"bimap $toString should map $c to a capability, but result = $cs") /** Fuse with another map */ @@ -6251,7 +6251,7 @@ object Types extends TypeUtils { try derivedCapturingType(tp, this(parent), refs.map(this)) finally variance = saved - def toTrackableRef(tp: Type): CaptureRef | Null = tp match + def toTrackableRef(tp: Type): Capability | Null = tp match case CapturingType(_) => null case tp: CoreCapability => @@ -6262,19 +6262,19 @@ object Types extends TypeUtils { case _ => null - def mapCapability(c: Capability, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + def mapCapability(c: Capability, deep: Boolean = false): Capability | (CaptureSet, Boolean) = c match case c: RootCapability => c case Reach(c1) => mapCapability(c1, deep = true) case ReadOnly(c1) => assert(!deep) mapCapability(c1) match - case c2: CaptureRef => c2.readOnly + case c2: Capability => c2.readOnly case (cs: CaptureSet, exact) => (cs.readOnly, exact) case Maybe(c1) => assert(!deep) mapCapability(c1) match - case c2: CaptureRef => c2.maybe + case c2: Capability => c2.maybe case (cs: CaptureSet, exact) => (cs.maybe, exact) case ref: CoreCapability => val tp1 = apply(ref) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index e1892dc2de46..db94babff750 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -170,14 +170,14 @@ class PlainPrinter(_ctx: Context) extends Printer { else val core: Text = if !cs.isConst && cs.elems.isEmpty then "?" - else "{" ~ Text(cs.processElems(_.toList.map(toTextCaptureRef)), ", ") ~ "}" + else "{" ~ Text(cs.processElems(_.toList.map(toTextCapability)), ", ") ~ "}" // ~ Str("?").provided(!cs.isConst) core ~ cs.optionalInfo private def toTextRetainedElem[T <: Untyped](ref: Tree[T]): Text = ref match case ref: RefTree[?] => ref.typeOpt match - case c: Capability => toTextCaptureRef(c) + case c: Capability => toTextCapability(c) case _ => toText(ref) case TypeApply(fn, arg :: Nil) if fn.symbol == defn.Caps_capsOf => toTextRetainedElem(arg) @@ -459,10 +459,10 @@ class PlainPrinter(_ctx: Context) extends Printer { } } - def toTextCaptureRef(c: Capability): Text = c match - case ReadOnly(c1) => toTextCaptureRef(c1) ~ ".rd" - case Reach(c1) => toTextCaptureRef(c1) ~ "*" - case Maybe(c1) => toTextCaptureRef(c1) ~ "?" + def toTextCapability(c: Capability): Text = c match + case ReadOnly(c1) => toTextCapability(c1) ~ ".rd" + case Reach(c1) => toTextCapability(c1) ~ "*" + case Maybe(c1) => toTextCapability(c1) ~ "?" case GlobalCap => "cap" case c: ResultCap => def idStr = s"##${c.rootId}" diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index f42a530ca8e6..9b37589585f0 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -109,7 +109,7 @@ abstract class Printer { def toTextRefinement(rt: RefinedType): Text /** Textual representation of a reference in a capture set */ - def toTextCaptureRef(ref: Capability): Text + def toTextCapability(ref: Capability): Text /** Textual representation of a reference in a capture set */ def toTextCaptureSet(cs: CaptureSet): Text diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index c400f83836dd..52ce2722a47d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -8,7 +8,7 @@ import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter} import printing.Texts.Text import printing.Formatting.hl import config.SourceVersion -import cc.{CaptureSet, CaptureRef, root} +import cc.{CaptureSet, root} import cc.Capabilities.* import scala.language.unsafeNulls @@ -53,7 +53,7 @@ object Message: case None => false end Disambiguation - private type Recorded = Symbol | ParamRef | SkolemType | CaptureRef + private type Recorded = Symbol | ParamRef | SkolemType | Capability private case class SeenKey(str: String, isType: Boolean) @@ -183,7 +183,7 @@ object Message: s"is a ${ctx.printer.kindString(sym)}${sym.showExtendedLocation}${addendum("bounds", info)}" case tp: SkolemType => s"is an unknown value of type ${tp.widen.show}" - case ref: CaptureRef => + case ref: Capability => val relation = if List("^", "=>", "?=>").exists(key.startsWith) then "refers to" else "is" @@ -218,7 +218,7 @@ object Message: case param: ParamRef => false case skolem: SkolemType => true case sym: Symbol => ctx.gadt.contains(sym) && ctx.gadt.fullBounds(sym) != TypeBounds.empty - case ref: CaptureRef => ref.isTerminalCapability + case ref: Capability => ref.isTerminalCapability } val toExplain: List[(String, Recorded)] = seen.toList.flatMap { kvs => @@ -267,9 +267,9 @@ object Message: case tp: SkolemType => seen.record(tp.repr.toString, isType = true, tp) case _ => super.toTextRef(tp) - override def toTextCaptureRef(c: Capability): Text = c match + override def toTextCapability(c: Capability): Text = c match case c: RootCapability if seen.isActive => seen.record("cap", isType = false, c) - case _ => super.toTextCaptureRef(c) + case _ => super.toTextCapability(c) override def toTextCapturing(parent: Type, refs: GeneralCaptureSet, boxText: Text) = refs match case refs: CaptureSet From 441ccb22dd021936ad28de285a831c1fe46c2e6c Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 18 May 2025 14:21:51 +0200 Subject: [PATCH 7/7] Integrate root.scala in Capability.scala --- .../src/dotty/tools/dotc/cc/CCState.scala | 4 +- .../src/dotty/tools/dotc/cc/Capability.scala | 301 ++++++++++++++++- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 6 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 8 +- .../dotty/tools/dotc/cc/CheckCaptures.scala | 28 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 12 +- .../src/dotty/tools/dotc/cc/ccConfig.scala | 2 +- compiler/src/dotty/tools/dotc/cc/root.scala | 311 ------------------ .../dotty/tools/dotc/reporting/Message.scala | 4 +- 9 files changed, 326 insertions(+), 350 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/cc/root.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CCState.scala b/compiler/src/dotty/tools/dotc/cc/CCState.scala index 9fecf1076bf0..34da267897f9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CCState.scala +++ b/compiler/src/dotty/tools/dotc/cc/CCState.scala @@ -164,7 +164,7 @@ object CCState: /** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */ def capIsRoot(using Context): Boolean = ccState.capIsRoot - /** Run `op` under the assumption that all root.Fresh instances are equal. + /** Run `op` under the assumption that all FreshCap instances are equal. * Needed to make override checking of types containing fresh work. * Asserted in override checking, tested in maxSubsumes. * Is this sound? Test case is neg-custom-args/captures/leaked-curried.scala. @@ -177,7 +177,7 @@ object CCState: try op finally ccs.ignoreFreshLevels = saved else op - /** Should all root.Fresh instances be treated equal? */ + /** Should all FreshCap instances be treated equal? */ def ignoreFreshLevels(using Context): Boolean = ccState.ignoreFreshLevels end CCState diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 8918d182d405..7cd5f7c6fec0 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -4,7 +4,7 @@ package cc import core.* import Types.*, Symbols.*, Contexts.*, Decorators.* -import util.{SimpleIdentitySet, Property} +import util.{SimpleIdentitySet, EqHashMap} import typer.ErrorReporting.Addenda import util.common.alwaysTrue import scala.collection.mutable @@ -21,9 +21,14 @@ import annotation.constructorOnly import ast.tpd import printing.{Printer, Showable} import printing.Texts.Text +import reporting.Message +import NameOps.isImpureFunction import annotation.internal.sharable -/** Capability --+-- RootCapabilty -----+-- GlobalCap +/** Capabilities are members of capture sets. They partially overlap with types + * as shown in the trait hierarchy below. + * + * Capability --+-- RootCapabilty -----+-- GlobalCap * | +-- FreshCap * | +-- ResultCap * | @@ -47,7 +52,7 @@ object Capabilities: def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId) val invalid: Validity = validId(NoRunId, 0) - @sharable var nextRootId = 0 + @sharable private var nextRootId = 0 /** The base trait of all root capabilities */ trait RootCapability extends Capability: @@ -130,8 +135,7 @@ object Capabilities: * @param origin an indication where and why the FreshCap was created, used * for diagnostics */ - case class FreshCap private (owner: Symbol, origin: root.Origin)(using @constructorOnly ctx: Context) - extends RootCapability: + case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability: val hiddenSet = CaptureSet.HiddenSet(owner) hiddenSet.owningCap = this @@ -140,10 +144,38 @@ object Capabilities: case _ => false object FreshCap: - def apply(origin: root.Origin)(using Context): FreshCap | GlobalCap.type = + def apply(origin: Origin)(using Context): FreshCap | GlobalCap.type = if ccConfig.useSepChecks then FreshCap(ctx.owner, origin) else GlobalCap + /** A root capability associated with a function type. These are conceptually + * existentially quantified over the function's result type. + * @param binder The function type with which the capability is associated. + * It is a MethodicType since we also have ResultCaps that are + * associated with the ExprTypes of parameterless functions. + * Currently we never create results over PolyTypes. TODO change this? + * Setup: + * + * In the setup phase, `cap` instances in the result of a dependent function type + * or method type such as `(x: T): C^{cap}` are converted to `ResultCap(binder)` instances, + * where `binder` refers to the method type. Most other cap instances are mapped to + * Fresh instances instead. For example the `cap` in the result of `T => C^{cap}` + * is mapped to a Fresh instance. + * + * If one needs to use a dependent function type yet one still want to map `cap` to + * a fresh instance instead an existential root, one can achieve that by the use + * of a type alias. For instance, the following type creates an existential for `^`: + * + * (x: A) => (C^{x}, D^) + * + * By contrast, this variant creates a fresh instance instead: + * + * type F[X] = (x: A) => (C^{x}, X) + * F[D^] + * + * The trick is that the argument D^ is mapped to D^{fresh} before the `F` alias + * is expanded. + */ case class ResultCap(binder: MethodicType) extends RootCapability: private var myOriginalBinder = binder def originalBinder: MethodicType = myOriginalBinder @@ -283,7 +315,7 @@ object Capabilities: * - If it starts with a reference `r`, `r`'s owner. * - If it starts with cap, the `scala.caps` package class. * - If it starts with a fresh instance, its owner. - * - If it starts with a ParamRef or a result root, NoSymbol. + * - If it starts with a ParamRef or a ResultCap, NoSymbol. */ final def pathOwner(using Context): Symbol = pathRoot match case tp1: ThisType => tp1.cls @@ -521,4 +553,259 @@ object Capabilities: def toText(printer: Printer): Text = printer.toTextCapability(this) end Capability + + /** The place of - and cause for - creating a fresh capability. Used for + * error diagnostics + */ + enum Origin: + case InDecl(sym: Symbol) + case TypeArg(tp: Type) + case UnsafeAssumePure + case Formal(pref: ParamRef, app: tpd.Apply) + case ResultInstance(methType: Type, meth: Symbol) + case UnapplyInstance(info: MethodType) + case NewMutable(tp: Type) + case NewCapability(tp: Type) + case LambdaExpected(respt: Type) + case LambdaActual(restp: Type) + case OverriddenType(member: Symbol) + case DeepCS(ref: TypeRef) + case Unknown + + def explanation(using Context): String = this match + case InDecl(sym: Symbol) => + if sym.is(Method) then i" in the result type of $sym" + else if sym.exists then i" in the type of $sym" + else "" + case TypeArg(tp: Type) => + i" of type argument $tp" + case UnsafeAssumePure => + " when instantiating argument of unsafeAssumePure" + case Formal(pref, app) => + val meth = app.symbol + if meth.exists + then i" when checking argument to parameter ${pref.paramName} of $meth" + else "" + case ResultInstance(mt, meth) => + val methDescr = if meth.exists then i"$meth's type " else "" + i" when instantiating $methDescr$mt" + case UnapplyInstance(info) => + i" when instantiating argument of unapply with type $info" + case NewMutable(tp) => + i" when constructing mutable $tp" + case NewCapability(tp) => + i" when constructing Capability instance $tp" + case LambdaExpected(respt) => + i" when instantiating expected result type $respt of lambda" + case LambdaActual(restp: Type) => + i" when instantiating result type $restp of lambda" + case OverriddenType(member: Symbol) => + i" when instantiating upper bound of member overridden by $member" + case DeepCS(ref: TypeRef) => + i" when computing deep capture set of $ref" + case Unknown => + "" + end Origin + + // ---------- Maps between different kinds of root capabilities ----------------- + + + /** Map each occurrence of cap to a different Fresh instance + * Exception: CapSet^ stays as it is. + */ + class CapToFresh(origin: Origin)(using Context) extends BiTypeMap, FollowAliasesMap: + thisMap => + + override def apply(t: Type) = + if variance <= 0 then t + else t match + case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => + t + case t @ CapturingType(_, _) => + mapOver(t) + case t @ AnnotatedType(parent, ann) => + val parent1 = this(parent) + if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then + this(CapturingType(parent1, ann.tree.toCaptureSet)) + else + t.derivedAnnotatedType(parent1, ann) + case _ => + mapFollowingAliases(t) + + override def mapCapability(c: Capability, deep: Boolean): Capability = c match + case GlobalCap => FreshCap(origin) + case _ => super.mapCapability(c, deep) + + override def fuse(next: BiTypeMap)(using Context) = next match + case next: Inverse => assert(false); Some(IdentityTypeMap) + case _ => None + + override def toString = "CapToFresh" + + class Inverse extends BiTypeMap, FollowAliasesMap: + def apply(t: Type): Type = t match + case t @ CapturingType(_, refs) => mapOver(t) + case _ => mapFollowingAliases(t) + + override def mapCapability(c: Capability, deep: Boolean): Capability = c match + case _: FreshCap => GlobalCap + case _ => super.mapCapability(c, deep) + + def inverse = thisMap + override def toString = thisMap.toString + ".inverse" + + lazy val inverse = Inverse() + + end CapToFresh + + /** Maps cap to fresh. CapToFresh is a BiTypeMap since we don't want to + * freeze a set when it is mapped. On the other hand, we do not want Fresh + * values to flow back to cap since that would fail disallowRootCapability + * tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent + * the map being installed for future use. + */ + def capToFresh(tp: Type, origin: Origin)(using Context): Type = + if ccConfig.useSepChecks then + ccState.withoutMappedFutureElems: + CapToFresh(origin)(tp) + else tp + + /** Maps fresh to cap */ + def freshToCap(tp: Type)(using Context): Type = + if ccConfig.useSepChecks then CapToFresh(Origin.Unknown).inverse(tp) else tp + + /** Map top-level free existential variables one-to-one to Fresh instances */ + def resultToFresh(tp: Type, origin: Origin)(using Context): Type = + val subst = new TypeMap: + val seen = EqHashMap[ResultCap, FreshCap | GlobalCap.type]() + var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty + + def apply(t: Type): Type = t match + case t: MethodType => + // skip parameters + val saved = localBinders + if t.marksExistentialScope then localBinders = localBinders + t + try t.derivedLambdaType(resType = this(t.resType)) + finally localBinders = saved + case t: PolyType => + // skip parameters + t.derivedLambdaType(resType = this(t.resType)) + case _ => + mapOver(t) + + 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 + case _ => super.mapCapability(c, deep) + end subst + + subst(tp) + end resultToFresh + + /** Replace all occurrences of `cap` (or fresh) in parts of this type by an existentially bound + * variable bound by `mt`. + * Stop at function or method types since these have been mapped before. + */ + def toResult(tp: Type, mt: MethodicType, fail: Message => Unit)(using Context): Type = + + abstract class CapMap extends BiTypeMap: + override def mapOver(t: Type): Type = t match + case t @ FunctionOrMethod(args, res) if variance > 0 && !t.isAliasFun => + t // `t` should be mapped in this case by a different call to `mapCap`. + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + super.mapOver(t) + + object toVar extends CapMap: + private val seen = EqHashMap[RootCapability, ResultCap]() + + def apply(t: Type) = t match + case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => + if variance > 0 then + super.mapOver: + defn.FunctionNOf(args, res, contextual) + .capturing(ResultCap(mt).singletonCaptureSet) + else mapOver(t) + case _ => + mapOver(t) + + override def mapCapability(c: Capability, deep: Boolean) = c match + case c: (FreshCap | GlobalCap.type) => + if variance > 0 then + seen.getOrElseUpdate(c, ResultCap(mt)) + else + if variance == 0 then + fail(em"""$tp captures the root capability `cap` in invariant position. + |This capability cannot be converted to an existential in the result type of a function.""") + // we accept variance < 0, and leave the cap as it is + c + case _ => + super.mapCapability(c, deep) + + //.showing(i"mapcap $t = $result") + override def toString = "toVar" + + object inverse extends BiTypeMap: + def apply(t: Type) = mapOver(t) + + override def mapCapability(c: Capability, deep: Boolean) = c match + case c @ ResultCap(`mt`) => + // do a reverse getOrElseUpdate on `seen` to produce the + // `Fresh` assosicated with `t` + val it = seen.iterator + var ref: RootCapability | Null = null + while it.hasNext && ref == null do + val (k, v) = it.next + if v eq c then ref = k + if ref == null then + ref = FreshCap(Origin.Unknown) + seen(ref) = c + ref + case _ => + super.mapCapability(c, deep) + + def inverse = toVar.this + override def toString = "toVar.inverse" + end inverse + end toVar + + toVar(tp) + end toResult + + /** Map global roots in function results to result roots. Also, + * map roots in the types of parameterless def methods. + */ + def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type = + val m = new TypeMap with FollowAliasesMap: + def apply(t: Type): Type = t match + case AnnotatedType(parent @ defn.RefinedFunctionOf(mt), ann) if ann.symbol == defn.InferredDepFunAnnot => + val mt1 = mapOver(mt).asInstanceOf[MethodType] + if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) + else parent + case defn.RefinedFunctionOf(mt) => + val mt1 = apply(mt) + if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) + else t + case t: MethodType if variance > 0 && t.marksExistentialScope => + val t1 = mapOver(t).asInstanceOf[MethodType] + t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail)) + case CapturingType(parent, refs) => + t.derivedCapturingType(this(parent), refs) + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + try + if keepAliases then mapOver(t) + else mapFollowingAliases(t) + catch case ex: AssertionError => + println(i"error while mapping $t") + throw ex + m(tp) match + case tp1: ExprType if sym.is(Method, butNot = Accessor) => + tp1.derivedExprType(toResult(tp1.resType, tp1, fail)) + case tp1 => tp1 + end toResultInResults + end Capabilities \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 1be2542cc551..de54dea83d75 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -120,7 +120,7 @@ extension (tp: Type) false /** The capture set of a type. This is: - * - For trackable capabilities: The singleton capture set consisting of + * - For object capabilities: The singleton capture set consisting of * just the reference, provided the underlying capture set of their info is not empty. * - For other capabilities: The capture set of their info * - For all other types: The result of CaptureSet.ofType @@ -370,7 +370,7 @@ extension (tp: Type) // preceding arguments are known to be always pure t.derivedFunctionOrMethod( args, - apply(root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol)))) + apply(resultToFresh(res, Origin.ResultInstance(t, NoSymbol)))) else t case _ => @@ -654,7 +654,7 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]: this(acc, parent) case t @ FunctionOrMethod(args, res) => if args.forall(_.isAlwaysPure) then - this(acc, root.resultToFresh(res, root.Origin.ResultInstance(t, NoSymbol))) + this(acc, resultToFresh(res, Origin.ResultInstance(t, NoSymbol))) else acc case _ => foldOver(acc, t) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index a107ebb0b10c..1c599b885c64 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -441,7 +441,7 @@ object CaptureSet: */ val csImpliedByCapability = Const(SimpleIdentitySet(GlobalCap.readOnly)) - def fresh(origin: root.Origin)(using Context): Const = + def fresh(origin: Origin)(using Context): Const = FreshCap(origin).singletonCaptureSet /** The shared capture set `{cap.rd}` */ @@ -707,7 +707,7 @@ object CaptureSet: def solve()(using Context): Unit = CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later val approx = upperApprox(empty) - .map(root.CapToFresh(root.Origin.Unknown).inverse) // Fresh --> cap + .map(CapToFresh(Origin.Unknown).inverse) // Fresh --> cap .showing(i"solve $this = $result", capt) //println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}") val newElems = approx.elems -- elems @@ -1126,7 +1126,7 @@ object CaptureSet: /** A map from captureset variables to their dependent sets at the time of the snapshot. */ private val depsMap: util.EqHashMap[Var, Deps] = new util.EqHashMap - /** A map from root.Result values to other such values. If two result values + /** A map from ResultCap values to other ResultCap values. If two result values * `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`. */ private var eqResultMap: util.SimpleIdentityMap[ResultCap, ResultCap] = util.SimpleIdentityMap.empty @@ -1402,7 +1402,7 @@ object CaptureSet: def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet) = this(acc, parent) ++ refs def abstractTypeCase(acc: CaptureSet, t: TypeRef, upperBound: Type) = - if includeTypevars && upperBound.isExactlyAny then fresh(root.Origin.DeepCS(t)) + if includeTypevars && upperBound.isExactlyAny then fresh(Origin.DeepCS(t)) else this(acc, upperBound) collect(CaptureSet.empty, tp) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 64c5ced0c763..e920b0e5319d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -564,7 +564,7 @@ class CheckCaptures extends Recheck, SymTransformer: def mapResultRoots(tp: Type, sym: Symbol)(using Context): Type = tp.widenSingleton match case tp: ExprType if sym.is(Method) => - root.resultToFresh(tp, root.Origin.ResultInstance(tp, sym)) + resultToFresh(tp, Origin.ResultInstance(tp, sym)) case _ => tp @@ -726,7 +726,7 @@ class CheckCaptures extends Recheck, SymTransformer: val meth = tree.fun.symbol if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.stripCapturing.capturing(FreshCap(root.Origin.UnsafeAssumePure))) + val argType0 = recheck(arg, pt.stripCapturing.capturing(FreshCap(Origin.UnsafeAssumePure))) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -742,7 +742,7 @@ class CheckCaptures extends Recheck, SymTransformer: * charge the deep capture set of the actual argument to the environment. */ protected override def recheckArg(arg: Tree, formal: Type, pref: ParamRef, app: Apply)(using Context): Type = - val freshenedFormal = root.capToFresh(formal, root.Origin.Formal(pref, app)) + val freshenedFormal = capToFresh(formal, Origin.Formal(pref, app)) val argType = recheck(arg, freshenedFormal) .showing(i"recheck arg $arg vs $freshenedFormal = $result", capt) if formal.hasAnnotation(defn.UseAnnot) || formal.hasAnnotation(defn.ConsumeAnnot) then @@ -776,9 +776,9 @@ class CheckCaptures extends Recheck, SymTransformer: */ protected override def recheckApplication(tree: Apply, qualType: Type, funType: MethodType, argTypes: List[Type])(using Context): Type = - val appType = root.resultToFresh( + val appType = resultToFresh( super.recheckApplication(tree, qualType, funType, argTypes), - root.Origin.ResultInstance(funType, tree.symbol)) + Origin.ResultInstance(funType, tree.symbol)) val qualCaptures = qualType.captureSet val argCaptures = for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield @@ -831,16 +831,16 @@ class CheckCaptures extends Recheck, SymTransformer: * * Second half: union of initial capture set and all capture sets of arguments * to tracked parameters. The initial capture set `initCs` is augmented with - * - root.Fresh(...) if `core` extends Mutable - * - root.Fresh(...).rd if `core` extends Capability + * - FreshCap(...) if `core` extends Mutable + * - FreshCap(...).rd if `core` extends Capability */ def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = var refined: Type = core var allCaptures: CaptureSet = if core.derivesFromMutable then - initCs ++ FreshCap(root.Origin.NewMutable(core)).singletonCaptureSet + initCs ++ FreshCap(Origin.NewMutable(core)).singletonCaptureSet else if core.derivesFromCapability then - initCs ++ FreshCap(root.Origin.NewCapability(core)).readOnly.singletonCaptureSet + initCs ++ FreshCap(Origin.NewCapability(core)).readOnly.singletonCaptureSet else initCs for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol @@ -882,7 +882,7 @@ class CheckCaptures extends Recheck, SymTransformer: def methDescr = if meth.exists then i"$meth's type " else "" disallowCapInTypeArgs(tree.fun, meth, tree.args) val funType = super.recheckTypeApply(tree, pt) - val res = root.resultToFresh(funType, root.Origin.ResultInstance(funType, meth)) + val res = resultToFresh(funType, Origin.ResultInstance(funType, meth)) includeCallCaptures(tree.symbol, res, tree) checkContains(tree) res @@ -929,7 +929,7 @@ class CheckCaptures extends Recheck, SymTransformer: assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}") for (argType, param) <- argTypes.lazyZip(params) do val paramTpt = param.asInstanceOf[ValDef].tpt - val paramType = root.freshToCap(paramTpt.nuType) + val paramType = freshToCap(paramTpt.nuType) checkConformsExpr(argType, paramType, param) .showing(i"compared expected closure formal $argType against $param with ${paramTpt.nuType}", capt) if ccConfig.preTypeClosureResults && !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then @@ -948,8 +948,8 @@ class CheckCaptures extends Recheck, SymTransformer: rinfo.instantiate(paramTypes) case _ => resType - val respt = root.resultToFresh(respt0, root.Origin.LambdaExpected(respt0)) - val res = root.resultToFresh(mdef.tpt.nuType, root.Origin.LambdaActual(mdef.tpt.nuType)) + val respt = resultToFresh(respt0, Origin.LambdaExpected(respt0)) + val res = resultToFresh(mdef.tpt.nuType, Origin.LambdaActual(mdef.tpt.nuType)) // We need to open existentials here in order not to get vars mixed up in them // We do the proper check with existentials when we are finished with the closure block. capt.println(i"pre-check closure $expr of type $res against $respt") @@ -1652,7 +1652,7 @@ class CheckCaptures extends Recheck, SymTransformer: otherTp.derivedTypeBounds( otherTp.lo, hi.derivedCapturingType(parent, - CaptureSet.fresh(root.Origin.OverriddenType(member)))))) + CaptureSet.fresh(Origin.OverriddenType(member)))))) case _ => None case _ => None case _ => None diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 443252e396e0..c9b4546c8c37 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -339,7 +339,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: try val tp1 = mapInferred(refine = true)(tp) - val tp2 = root.toResultInResults(NoSymbol, _ => assert(false))(tp1) + val tp2 = toResultInResults(NoSymbol, _ => assert(false))(tp1) if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 catch case ex: AssertionError => @@ -474,7 +474,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transform(tp: Type): Type = val tp1 = toCapturing(tp) - val tp2 = root.toResultInResults(sym, fail, toCapturing.keepFunAliases)(tp1) + val tp2 = toResultInResults(sym, fail, toCapturing.keepFunAliases)(tp1) val snd = if toCapturing.keepFunAliases then "" else " 2nd time" if tp2 ne tp then capt.println(i"expanded explicit$snd in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 @@ -489,7 +489,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if sym.isType then stripImpliedCaptureSet(tp2) else tp2 if freshen then - root.capToFresh(tp3, root.Origin.InDecl(sym)).tap(addOwnerAsHidden(_, sym)) + capToFresh(tp3, Origin.InDecl(sym)).tap(addOwnerAsHidden(_, sym)) else tp3 end transformExplicitType @@ -589,7 +589,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for case arg: TypeTree <- args do if defn.isTypeTestOrCast(fn.symbol) then arg.setNuType( - root.capToFresh(arg.tpe, root.Origin.TypeArg(arg.tpe))) + capToFresh(arg.tpe, Origin.TypeArg(arg.tpe))) else transformTT(arg, NoSymbol, boxed = true) // type arguments in type applications are boxed @@ -669,7 +669,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case mt: MethodType => try mt.derivedLambdaType( - paramInfos = mt.paramInfos.map(root.freshToCap), + paramInfos = mt.paramInfos.map(freshToCap), resType = paramsToCap(mt.resType)) catch case ex: AssertionError => println(i"error while mapping params ${mt.paramInfos} of $sym") @@ -684,7 +684,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if signatureChanges then val paramSymss = sym.paramSymss def newInfo(using Context) = // will be run in this or next phase - root.toResultInResults(sym, report.error(_, tree.srcPos)): + toResultInResults(sym, report.error(_, tree.srcPos)): if sym.is(Method) then paramsToCap(methodType(paramSymss, localReturnType)) else tree.tpt.nuType diff --git a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala index c66c7972eaf5..27ddd0c923be 100644 --- a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala +++ b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala @@ -50,7 +50,7 @@ object ccConfig: def useSepChecks(using Context): Boolean = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) - /** If true, do level checking for root.Fresh instances */ + /** If true, do level checking for FreshCap instances */ def useFreshLevels(using Context): Boolean = Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala deleted file mode 100644 index 69bdfe26a698..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ /dev/null @@ -1,311 +0,0 @@ -package dotty.tools -package dotc -package cc - -import core.* -import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* -import StdNames.nme -import ast.tpd.* -import Decorators.* -import typer.ErrorReporting.errorType -import Names.TermName -import NameKinds.ExistentialBinderName -import NameOps.isImpureFunction -import reporting.Message -import util.{SimpleIdentitySet, EqHashMap} -import ast.tpd -import annotation.constructorOnly -import Capabilities.* - -/** A module defining three kinds of root capabilities - * - `cap` of kind `Global`: This is the global root capability. Among others it is - * used in the types of formal parameters, in type bounds, and in self types. - * `cap` does not subsume other capabilities, except in arguments of - * `withCapAsRoot` calls. - * - Instances of Fresh(hidden), of kind Fresh. These do subsume other capabilties in scope. - * They track with hidden sets which other capabilities were subsumed. - * Hidden sets are inspected by separation checking. - * - Instances of Result(binder), of kind Result. These are existentials associated with - * the result types of dependent methods. They don't subsume other capabilties. - * - * Representation: - * - * - `cap` is just the TermRef `scala.caps.cap` defined in the `caps` module - * - `Fresh` and `Result` instances are annotated types of `scala.caps.cap` - * with a special `root.Annot` annotation. The symbol of the annotation is - * `annotation.internal.rootCapability`. The annotation carries a kind, which provides - * a hidden set for Fresh instances and a binder method type for Result instances. - * - * Setup: - * - * In the setup phase, `cap` instances in the result of a dependent function type - * or method type such as `(x: T): C^{cap}` are converted to `Result(binder)` instances, - * where `binder` refers to the method type. Most other cap instances are mapped to - * Fresh instances instead. For example the `cap` in the result of `T => C^{cap}` - * is mapped to a Fresh instance. - * - * If one needs to use a dependent function type yet one still want to map `cap` to - * a fresh instance instead an existential root, one can achieve that by the use - * of a type alias. For instance, the following type creates an existential for `^`: - * - * (x: A) => (C^{x}, D^) - * - * By contrast, this variant creates a fresh instance instead: - * - * type F[X] = (x: A) => (C^{x}, X) - * F[D^] - * - * The trick is that the argument D^ is mapped to D^{fresh} before the `F` alias - * is expanded. - */ -object root: - - enum Origin: - case InDecl(sym: Symbol) - case TypeArg(tp: Type) - case UnsafeAssumePure - case Formal(pref: ParamRef, app: tpd.Apply) - case ResultInstance(methType: Type, meth: Symbol) - case UnapplyInstance(info: MethodType) - case NewMutable(tp: Type) - case NewCapability(tp: Type) - case LambdaExpected(respt: Type) - case LambdaActual(restp: Type) - case OverriddenType(member: Symbol) - case DeepCS(ref: TypeRef) - case Unknown - - def explanation(using Context): String = this match - case InDecl(sym: Symbol) => - if sym.is(Method) then i" in the result type of $sym" - else if sym.exists then i" in the type of $sym" - else "" - case TypeArg(tp: Type) => - i" of type argument $tp" - case UnsafeAssumePure => - " when instantiating argument of unsafeAssumePure" - case Formal(pref, app) => - val meth = app.symbol - if meth.exists - then i" when checking argument to parameter ${pref.paramName} of $meth" - else "" - case ResultInstance(mt, meth) => - val methDescr = if meth.exists then i"$meth's type " else "" - i" when instantiating $methDescr$mt" - case UnapplyInstance(info) => - i" when instantiating argument of unapply with type $info" - case NewMutable(tp) => - i" when constructing mutable $tp" - case NewCapability(tp) => - i" when constructing Capability instance $tp" - case LambdaExpected(respt) => - i" when instantiating expected result type $respt of lambda" - case LambdaActual(restp: Type) => - i" when instantiating result type $restp of lambda" - case OverriddenType(member: Symbol) => - i" when instantiating upper bound of member overridden by $member" - case DeepCS(ref: TypeRef) => - i" when computing deep capture set of $ref" - case Unknown => - "" - end Origin - - /** Map each occurrence of cap to a different Fresh instance - * Exception: CapSet^ stays as it is. - */ - class CapToFresh(origin: Origin)(using Context) extends BiTypeMap, FollowAliasesMap: - thisMap => - - override def apply(t: Type) = - if variance <= 0 then t - else t match - case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => - t - case t @ CapturingType(_, _) => - mapOver(t) - case t @ AnnotatedType(parent, ann) => - val parent1 = this(parent) - if ann.symbol.isRetains && ann.tree.toCaptureSet.containsCap then - this(CapturingType(parent1, ann.tree.toCaptureSet)) - else - t.derivedAnnotatedType(parent1, ann) - case _ => - mapFollowingAliases(t) - - override def mapCapability(c: Capability, deep: Boolean): Capability = c match - case GlobalCap => FreshCap(origin) - case _ => super.mapCapability(c, deep) - - override def fuse(next: BiTypeMap)(using Context) = next match - case next: Inverse => assert(false); Some(IdentityTypeMap) - case _ => None - - override def toString = "CapToFresh" - - class Inverse extends BiTypeMap, FollowAliasesMap: - def apply(t: Type): Type = t match - case t @ CapturingType(_, refs) => mapOver(t) - case _ => mapFollowingAliases(t) - - override def mapCapability(c: Capability, deep: Boolean): Capability = c match - case _: FreshCap => GlobalCap - case _ => super.mapCapability(c, deep) - - def inverse = thisMap - override def toString = thisMap.toString + ".inverse" - - lazy val inverse = Inverse() - - end CapToFresh - - /** Maps cap to fresh. CapToFresh is a BiTypeMap since we don't want to - * freeze a set when it is mapped. On the other hand, we do not want Fresh - * values to flow back to cap since that would fail disallowRootCapability - * tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent - * the map being installed for future use. - */ - def capToFresh(tp: Type, origin: Origin)(using Context): Type = - if ccConfig.useSepChecks then - ccState.withoutMappedFutureElems: - CapToFresh(origin)(tp) - else tp - - /** Maps fresh to cap */ - def freshToCap(tp: Type)(using Context): Type = - if ccConfig.useSepChecks then CapToFresh(Origin.Unknown).inverse(tp) else tp - - /** Map top-level free existential variables one-to-one to Fresh instances */ - def resultToFresh(tp: Type, origin: Origin)(using Context): Type = - val subst = new TypeMap: - val seen = EqHashMap[ResultCap, FreshCap | GlobalCap.type]() - var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty - - def apply(t: Type): Type = t match - case t: MethodType => - // skip parameters - val saved = localBinders - if t.marksExistentialScope then localBinders = localBinders + t - try t.derivedLambdaType(resType = this(t.resType)) - finally localBinders = saved - case t: PolyType => - // skip parameters - t.derivedLambdaType(resType = this(t.resType)) - case _ => - mapOver(t) - - 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 - case _ => super.mapCapability(c, deep) - end subst - - subst(tp) - end resultToFresh - - /** Replace all occurrences of `cap` (or fresh) in parts of this type by an existentially bound - * variable bound by `mt`. - * Stop at function or method types since these have been mapped before. - */ - def toResult(tp: Type, mt: MethodicType, fail: Message => Unit)(using Context): Type = - - abstract class CapMap extends BiTypeMap: - override def mapOver(t: Type): Type = t match - case t @ FunctionOrMethod(args, res) if variance > 0 && !t.isAliasFun => - t // `t` should be mapped in this case by a different call to `mapCap`. - case t: (LazyRef | TypeVar) => - mapConserveSuper(t) - case _ => - super.mapOver(t) - - object toVar extends CapMap: - private val seen = EqHashMap[RootCapability, ResultCap]() - - def apply(t: Type) = t match - case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => - if variance > 0 then - super.mapOver: - defn.FunctionNOf(args, res, contextual) - .capturing(ResultCap(mt).singletonCaptureSet) - else mapOver(t) - case _ => - mapOver(t) - - override def mapCapability(c: Capability, deep: Boolean) = c match - case c: (FreshCap | GlobalCap.type) => - if variance > 0 then - seen.getOrElseUpdate(c, ResultCap(mt)) - else - if variance == 0 then - fail(em"""$tp captures the root capability `cap` in invariant position. - |This capability cannot be converted to an existential in the result type of a function.""") - // we accept variance < 0, and leave the cap as it is - c - case _ => - super.mapCapability(c, deep) - - //.showing(i"mapcap $t = $result") - override def toString = "toVar" - - object inverse extends BiTypeMap: - def apply(t: Type) = mapOver(t) - - override def mapCapability(c: Capability, deep: Boolean) = c match - case c @ ResultCap(`mt`) => - // do a reverse getOrElseUpdate on `seen` to produce the - // `Fresh` assosicated with `t` - val it = seen.iterator - var ref: RootCapability | Null = null - while it.hasNext && ref == null do - val (k, v) = it.next - if v eq c then ref = k - if ref == null then - ref = FreshCap(Origin.Unknown) - seen(ref) = c - ref - case _ => - super.mapCapability(c, deep) - - def inverse = toVar.this - override def toString = "toVar.inverse" - end inverse - end toVar - - toVar(tp) - end toResult - - /** Map global roots in function results to result roots. Also, - * map roots in the types of parameterless def methods. - */ - def toResultInResults(sym: Symbol, fail: Message => Unit, keepAliases: Boolean = false)(tp: Type)(using Context): Type = - val m = new TypeMap with FollowAliasesMap: - def apply(t: Type): Type = t match - case AnnotatedType(parent @ defn.RefinedFunctionOf(mt), ann) if ann.symbol == defn.InferredDepFunAnnot => - val mt1 = mapOver(mt).asInstanceOf[MethodType] - if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) - else parent - case defn.RefinedFunctionOf(mt) => - val mt1 = apply(mt) - if mt1 ne mt then mt1.toFunctionType(alwaysDependent = true) - else t - case t: MethodType if variance > 0 && t.marksExistentialScope => - val t1 = mapOver(t).asInstanceOf[MethodType] - t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail)) - case CapturingType(parent, refs) => - t.derivedCapturingType(this(parent), refs) - case t: (LazyRef | TypeVar) => - mapConserveSuper(t) - case _ => - try - if keepAliases then mapOver(t) - else mapFollowingAliases(t) - catch case ex: AssertionError => - println(i"error while mapping $t") - throw ex - m(tp) match - case tp1: ExprType if sym.is(Method, butNot = Accessor) => - tp1.derivedExprType(toResult(tp1.resType, tp1, fail)) - case tp1 => tp1 - end toResultInResults - -end root \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 52ce2722a47d..25d0c479b8a8 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -8,7 +8,7 @@ import printing.{RefinedPrinter, MessageLimiter, ErrorMessageLimiter} import printing.Texts.Text import printing.Formatting.hl import config.SourceVersion -import cc.{CaptureSet, root} +import cc.CaptureSet import cc.Capabilities.* import scala.language.unsafeNulls @@ -201,7 +201,7 @@ object Message: case GlobalCap => "the universal root capability" case ref: FreshCap => val descr = ref.origin match - case origin @ root.Origin.InDecl(sym) if sym.exists => + case origin @ Origin.InDecl(sym) if sym.exists => origin.explanation case origin => i" created in ${ownerStr(ref.hiddenSet.owner)}${origin.explanation}"