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/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/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 3c6620678b09..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 + 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 a1cfe7addf60..f4e717a9aa5e 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,22 +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(ref) match - case result: CaptureRef if result.isTrackableRef => result + /** 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 @@ -6259,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