diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index 2be492ed6189..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)) @@ -63,10 +64,12 @@ 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) - then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed) + else if elems1.forall: + case elem1: Capability => elem1.isWellformed + case _ => false + 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 f53650f425b2..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,16 +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 AnnotatedType(parent, annot) => - defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef case _ => false @@ -126,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 @@ -142,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(_) => - tp.singletonCaptureSet - case ReadOnlyCapability(ref) => - 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) @@ -191,33 +185,6 @@ 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. - */ - 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 => 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 pathOwner(using Context): Symbol = pathRoot match - case tp1: NamedType => tp1.symbol.owner - case tp1: ThisType => tp1.cls - 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 => prefix.isParamPath - 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. @@ -242,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) @@ -322,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 `=>`? */ @@ -351,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 => @@ -378,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`? */ @@ -499,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))) @@ -632,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 = @@ -680,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 21e05e4663b3..1ae2feda972e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -17,362 +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.prefix match - case prefix: CaptureRef => prefix.ccOwner - case _ => ref.symbol - 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) 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 - (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: 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) => - /* 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: CaptureRef, _) => - lo.subsumes(y) + case TypeBounds(_, hi @ CapturingType(parent, refs)) => + refs.elems.forall(this.subsumes) + case TypeBounds(_, hi: Capability) => + this.subsumes(hi) case _ => - x.captureSetOfInfo.elems.exists(_.subsumes(y)) - case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) => - refs.elems.exists(_.subsumes(y)) + y.captureSetOfInfo.elems.forall(this.subsumes) case _ => false - 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.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 _ => - 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 - - /** `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 _) + || 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 + 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 882f557fec24..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) @@ -313,13 +314,13 @@ 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 = 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) @@ -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` */ @@ -487,7 +486,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 +586,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") @@ -599,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 @@ -622,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 @@ -650,8 +648,8 @@ object CaptureSet: |elem binder = ${elem.binder}""") false } - case QualifiedCapability(elem1) => - levelOK(elem1) + case elem: DerivedCapability => + levelOK(elem.underlying) case _ => true @@ -690,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.""" @@ -703,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 @@ -817,14 +812,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 => @@ -948,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 = @@ -996,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() @@ -1030,16 +1025,14 @@ object CaptureSet: * - if the variance is contravariant, return {} * - 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 + final def extrapolateCaptureRef(r: CaptureRef, tm: TypeMap, variance: Int)(using Context): CaptureSet = + tm.mapCapability(r) match + 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) + 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 = @@ -1070,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 @@ -1136,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) @@ -1192,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 = @@ -1287,11 +1277,7 @@ 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) = t match - case t: CaptureRef if t.isTrackableRef => mapRef(t) - case _ => mapOver(t) + def apply(t: Type) = mapOver(t) override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) @@ -1300,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 @@ -1312,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: @@ -1343,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. @@ -1374,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 d15fa06e64fc..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) @@ -441,7 +442,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 @@ -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 88bb883df67e..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: @@ -209,6 +210,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. */ @@ -363,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)) @@ -426,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) = @@ -504,12 +508,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 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) if ref.isTrackableRef then add.traverse(tp) @@ -660,9 +667,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 @@ -837,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) @@ -905,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) @@ -951,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) @@ -961,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 36dd1c5661e3..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,111 +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 - - /** 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 - - /** 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. @@ -222,8 +119,6 @@ object root: override def apply(t: Type) = if variance <= 0 then t else t match - case t: CaptureRef if t.isCap => - Fresh(origin) case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => t case t @ CapturingType(_, _) => @@ -237,6 +132,10 @@ object root: case _ => mapFollowingAliases(t) + override def mapCapability(c: CaptureRef, deep: Boolean): CaptureRef = c match + case GlobalCap => FreshCap(origin) + case _ => super.mapCapability(c, deep) + override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse => assert(false); Some(IdentityTypeMap) case _ => None @@ -245,13 +144,12 @@ object root: class Inverse extends BiTypeMap, FollowAliasesMap: def apply(t: Type): Type = t match - case t @ Fresh(_) => cap 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 _: FreshCap => GlobalCap + case _ => super.mapCapability(c, deep) def inverse = thisMap override def toString = thisMap.toString + ".inverse" @@ -279,13 +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 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 t: MethodType => // skip parameters val saved = localBinders @@ -298,6 +193,13 @@ object root: case _ => mapOver(t) + override def mapCapability(c: CaptureRef, deep: Boolean) = c match + case c @ ResultCap(binder) => + if localBinders.contains(binder) then c // keep bound references + else seen.getOrElseUpdate(c, FreshCap(origin)) // map free references to FreshCap + case _ => super.mapCapability(c, deep) + end subst + subst(tp) end resultToFresh @@ -317,46 +219,56 @@ 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 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 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: (FreshCap | GlobalCap.type) => + if variance > 0 then + seen.getOrElseUpdate(c, ResultCap(mt)) + else + if variance == 0 then + fail(em"""$tp captures the root capability `cap` in invariant position. + |This capability cannot be converted to an existential in the result type of a function.""") + // we accept variance < 0, and leave the cap as it is + c + case _ => + super.mapCapability(c, deep) + //.showing(i"mapcap $t = $result") override def toString = "toVar" object inverse extends BiTypeMap: - def apply(t: Type) = t match - case t @ Result(`mt`) => + def apply(t: Type) = mapOver(t) + + override def mapCapability(c: CaptureRef, deep: Boolean) = c match + case c @ ResultCap(`mt`) => // do a reverse getOrElseUpdate on `seen` to produce the // `Fresh` assosicated with `t` val it = seen.iterator - var ref: CaptureRef | Null = null + var ref: RootCapability | 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 + ref = FreshCap(Origin.Unknown) + seen(ref) = c ref - case _ => mapOver(t) + case _ => + super.mapCapability(c, deep) + def inverse = toVar.this override def toString = "toVar.inverse" + end inverse end toVar toVar(tp) 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 6cd238bb0e19..425b6193f3cd 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.Capabilities.{Capability, ResultCap} /** 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: 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) + 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,22 @@ object Substituters: case _ => mapOver(tp) + 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.derivedResult(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 c2c508dc27ea..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 @@ -3993,7 +3994,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 { @@ -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) @@ -4113,16 +4114,12 @@ 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))) 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 @@ -4133,6 +4130,12 @@ object Types extends TypeUtils { parent1 case _ => mapOver(tp) } + override def mapCapability(c: CaptureRef, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + case Reach(c1) => + apply(c1) match + case tp1a: ObjectCapability if tp1a.isTrackableRef => tp1a.reach + case _ => GlobalCap + case _ => super.mapCapability(c, deep) } dropDependencies(resultType) else resultType @@ -4281,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) @@ -4787,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) @@ -4822,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) @@ -4834,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) @@ -5791,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 @@ -6159,22 +6162,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 +6251,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: CoreCapability => + if tp.isTrackableRef then tp + else toTrackableRef(tp.underlying) + case tp: TypeAlias => + toTrackableRef(tp.alias) + case _ => + null + + def mapCapability(c: Capability, deep: Boolean = false): CaptureRef | (CaptureSet, Boolean) = c match + case c: RootCapability => c + case Reach(c1) => + mapCapability(c1, deep = true) + case ReadOnly(c1) => + assert(!deep) + mapCapability(c1) match + case c2: CaptureRef => c2.readOnly + case (cs: CaptureSet, exact) => (cs.readOnly, exact) + case Maybe(c1) => + assert(!deep) + mapCapability(c1) match + case c2: CaptureRef => c2.maybe + case (cs: CaptureSet, exact) => (cs.maybe, exact) + case ref: CoreCapability => + val tp1 = apply(ref) + 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. @@ -6832,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/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/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("{", ", ", "}") } 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/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 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 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