From 254e71c36db4b47263a1979fe0bf78da816c5da8 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 12 May 2025 11:46:59 +0200 Subject: [PATCH 1/5] 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 f1e17efffa42b7dbd67c170876559e81601a94bc Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 13 May 2025 18:03:58 +0200 Subject: [PATCH 2/5] 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 16ed66110bd3e0ee287f6731336c74ee254e30bc Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 18 May 2025 09:03:12 +0200 Subject: [PATCH 3/5] 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 fc95824aa3453a538d4b16ceb5f26a36bf949f08 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 14 May 2025 19:13:59 +0200 Subject: [PATCH 4/5] 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 d73ee393fbb80e58ae056f15b867387a4ac0f313 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 10 May 2025 15:36:36 +0200 Subject: [PATCH 5/5] 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