diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index d71a6329e8b0..ebe166e29f4b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -2268,10 +2268,6 @@ object desugar { Annotated( AppliedTypeTree(ref(defn.SeqType), t), New(ref(defn.RepeatedAnnot.typeRef), Nil :: Nil)) - else if op.name == nme.CC_REACH then - Annotated(t, New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil)) - else if op.name == nme.CC_READONLY then - Annotated(t, New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil)) else assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode) Select(t, op.name) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 4198c78e3288..96c8c4c4f845 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -550,6 +550,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { annot.putAttachment(RetainsAnnot, ()) Annotated(parent, annot) + def makeReachAnnot()(using Context): Tree = + New(ref(defn.ReachCapabilityAnnot.typeRef), Nil :: Nil) + + def makeReadOnlyAnnot()(using Context): Tree = + New(ref(defn.ReadOnlyCapabilityAnnot.typeRef), Nil :: Nil) + + def makeOnlyAnnot(qid: Tree)(using Context) = + New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil) + def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 5c1de33aea0e..2562ac4ac5a4 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -39,8 +39,9 @@ import annotation.internal.sharable * | +-- SetCapability -----+-- TypeRef * | +-- TypeParamRef * | - * +-- DerivedCapability -+-- ReadOnly - * +-- Reach + * +-- DerivedCapability -+-- Reach + * +-- Only + * +-- ReadOnly * +-- Maybe * * All CoreCapabilities are Types, or, more specifically instances of TypeProxy. @@ -96,9 +97,18 @@ object Capabilities: * but they can wrap reach capabilities. We have * (x?).readOnly = (x.rd)? */ - case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach) - extends DerivedCapability: - assert(!underlying.isInstanceOf[Maybe]) + case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach | Restricted) + extends DerivedCapability + + /** The restricted capability `x.only[C]`. We have {x.only[C]} <: {x}. + * + * Restricted capabilities cannot wrap maybe capabilities or read-only capabilities + * but they can wrap reach capabilities. We have + * (x?).restrict[T] = (x.restrict[T])? + * (x.rd).restrict[T] = (x.restrict[T]).rd + */ + case class Restricted(underlying: ObjectCapability | RootCapability | Reach, cls: ClassSymbol) + extends DerivedCapability /** If `x` is a capability, its reach capability `x*`. `x*` stands for all * capabilities reachable through `x`. @@ -109,11 +119,11 @@ object Capabilities: * * Reach capabilities cannot wrap read-only capabilities or maybe capabilities. * We have - * (x.rd).reach = x*.rd - * (x.rd)? = (x*)? + * (x?).reach = (x.reach)? + * (x.rd).reach = (x.reach).rd + * (x.only[T]).reach = (x*).only[T] */ - case class Reach(underlying: ObjectCapability) extends DerivedCapability: - assert(!underlying.isInstanceOf[Maybe | ReadOnly]) + case class Reach(underlying: ObjectCapability) extends DerivedCapability /** The global root capability referenced as `caps.cap` * `cap` does not subsume other capabilities, except in arguments of @@ -124,6 +134,7 @@ object Capabilities: def descr(using Context) = "the universal root capability" override val maybe = Maybe(this) override val readOnly = ReadOnly(this) + override def restrict(cls: ClassSymbol)(using Context) = Restricted(this, cls) override def reach = unsupported("cap.reach") override def singletonCaptureSet(using Context) = CaptureSet.universal override def captureSetOfInfo(using Context) = singletonCaptureSet @@ -242,19 +253,29 @@ object Capabilities: /** 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. + * `*` first, `.only` next, `.rd` next, `?` last. */ trait Capability extends Showable: private var myCaptureSet: CaptureSet | Null = uninitialized - private var myCaptureSetValid: Validity = invalid + private var captureSetValid: Validity = invalid private var mySingletonCaptureSet: CaptureSet.Const | Null = null private var myDerived: List[DerivedCapability] = Nil + private var myClassifiers: Classifiers = UnknownClassifier + private var classifiersValid: Validity = invalid 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) + val exists = ref match + case Restricted(_, cls) => + newRef match + case Restricted(_, newCls) => cls == newCls + case _ => false + case _ => + ref.getClass == newRef.getClass + if exists then ref.asInstanceOf[C] + else recur(refs1) case Nil => myDerived = newRef :: myDerived newRef @@ -267,11 +288,21 @@ object Capabilities: 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 self: (ObjectCapability | RootCapability | Reach | Restricted) => cached(ReadOnly(self)) + + def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match + case Maybe(ref1) => Maybe(ref1.restrict(cls)) + case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted]) + case self @ Restricted(ref1, prevCls) => + val combinedCls = leastClassifier(prevCls, cls) + if combinedCls == prevCls then self + else cached(Restricted(ref1, combinedCls)) + case self: (ObjectCapability | RootCapability | Reach) => cached(Restricted(self, cls)) + + def reach: Reach | Restricted | ReadOnly | Maybe = this match case Maybe(ref1) => Maybe(ref1.reach) - case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach]) + case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted]) + case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls) case self: Reach => self case self: ObjectCapability => cached(Reach(self)) @@ -285,6 +316,12 @@ object Capabilities: case tp: SetCapability => tp.captureSetOfInfo.isReadOnly case _ => this ne stripReadOnly + final def restriction(using Context): Symbol = this match + case Restricted(_, cls) => cls + case ReadOnly(ref1) => ref1.restriction + case Maybe(ref1) => ref1.restriction + case _ => NoSymbol + /** Is this a reach reference of the form `x*` or a readOnly or maybe variant * of a reach reference? */ @@ -299,9 +336,20 @@ object Capabilities: case Maybe(ref1) => ref1.stripReadOnly.maybe case _ => this + /** Drop restrictions with clss `cls` or a superclass of `cls` */ + final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match + case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1 + case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly + case Maybe(ref1) => ref1.stripRestricted(cls).maybe + case _ => this + + final def stripRestricted(using Context): Capability = + stripRestricted(defn.NothingClass) + final def stripReach(using Context): Capability = this match case Reach(ref1) => ref1 case ReadOnly(ref1) => ref1.stripReach.readOnly + case Restricted(ref1, cls) => ref1.stripReach.restrict(cls) case Maybe(ref1) => ref1.stripReach.maybe case _ => this @@ -425,7 +473,7 @@ object Capabilities: 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) + def derivesFromSharable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Sharable) /** The capture set consisting of exactly this reference */ def singletonCaptureSet(using Context): CaptureSet.Const = @@ -435,7 +483,7 @@ object Capabilities: /** The capture set of the type underlying this reference */ def captureSetOfInfo(using Context): CaptureSet = - if myCaptureSetValid == currentId then myCaptureSet.nn + if captureSetValid == currentId then myCaptureSet.nn else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty else myCaptureSet = CaptureSet.Pending @@ -447,11 +495,76 @@ object Capabilities: myCaptureSet = null else myCaptureSet = computed - myCaptureSetValid = currentId + captureSetValid = currentId computed + /** The transitive classifiers of this capability. */ + def transClassifiers(using Context): Classifiers = + def toClassifiers(cls: ClassSymbol): Classifiers = + if cls == defn.AnyClass then Unclassified + else ClassifiedAs(cls :: Nil) + if classifiersValid != currentId then + myClassifiers = this match + case self: FreshCap => + toClassifiers(self.hiddenSet.classifier) + case self: RootCapability => + Unclassified + case Restricted(_, cls) => + assert(cls != defn.AnyClass) + if cls == defn.NothingClass then ClassifiedAs(Nil) + else ClassifiedAs(cls :: Nil) + case ReadOnly(ref1) => + ref1.transClassifiers + case Maybe(ref1) => + ref1.transClassifiers + case Reach(_) => + captureSetOfInfo.transClassifiers + case self: CoreCapability => + joinClassifiers(toClassifiers(self.classifier), captureSetOfInfo.transClassifiers) + if myClassifiers != UnknownClassifier then + classifiersValid == currentId + myClassifiers + end transClassifiers + + def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + cls == defn.AnyClass + || this.match + case self: FreshCap => + self.hiddenSet.tryClassifyAs(cls) + case self: RootCapability => + true + case Restricted(_, cls1) => + assert(cls != defn.AnyClass) + cls1.isSubClass(cls) + case ReadOnly(ref1) => + ref1.tryClassifyAs(cls) + case Maybe(ref1) => + ref1.tryClassifyAs(cls) + case Reach(_) => + captureSetOfInfo.tryClassifyAs(cls) + case self: CoreCapability => + self.classifier.isSubClass(cls) + && captureSetOfInfo.tryClassifyAs(cls) + + def isKnownClassifiedAs(cls: ClassSymbol)(using Context): Boolean = + transClassifiers match + case ClassifiedAs(cs) => cs.forall(_.isSubClass(cls)) + case _ => false + + def isKnownEmpty(using Context): Boolean = this match + case Restricted(ref1, cls) => + val isEmpty = ref1.transClassifiers match + case ClassifiedAs(cs) => + cs.forall(c => leastClassifier(c, cls) == defn.NothingClass) + case _ => false + isEmpty || ref1.isKnownEmpty + case ReadOnly(ref1) => ref1.isKnownEmpty + case Maybe(ref1) => ref1.isKnownEmpty + case _ => false + def invalidateCaches() = - myCaptureSetValid = invalid + captureSetValid = invalid + classifiersValid = invalid /** x subsumes x * x =:= y ==> x subsumes y @@ -505,6 +618,7 @@ object Capabilities: || viaInfo(y.info)(subsumingRefs(this, _)) case Maybe(y1) => this.stripMaybe.subsumes(y1) case ReadOnly(y1) => this.stripReadOnly.subsumes(y1) + case Restricted(y1, cls) => this.stripRestricted(cls).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`, @@ -519,6 +633,7 @@ object Capabilities: case _ => false || this.match case Reach(x1) => x1.subsumes(y.stripReach) + case Restricted(x1, cls) => y.isKnownClassifiedAs(cls) && x1.subsumes(y) 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) => @@ -559,12 +674,15 @@ object Capabilities: vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y))) || levelOK + && ( y.tryClassifyAs(x.hiddenSet.classifier) + || { capt.println(i"$y cannot be classified as $x"); false } + ) && canAddHidden && vs.addHidden(x.hiddenSet, y) case x: ResultCap => val result = y match case y: ResultCap => vs.unify(x, y) - case _ => y.derivesFromSharedCapability + case _ => y.derivesFromSharable if !result then TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y)) result @@ -574,11 +692,12 @@ object Capabilities: case _: ResultCap => false case _: FreshCap if CCState.collapseFresh => true case _ => - y.derivesFromSharedCapability + y.derivesFromSharable || canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot case _ => y match case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) + case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden) case _ => false /** `x covers y` if we should retain `y` when computing the overlap of @@ -623,6 +742,7 @@ object Capabilities: val c1 = c.underlying.toType c match case _: ReadOnly => ReadOnlyCapability(c1) + case Restricted(_, cls) => OnlyCapability(c1, cls) case _: Reach => ReachCapability(c1) case _: Maybe => MaybeCapability(c1) case _ => c1 @@ -630,6 +750,39 @@ object Capabilities: def toText(printer: Printer): Text = printer.toTextCapability(this) end Capability + /** Result type of `transClassifiers`. Interprete as follows: + * UnknownClassifier: No list could be computed since some capture sets + * are still unsolved variables + * Unclassified : No set exists since some parts of tcs are not classified + * ClassifiedAs(clss: All parts of tcss are classified with classes in clss + */ + enum Classifiers: + case UnknownClassifier + case Unclassified + case ClassifiedAs(clss: List[ClassSymbol]) + + export Classifiers.{UnknownClassifier, Unclassified, ClassifiedAs} + + /** The least classifier between `cls1` and `cls2`, which are either + * AnyClass, NothingClass, or a class directly extending caps.Classifier. + * @return if oen of cls1, cls2 is a subclass of the other, the subclass + * otherwise NothingClass (which is a subclass of all classes) + */ + def leastClassifier(cls1: ClassSymbol, cls2: ClassSymbol)(using Context): ClassSymbol = + if cls1.isSubClass(cls2) then cls1 + else if cls2.isSubClass(cls1) then cls2 + else defn.NothingClass + + def joinClassifiers(cs1: Classifiers, cs2: Classifiers)(using Context): Classifiers = + // Drop classes that subclass classes of the other set + def filterSub(cs1: List[ClassSymbol], cs2: List[ClassSymbol]) = + cs1.filter(cls1 => !cs2.exists(cls2 => cls1.isSubClass(cls2))) + (cs1, cs2) match + case (Unclassified, _) | (_, Unclassified) => Unclassified + case (UnknownClassifier, _) | (_, UnknownClassifier) => UnknownClassifier + case (ClassifiedAs(cs1), ClassifiedAs(cs2)) => + ClassifiedAs(filterSub(cs1, cs2) ++ filterSub(cs2, cs1)) + /** The place of - and cause for - creating a fresh capability. Used for * error diagnostics */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 2f5c59c11071..d6a58167cb9c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -80,6 +80,8 @@ extension (tp: Type) tp1.toCapability.reach case ReadOnlyCapability(tp1) => tp1.toCapability.readOnly + case OnlyCapability(tp1, cls) => + tp1.toCapability.restrict(cls) case ref: TermRef if ref.isCapRef => GlobalCap case ref: Capability if ref.isTrackableRef => @@ -288,7 +290,7 @@ extension (tp: Type) def forceBoxStatus(boxed: Boolean)(using Context): Type = tp.widenDealias match case tp @ CapturingType(parent, refs) if tp.isBoxed != boxed => val refs1 = tp match - case ref: Capability if ref.isTracked || ref.isReach || ref.isReadOnly => + case ref: Capability if ref.isTracked || ref.isInstanceOf[DerivedCapability] => ref.singletonCaptureSet case _ => refs CapturingType(parent, refs1, boxed) @@ -374,7 +376,7 @@ extension (tp: Type) 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) + def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Sharable) /** Drop @retains annotations everywhere */ def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling @@ -440,6 +442,30 @@ extension (tp: Type) def dropUseAndConsumeAnnots(using Context): Type = tp.dropAnnot(defn.UseAnnot).dropAnnot(defn.ConsumeAnnot) + /** If `tp` is a function or method, a type of the same kind with the given + * argument and result types. + */ + def derivedFunctionOrMethod(argTypes: List[Type], resType: Type)(using Context): Type = tp match + case tp @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp) => + val args1 = argTypes :+ resType + if args.corresponds(args1)(_ eq _) then tp + else tp.derivedAppliedType(tycon, args1) + case tp @ defn.RefinedFunctionOf(rinfo) => + val rinfo1 = rinfo.derivedFunctionOrMethod(argTypes, resType) + if rinfo1 eq rinfo then tp + else if rinfo1.isInstanceOf[PolyType] then tp.derivedRefinedType(refinedInfo = rinfo1) + else rinfo1.toFunctionType(alwaysDependent = true) + case tp: MethodType => + tp.derivedLambdaType(paramInfos = argTypes, resType = resType) + case tp: PolyType => + assert(argTypes.isEmpty) + tp.derivedLambdaType(resType = resType) + case _ => + tp + + def classifier(using Context): ClassSymbol = + tp.classSymbols.map(_.classifier).foldLeft(defn.AnyClass)(leastClassifier) + extension (tp: MethodType) /** A method marks an existential scope unless it is the prefix of a curried method */ def marksExistentialScope(using Context): Boolean = @@ -471,6 +497,16 @@ extension (cls: ClassSymbol) val selfType = bc.givenSelfType bc.is(CaptureChecked) && selfType.exists && selfType.captureSet.elems == refs.elems + def isClassifiedCapabilityClass(using Context): Boolean = + cls.derivesFrom(defn.Caps_Capability) && cls.parentSyms.contains(defn.Caps_Classifier) + + def classifier(using Context): ClassSymbol = + if cls.derivesFrom(defn.Caps_Capability) then + cls.baseClasses + .filter(_.parentSyms.contains(defn.Caps_Classifier)) + .foldLeft(defn.AnyClass)(leastClassifier) + else defn.AnyClass + extension (sym: Symbol) /** This symbol is one of `retains` or `retainsCap` */ @@ -587,7 +623,6 @@ abstract class AnnotatedCapability(annotCls: Context ?=> ClassSymbol): def unapply(tree: AnnotatedType)(using Context): Option[Type] = tree match case AnnotatedType(parent: Type, ann) if ann.hasSymbol(annotCls) => Some(parent) case _ => None - end AnnotatedCapability /** An extractor for `ref @readOnlyCapability`, which is used to express @@ -605,6 +640,17 @@ object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot) */ object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot) +object OnlyCapability: + def apply(tp: Type, cls: ClassSymbol)(using Context): AnnotatedType = + AnnotatedType(tp, + Annotation(defn.OnlyCapabilityAnnot.typeRef.appliedTo(cls.typeRef), Nil, util.Spans.NoSpan)) + + def unapply(tree: AnnotatedType)(using Context): Option[(Type, ClassSymbol)] = tree match + case AnnotatedType(parent: Type, ann) if ann.hasSymbol(defn.OnlyCapabilityAnnot) => + Some((parent, ann.tree.tpe.argTypes.head.classSymbol.asClass)) + case _ => None +end OnlyCapability + /** 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 @@ -618,28 +664,6 @@ object FunctionOrMethod: case defn.RefinedFunctionOf(rinfo) => unapply(rinfo) case _ => None -/** If `tp` is a function or method, a type of the same kind with the given - * argument and result types. - */ -extension (self: Type) - def derivedFunctionOrMethod(argTypes: List[Type], resType: Type)(using Context): Type = self match - case self @ AppliedType(tycon, args) if defn.isNonRefinedFunction(self) => - val args1 = argTypes :+ resType - if args.corresponds(args1)(_ eq _) then self - else self.derivedAppliedType(tycon, args1) - case self @ defn.RefinedFunctionOf(rinfo) => - val rinfo1 = rinfo.derivedFunctionOrMethod(argTypes, resType) - if rinfo1 eq rinfo then self - else if rinfo1.isInstanceOf[PolyType] then self.derivedRefinedType(refinedInfo = rinfo1) - else rinfo1.toFunctionType(alwaysDependent = true) - case self: MethodType => - self.derivedLambdaType(paramInfos = argTypes, resType = resType) - case self: PolyType => - assert(argTypes.isEmpty) - self.derivedLambdaType(resType = resType) - case _ => - self - /** An extractor for a contains argument */ object ContainsImpl: def unapply(tree: TypeApply)(using Context): Option[(Tree, Tree)] = diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 37ee3f68b9ad..720e1fbcf25d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -210,6 +210,7 @@ sealed abstract class CaptureSet extends Showable: protected def addIfHiddenOrFail(elem: Capability)(using ctx: Context, vs: VarState): Boolean = elems.exists(_.maxSubsumes(elem, canAddHidden = true)) + || elem.isKnownEmpty || failWith(IncludeFailure(this, elem)) /** If this is a variable, add `cs` as a dependent set */ @@ -403,11 +404,27 @@ sealed abstract class CaptureSet extends Showable: def maybe(using Context): CaptureSet = map(MaybeMap()) + def restrict(cls: ClassSymbol)(using Context): CaptureSet = map(RestrictMap(cls)) + def readOnly(using Context): CaptureSet = val res = map(ReadOnlyMap()) if mutability != Ignored then res.mutability = Reader res + def transClassifiers(using Context): Classifiers = + if isConst then + (ClassifiedAs(Nil) /: elems.map(_.transClassifiers))(joinClassifiers) + else UnknownClassifier + + def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + elems.forall(_.tryClassifyAs(cls)) + + def adoptClassifier(cls: ClassSymbol)(using Context): Unit = + for elem <- elems do + elem.stripReadOnly match + case fresh: FreshCap => fresh.hiddenSet.adoptClassifier(cls) + case _ => + /** A bad root `elem` is inadmissible as a member of this set. What is a bad roots depends * on the value of `rootLimit`. * If the limit is null, all capture roots are good. @@ -649,6 +666,25 @@ object CaptureSet: */ private[CaptureSet] var rootLimit: Symbol | Null = null + private var myClassifier: ClassSymbol = defn.AnyClass + def classifier: ClassSymbol = myClassifier + + private def narrowClassifier(cls: ClassSymbol)(using Context): Unit = + val newClassifier = leastClassifier(classifier, cls) + if newClassifier == defn.NothingClass then + println(i"conflicting classifications for $this, was $classifier, now $cls") + myClassifier = newClassifier + + override def adoptClassifier(cls: ClassSymbol)(using Context): Unit = + if !classifier.isSubClass(cls) then // serves as recursion brake + narrowClassifier(cls) + super.adoptClassifier(cls) + + override def tryClassifyAs(cls: ClassSymbol)(using Context): Boolean = + classifier.isSubClass(cls) + || super.tryClassifyAs(cls) + && { narrowClassifier(cls); true } + /** A handler to be invoked when new elems are added to this set */ var newElemAddedHandler: Capability => Context ?=> Unit = _ => () @@ -680,6 +716,8 @@ object CaptureSet: addIfHiddenOrFail(elem) else if !levelOK(elem) then failWith(IncludeFailure(this, elem, levelError = true)) // or `elem` is not visible at the level of the set. + else if !elem.tryClassifyAs(classifier) then + failWith(IncludeFailure(this, elem)) else // id == 108 then assert(false, i"trying to add $elem to $this") assert(elem.isWellformed, elem) @@ -687,7 +725,6 @@ object CaptureSet: includeElem(elem) if isBadRoot(rootLimit, elem) then rootAddedHandler() - newElemAddedHandler(elem) val normElem = if isMaybeSet then elem else elem.stripMaybe // assert(id != 5 || elems.size != 3, this) val res = deps.forall: dep => @@ -1344,9 +1381,10 @@ 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 apply(t: Type) = mapOver(t) + protected def isSameMap(other: BiTypeMap) = other.getClass == getClass + override def fuse(next: BiTypeMap)(using Context) = next match case next: Inverse if next.inverse.getClass == getClass => Some(IdentityTypeMap) case next: NarrowingCapabilityMap if next.getClass == getClass => Some(this) @@ -1358,8 +1396,8 @@ object CaptureSet: def inverse = NarrowingCapabilityMap.this override def toString = NarrowingCapabilityMap.this.toString ++ ".inverse" override def fuse(next: BiTypeMap)(using Context) = next match - case next: NarrowingCapabilityMap if next.inverse.getClass == getClass => Some(IdentityTypeMap) - case next: NarrowingCapabilityMap if next.getClass == getClass => Some(this) + case next: NarrowingCapabilityMap if isSameMap(next.inverse) => Some(IdentityTypeMap) + case next: NarrowingCapabilityMap if isSameMap(next) => Some(this) case _ => None lazy val inverse = Inverse() @@ -1375,6 +1413,13 @@ object CaptureSet: override def mapCapability(c: Capability, deep: Boolean) = c.readOnly override def toString = "ReadOnly" + private class RestrictMap(val cls: ClassSymbol)(using Context) extends NarrowingCapabilityMap: + override def mapCapability(c: Capability, deep: Boolean) = c.restrict(cls) + override def toString = "Restrict" + override def isSameMap(other: BiTypeMap) = other match + case other: RestrictMap => cls == other.cls + case _ => false + /* Not needed: def ofClass(cinfo: ClassInfo, argTypes: List[Type])(using Context): CaptureSet = CaptureSet.empty @@ -1402,6 +1447,9 @@ object CaptureSet: case Reach(c1) => c1.widen.deepCaptureSet(includeTypevars = true) .showing(i"Deep capture set of $c: ${c1.widen} = ${result}", capt) + case Restricted(c1, cls) => + if cls == defn.NothingClass then CaptureSet.empty + else c1.captureSetOfInfo.restrict(cls) // todo: should we simplify using subsumption here? case ReadOnly(c1) => c1.captureSetOfInfo.readOnly case Maybe(c1) => diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 58bee8132b98..1cafe295f529 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -40,6 +40,8 @@ object CapturingType: apply(parent1, refs ++ refs1, boxed) case _ => if parent.derivesFromMutable then refs.setMutable() + val classifier = parent.classifier + refs.adoptClassifier(parent.classifier) AnnotatedType(parent, CaptureAnnotation(refs, boxed)(defn.RetainsAnnot)) /** An extractor for CapturingTypes. Capturing types are recognized if diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index dccbd0a005d7..615b3f41ef44 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -112,6 +112,13 @@ object CheckCaptures: report.error(em"Cannot form a reach capability from `cap`", ann.srcPos) case ReadOnlyCapability(ref) => check(ref) + case OnlyCapability(ref, cls) => + if !cls.isClassifiedCapabilityClass then + report.error( + em"""${ref.showRef}.only[${cls.name}] is not well-formed since $cls is not a classifier class. + |A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`.""", + ann.srcPos) + check(ref) case tpe => report.error(em"$elem: $tpe is not a legal element of a capture set", ann.srcPos) ann.retainedSet.retainedElementsRaw.foreach(check) @@ -1290,7 +1297,7 @@ class CheckCaptures extends Recheck, SymTransformer: case ExistentialSubsumesFailure(ex, other) => def since = if other.isTerminalCapability then "" - else " since that capability is not a SharedCapability" + else " since that capability is not a `Sharable` capability" i"""the existential capture root in ${ex.originalBinder.resType} |cannot subsume the capability $other$since""" case MutAdaptFailure(cs, lo, hi) => diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index 4a391bf2c62f..2662a5481f83 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -256,6 +256,7 @@ object SepCheck: def hiddenByElem(elem: Capability): Refs = elem match case elem: FreshCap => elem.hiddenSet.elems ++ recur(elem.hiddenSet.elems) + case Restricted(elem1, cls) => hiddenByElem(elem1).map(_.restrict(cls)) case ReadOnly(elem1) => hiddenByElem(elem1).map(_.readOnly) case _ => emptyRefs @@ -597,7 +598,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: * - If the reference is to a this type of the enclosing class, the * access must be in a @consume method. * - * References that extend SharedCapability are excluded from checking. + * References that extend caps.Sharable are excluded from checking. * As a side effect, add all checked references with the given position `pos` * to the global `consumed` map. * @@ -611,7 +612,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val badParams = mutable.ListBuffer[Symbol]() def currentOwner = role.dclSym.orElse(ctx.owner) for hiddenRef <- refsToCheck.deductSymRefs(role.dclSym).deduct(explicitRefs(tpe)) do - if !hiddenRef.derivesFromSharedCapability then + if !hiddenRef.derivesFromSharable then hiddenRef.pathRoot match case ref: TermRef => val refSym = ref.symbol @@ -648,7 +649,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: role match case _: TypeRole.Argument | _: TypeRole.Qualifier => for ref <- refsToCheck do - if !ref.derivesFromSharedCapability then + if !ref.derivesFromSharable then consumed.put(ref, pos) case _ => end checkConsumedRefs diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 6143a7131e32..326c9567e3d0 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -375,10 +375,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else fntpe /** 1. Check that parents of capturing types are not pure. - * 2. Check that types extending SharedCapability don't have a `cap` in their capture set. + * 2. Check that types extending caps.Sharable don't have a `cap` in their capture set. * TODO This is not enough. * We need to also track that we cannot get exclusive capabilities in paths - * where some prefix derives from SharedCapability. Also, can we just + * where some prefix derives from Sharable. Also, can we just * exclude `cap`, or do we have to extend this to all exclusive capabilties? * The problem is that we know what is exclusive in general only after capture * checking, not before. @@ -393,7 +393,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // will be ignored anyway. fail(em"$parent is a pure type, it makes no sense to add a capture set to it") else if refs.isUniversal && parent.derivesFromSharedCapability then - fail(em"$tp extends SharedCapability, so it cannot capture `cap`") + fail(em"$tp extends Sharable, so it cannot capture `cap`") case _ => tp @@ -696,6 +696,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: TypeDef => tree.symbol match case cls: ClassSymbol => + checkClassifiedInheritance(cls) ccState.inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo @@ -912,6 +913,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def setupUnit(tree: Tree, checker: CheckerAPI)(using Context): Unit = setupTraverser(checker).traverse(tree)(using ctx.withPhase(thisPhase)) + // ------ Checks to run at Setup ---------------------------------------- + + private def checkClassifiedInheritance(cls: ClassSymbol)(using Context): Unit = + def recur(cs: List[ClassSymbol]): Unit = cs match + case c :: cs1 => + for c1 <- cs1 do + if !c.derivesFrom(c1) && !c1.derivesFrom(c) then + report.error(i"$cls inherits two unrelated classifier traits: $c and $c1", cls.srcPos) + recur(cs1) + case Nil => + recur(cls.baseClasses.filter(_.isClassifiedCapabilityClass).distinct) + // ------ Checks to run after main capture checking -------------------------- /** A list of actions to perform at postCheck */ diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index d662d3c0d412..cf5610dcdbf2 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -8,7 +8,7 @@ import Feature.isPreviewEnabled import util.Property enum SourceVersion: - case `3.0-migration`, `3.0` + case `3.0-migration`, `3.0` case `3.1-migration`, `3.1` case `3.2-migration`, `3.2` case `3.3-migration`, `3.3` @@ -45,7 +45,7 @@ enum SourceVersion: def enablesBetterFors(using Context) = isAtLeast(`3.7`) && isPreviewEnabled object SourceVersion extends Property.Key[SourceVersion]: - + /* The default source version used by the built compiler */ val defaultSourceVersion = `3.7` diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 85edadd40c80..9de714be8c37 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -870,6 +870,13 @@ object Contexts { result.init(ctx) result + def currentComparer(using Context): TypeComparer = + val base = ctx.base + if base.comparersInUse > 0 then + base.comparers(base.comparersInUse - 1) + else + comparer + inline def comparing[T](inline op: TypeComparer => T)(using Context): T = util.Stats.record("comparing") val saved = ctx.base.comparersInUse diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 381caa775dbd..2a1cf222c106 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1011,7 +1011,9 @@ class Definitions { @tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains") @tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl") @tu lazy val Caps_Mutable: ClassSymbol = requiredClass("scala.caps.Mutable") - @tu lazy val Caps_SharedCapability: ClassSymbol = requiredClass("scala.caps.SharedCapability") + @tu lazy val Caps_Sharable: ClassSymbol = requiredClass("scala.caps.Sharable") + @tu lazy val Caps_Control: ClassSymbol = requiredClass("scala.caps.Control") + @tu lazy val Caps_Classifier: ClassSymbol = requiredClass("scala.caps.Classifier") @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") @@ -1089,6 +1091,7 @@ class Definitions { @tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability") @tu lazy val RootCapabilityAnnot = requiredClass("scala.caps.internal.rootCapability") @tu lazy val ReadOnlyCapabilityAnnot = requiredClass("scala.annotation.internal.readOnlyCapability") + @tu lazy val OnlyCapabilityAnnot = requiredClass("scala.annotation.internal.onlyCapability") @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") @tu lazy val RetainsCapAnnot: ClassSymbol = requiredClass("scala.annotation.retainsCap") @@ -2094,7 +2097,7 @@ class Definitions { Caps_Capability, // TODO: Remove when Capability is stabilized RequiresCapabilityAnnot, captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass, UseAnnot, - Caps_Mutable, Caps_SharedCapability, ConsumeAnnot, + Caps_Mutable, Caps_Sharable, Caps_Control, Caps_Classifier, ConsumeAnnot, CapsUnsafeModule, CapsUnsafeModule.moduleClass, CapsInternalModule, CapsInternalModule.moduleClass, RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 9352be725e2c..9271961d02dd 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -120,8 +120,6 @@ object StdNames { val BITMAP_TRANSIENT: N = s"${BITMAP_PREFIX}trans$$" // initialization bitmap for transient lazy vals val BITMAP_CHECKINIT: N = s"${BITMAP_PREFIX}init$$" // initialization bitmap for checkinit values val BITMAP_CHECKINIT_TRANSIENT: N = s"${BITMAP_PREFIX}inittrans$$" // initialization bitmap for transient checkinit values - val CC_REACH: N = "$reach" - val CC_READONLY: N = "$readOnly" val DEFAULT_GETTER: N = str.DEFAULT_GETTER val DEFAULT_GETTER_INIT: N = "$lessinit$greater" val DO_WHILE_PREFIX: N = "doWhile$" @@ -570,6 +568,7 @@ object StdNames { val null_ : N = "null" val ofDim: N = "ofDim" val on: N = "on" + val only: N = "only" val opaque: N = "opaque" val open: N = "open" val ordinal: N = "ordinal" diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index eb03a2b1c05d..870a73a28689 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3536,16 +3536,16 @@ object TypeComparer { comparing(_.subCaptures(refs1, refs2, vs)) def logUndoAction(action: () => Unit)(using Context): Unit = - comparer.logUndoAction(action) + currentComparer.logUndoAction(action) def inNestedLevel(op: => Boolean)(using Context): Boolean = - comparer.inNestedLevel(op) + currentComparer.inNestedLevel(op) def addErrorNote(note: ErrorNote)(using Context): Unit = - comparer.addErrorNote(note) + currentComparer.addErrorNote(note) def updateErrorNotes(f: PartialFunction[ErrorNote, ErrorNote])(using Context): Unit = - comparer.errorNotes = comparer.errorNotes.mapConserve: p => + currentComparer.errorNotes = currentComparer.errorNotes.mapConserve: p => val (level, note) = p if f.isDefinedAt(note) then (level, f(note)) else p @@ -3553,7 +3553,7 @@ object TypeComparer { comparing(_.compareResult(op)) inline def noNotes(inline op: Boolean)(using Context): Boolean = - comparer.isolated(op, x => x) + currentComparer.isolated(op, x => x) } object MatchReducer: diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 82c027744c38..1b0f87f8f6b1 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -267,5 +267,9 @@ class TypeUtils: self.decl(nme.CONSTRUCTOR).altsWith(isApplicable).map(_.symbol) + def showRef(using Context): String = self match + case self: SingletonType => ctx.printer.toTextRef(self).show + case _ => self.show + end TypeUtils diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 61b3b958fca3..d4b068ae41c1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -6278,6 +6278,10 @@ object Types extends TypeUtils { case c: RootCapability => c case Reach(c1) => mapCapability(c1, deep = true) + case Restricted(c1, cls) => + mapCapability(c1) match + case c2: Capability => c2.restrict(cls) + case (cs: CaptureSet, exact) => (cs.restrict(cls), exact) case ReadOnly(c1) => assert(!deep) mapCapability(c1) match diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e4314b27a32c..52903077ec3d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1589,24 +1589,35 @@ object Parsers { case _ => None } - /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking + /** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [CapFilter] [`.` `rd`] -- under captureChecking + * CapFilter ::= `.` `as` `[` QualId `]` */ def captureRef(): Tree = - def derived(ref: Tree, name: TermName) = - in.nextToken() - atSpan(startOffset(ref)) { PostfixOp(ref, Ident(name)) } + def derived(ref: Tree): Tree = + atSpan(startOffset(ref)): + if in.isIdent(nme.raw.STAR) then + in.nextToken() + Annotated(ref, makeReachAnnot()) + else if in.isIdent(nme.rd) then + in.nextToken() + Annotated(ref, makeReadOnlyAnnot()) + else if in.isIdent(nme.only) then + in.nextToken() + Annotated(ref, makeOnlyAnnot(inBrackets(convertToTypeId(qualId())))) + else assert(false) def recur(ref: Tree): Tree = if in.token == DOT then in.nextToken() - if in.isIdent(nme.rd) then derived(ref, nme.CC_READONLY) + if in.isIdent(nme.rd) || in.isIdent(nme.only) then derived(ref) else recur(selector(ref)) else if in.isIdent(nme.raw.STAR) then - val reachRef = derived(ref, nme.CC_REACH) - if in.token == DOT && in.lookahead.isIdent(nme.rd) then + val reachRef = derived(ref) + val next = in.lookahead + if in.token == DOT && (next.isIdent(nme.rd) || next.isIdent(nme.only)) then in.nextToken() - derived(reachRef, nme.CC_READONLY) + derived(reachRef) else reachRef else ref diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index f6e60cd990d6..3416337e754f 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -467,6 +467,7 @@ class PlainPrinter(_ctx: Context) extends Printer { def toTextCapability(c: Capability): Text = c match case ReadOnly(c1) => toTextCapability(c1) ~ ".rd" + case Restricted(c1, cls) => toTextCapability(c1) ~ s".only[${nameString(cls)}]" case Reach(c1) => toTextCapability(c1) ~ "*" case Maybe(c1) => toTextCapability(c1) ~ "?" case GlobalCap => "cap" @@ -480,7 +481,10 @@ class PlainPrinter(_ctx: Context) extends Printer { 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"" + def classified = + if c.hiddenSet.classifier == defn.AnyClass then "" + else s" classified as ${c.hiddenSet.classifier.name.show}" + if ccVerbose then s"" else "cap" case tp: TypeProxy => homogenize(tp) match diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 2578bbba6316..1e58268c2e38 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -762,12 +762,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val opPrec = parsing.precedence(op.name) changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) } case PostfixOp(l, op) => - if op.name == nme.CC_REACH then - changePrec(DotPrec) { toText(l) ~ "*" } - else if op.name == nme.CC_READONLY then - changePrec(DotPrec) { toText(l) ~ ".rd" } - else - changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) } + changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) } case PrefixOp(op, r) => changePrec(DotPrec) { toText(op) ~ " " ~ toText(r) } case Parens(t) => diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 25fed4e62de9..3952959c93e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -558,8 +558,8 @@ object Implicits: var str1 = err.refStr(alt1.ref) var str2 = err.refStr(alt2.ref) if str1 == str2 then - str1 = ctx.printer.toTextRef(alt1.ref).show - str2 = ctx.printer.toTextRef(alt2.ref).show + str1 = alt1.ref.showRef + str2 = alt2.ref.showRef em"both $str1 and $str2 $qualify".withoutDisambiguation() override def toAdd(using Context) = @@ -1724,7 +1724,7 @@ trait Implicits: "argument" def showResult(r: SearchResult) = r match - case r: SearchSuccess => ctx.printer.toTextRef(r.ref).show + case r: SearchSuccess => r.ref.showRef case r => r.show result match diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 98fbede5f5ba..f9027cf7a961 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -336,7 +336,7 @@ trait ImportSuggestions: if ref.symbol.is(ExtensionMethod) then s"${ctx.printer.toTextPrefixOf(ref).show}${ref.symbol.name}" else - ctx.printer.toTextRef(ref).show + ref.showRef s" import $imported" val suggestions = suggestedRefs .zip(suggestedRefs.map(importString)) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 899b7f5d3c0b..9a5d3d4b2776 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -235,7 +235,8 @@ TypeBound ::= Type NamesAndTypes ::= NameAndType {‘,’ NameAndType} NameAndType ::= id ':' Type CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking -CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’] -- under captureChecking +CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [CapFilter] [‘.’ ‘rd’] -- under captureChecking +CapFilter ::= ‘.’ ‘as’ ‘[’ QualId ’]’ -- under captureChecking ``` ### Expressions diff --git a/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index 485dcecb37df..f6f9bcf40006 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -8,7 +8,7 @@ import annotation.{implicitNotFound, experimental, capability} */ @experimental @implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - Adding a using clause `(using CanThrow[${E}])` to the definition of the enclosing method\n - Adding `throws ${E}` clause after the result type of the enclosing method\n - Wrapping this piece of code with a `try` block that catches ${E}") -erased class CanThrow[-E <: Exception] extends caps.SharedCapability +erased class CanThrow[-E <: Exception] extends caps.Control @experimental object unsafeExceptions: diff --git a/library/src/scala/annotation/internal/onlyCapability.scala b/library/src/scala/annotation/internal/onlyCapability.scala new file mode 100644 index 000000000000..6eaa72d45dcc --- /dev/null +++ b/library/src/scala/annotation/internal/onlyCapability.scala @@ -0,0 +1,8 @@ +package scala.annotation +package internal + +/** An annotation that represents a capability `c.only[T]`, + * encoded as `x.type @onlyCapability[T]` + */ +class onlyCapability[T] extends StaticAnnotation + diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index fedfd7400e25..135437752441 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -25,19 +25,32 @@ import annotation.{experimental, compileTimeOnly, retainsCap} @experimental trait Capability extends Any +/** A marker trait for classifier capabilities that can appear in `.only` + * qualifiers. Capability classes directly extending `Classifier` are treated + * as classifier capbilities + */ +@experimental +trait Classifier + /** The universal capture reference. */ @experimental object cap extends Capability /** Marker trait for classes with methods that requires an exclusive reference. */ @experimental -trait Mutable extends Capability +trait Mutable extends Capability, Classifier /** Marker trait for capabilities that can be safely shared in a concurrent context. * During separation checking, shared capabilities are not taken into account. */ @experimental -trait SharedCapability extends Capability +trait Sharable extends Capability, Classifier + +/** Base trait for capabilities that capture some continuation or return point in + * the stack. Examples are exceptions, labels, Async, CanThrow. + */ +@experimental +trait Control extends Sharable, Classifier /** Carrier trait for capture set type parameters */ @experimental diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index c57136b262dd..d4367a230aaa 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,6 +9,7 @@ object MiMaFilters { // Additions that require a new minor version of the library Build.mimaPreviousDottyVersion -> Seq( ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.readOnlyCapability"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.onlyCapability"), // Scala.js-only class ProblemFilters.exclude[FinalClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), diff --git a/tests/neg-custom-args/captures/capt-capability.scala b/tests/neg-custom-args/captures/capt-capability.scala index 7813ad8144b8..0f293872da25 100644 --- a/tests/neg-custom-args/captures/capt-capability.scala +++ b/tests/neg-custom-args/captures/capt-capability.scala @@ -1,7 +1,7 @@ -import caps.{Capability, SharedCapability} +import caps.{Capability, Sharable} def foo() = - val x: SharedCapability = ??? + val x: Sharable = ??? val z3 = if x == null then (y: Unit) => x else (y: Unit) => new Capability() {} // error diff --git a/tests/neg-custom-args/captures/cc-existential-conformance.check b/tests/neg-custom-args/captures/cc-existential-conformance.check index a644b4c897df..549e1c0543b5 100644 --- a/tests/neg-custom-args/captures/cc-existential-conformance.check +++ b/tests/neg-custom-args/captures/cc-existential-conformance.check @@ -19,7 +19,7 @@ | where: ^ refers to a root capability associated with the result type of (x: A): B^ | | Note that the existential capture root in B^ - | cannot subsume the capability y* since that capability is not a SharedCapability + | cannot subsume the capability y* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-existential-conformance.scala:13:19 ------------------- @@ -43,6 +43,6 @@ | where: ^ refers to a root capability associated with the result type of (x: A): B^ | | Note that the existential capture root in B^ - | cannot subsume the capability y* since that capability is not a SharedCapability + | cannot subsume the capability y* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-poly-source.scala b/tests/neg-custom-args/captures/cc-poly-source.scala index fc47e6504810..915903d670e8 100644 --- a/tests/neg-custom-args/captures/cc-poly-source.scala +++ b/tests/neg-custom-args/captures/cc-poly-source.scala @@ -30,7 +30,7 @@ import caps.use val listeners = lbls.map(makeListener) // error // we get an error here because we no longer allow contravariant cap // to subsume other capabilities. The problem can be solved by declaring - // Label a SharedCapability, see cc-poly-source-capability.scala + // Label a Sharable, see cc-poly-source-capability.scala val src = Source[{lbls*}] for l <- listeners do src.register(l) diff --git a/tests/neg-custom-args/captures/classified-inheritance.check b/tests/neg-custom-args/captures/classified-inheritance.check new file mode 100644 index 000000000000..629f815c4b06 --- /dev/null +++ b/tests/neg-custom-args/captures/classified-inheritance.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-custom-args/captures/classified-inheritance.scala:5:6 ---------------------------------------------- +5 |class C2 extends caps.Control, caps.Mutable // error + | ^ + | class C2 inherits two unrelated classifier traits: trait Mutable and trait Control +-- Error: tests/neg-custom-args/captures/classified-inheritance.scala:10:6 --------------------------------------------- +10 |class C3 extends Matrix, Async // error + | ^ + | class C3 inherits two unrelated classifier traits: trait Control and trait Mutable diff --git a/tests/neg-custom-args/captures/classified-inheritance.scala b/tests/neg-custom-args/captures/classified-inheritance.scala new file mode 100644 index 000000000000..11f342d314a7 --- /dev/null +++ b/tests/neg-custom-args/captures/classified-inheritance.scala @@ -0,0 +1,10 @@ +import language.experimental.captureChecking + +class C1 extends caps.Control, caps.Sharable // OK + +class C2 extends caps.Control, caps.Mutable // error + +trait Async extends caps.Control +class Matrix extends caps.Mutable + +class C3 extends Matrix, Async // error diff --git a/tests/neg-custom-args/captures/classified-wf.check b/tests/neg-custom-args/captures/classified-wf.check new file mode 100644 index 000000000000..9552653d84df --- /dev/null +++ b/tests/neg-custom-args/captures/classified-wf.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-custom-args/captures/classified-wf.scala:7:19 ------------------------------------------------------ +7 |def foo(x: Object^{cap.only[Async]}) = ??? // error + | ^^^^^^^^^^^^^^^ + | scala.caps.cap.only[Async] is not well-formed since class Async is not a classifier class. + | A classifier class is a class extending `caps.Capability` and directly extending `caps.Classifier`. diff --git a/tests/neg-custom-args/captures/classified-wf.scala b/tests/neg-custom-args/captures/classified-wf.scala new file mode 100644 index 000000000000..98a61b2d7b7d --- /dev/null +++ b/tests/neg-custom-args/captures/classified-wf.scala @@ -0,0 +1,8 @@ +import caps.* + +class Async extends Capability + +class IO extends Capability, Classifier + +def foo(x: Object^{cap.only[Async]}) = ??? // error +def bar(x: Object^{cap.only[IO]}) = ??? // ok diff --git a/tests/neg-custom-args/captures/classifiers-1.scala b/tests/neg-custom-args/captures/classifiers-1.scala new file mode 100644 index 000000000000..ee49330ec801 --- /dev/null +++ b/tests/neg-custom-args/captures/classifiers-1.scala @@ -0,0 +1,9 @@ +class M extends caps.Mutable + +class M1(x: Int => Int) extends M // error + +def f(x: M^) = ??? + +def test(g: Int => Int) = f(new M1(g)) // error + + diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 33596772b9a0..56ab856a7782 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -14,7 +14,7 @@ end boundary import boundary.{Label, break} -trait Async extends caps.SharedCapability +trait Async extends caps.Sharable object Async: def blocking[T](body: Async ?=> T): T = ??? diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 06dca2bfa004..27d84d27e556 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -14,7 +14,7 @@ end boundary import boundary.{Label, break} -trait Async extends caps.SharedCapability +trait Async extends caps.Sharable object Async: def blocking[T](body: Async ?=> T): T = ??? diff --git a/tests/neg-custom-args/captures/erased-methods2.check b/tests/neg-custom-args/captures/erased-methods2.check index d7cca4635f20..8e163795f94b 100644 --- a/tests/neg-custom-args/captures/erased-methods2.check +++ b/tests/neg-custom-args/captures/erased-methods2.check @@ -9,7 +9,7 @@ | ^ refers to the universal root capability | |Note that the existential capture root in (erased x$2: CT[Ex2]^) ?=> Unit - |cannot subsume the capability x$1.type since that capability is not a SharedCapability + |cannot subsume the capability x$1.type since that capability is not a `Sharable` capability 21 | ?=> (x$2: CT[Ex2]^) 22 | ?=> 23 | //given (CT[Ex3]^) = x$1 @@ -28,7 +28,7 @@ | ^ refers to the universal root capability | |Note that the existential capture root in (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?=> Unit - |cannot subsume the capability x$1.type since that capability is not a SharedCapability + |cannot subsume the capability x$1.type since that capability is not a `Sharable` capability 32 | ?=> (erased x$2: CT[Ex2]^) 33 | ?=> (erased x$3: CT[Ex1]^) 34 | ?=> Throw(new Ex3) diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index d4a3734ff226..cfda44733b6e 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -23,7 +23,7 @@ | ^ refers to the universal root capability | | Note that the existential capture root in () => Unit - | cannot subsume the capability x$0.type since that capability is not a SharedCapability + | cannot subsume the capability x$0.type since that capability is not a `Sharable` capability 16 | (c1: Capp^) => () => { c1.use() } 17 | } | diff --git a/tests/neg-custom-args/captures/i16226.check b/tests/neg-custom-args/captures/i16226.check index 6d59d362b464..1d79d29165dc 100644 --- a/tests/neg-custom-args/captures/i16226.check +++ b/tests/neg-custom-args/captures/i16226.check @@ -22,6 +22,6 @@ | ^ refers to a root capability associated with the result type of (ref: LazyRef[A]^{io}, f: A =>² B): LazyRef[B]^ | |Note that the existential capture root in LazyRef[B]^ - |cannot subsume the capability f1.type since that capability is not a SharedCapability + |cannot subsume the capability f1.type since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index 0d683cbaf1ca..9734845a2e31 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -75,7 +75,7 @@ | ^² refers to a root capability associated with the result type of (x: File^): File^² | | Note that the existential capture root in File^ - | cannot subsume the capability x.type since that capability is not a SharedCapability + | cannot subsume the capability x.type since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:70:38 -------------------------------------- diff --git a/tests/neg-custom-args/captures/scoped-caps.check b/tests/neg-custom-args/captures/scoped-caps.check index b92464f8ce6f..65d9865a393c 100644 --- a/tests/neg-custom-args/captures/scoped-caps.check +++ b/tests/neg-custom-args/captures/scoped-caps.check @@ -19,7 +19,7 @@ | ^² refers to a root capability associated with the result type of (x: A^): B^² | | Note that the existential capture root in B^ - | cannot subsume the capability g* since that capability is not a SharedCapability + | cannot subsume the capability g* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:10:20 ---------------------------------- @@ -36,14 +36,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:13:25 ---------------------------------- 13 | val _: (x: A^) -> B^ = x => f(x) // error: existential in B cannot subsume `x` since `x` is not shared | ^^^^^^^^^ - | Found: (x: A^) ->? B^{x} - | Required: (x: A^) -> B^² + | Found: (x: A^) ->? B^{x} + | Required: (x: A^) -> B^² | - | where: ^ refers to the universal root capability - | ^² refers to a root capability associated with the result type of (x: A^): B^² + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: A^): B^² | - | Note that the existential capture root in B^ - | cannot subsume the capability x.type since that capability is not a SharedCapability + | Note that the existential capture root in B^ + | cannot subsume the capability x.type since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:16:24 ---------------------------------- @@ -56,7 +56,7 @@ | cap is the universal root capability | | Note that the existential capture root in B^ - | cannot subsume the capability h* since that capability is not a SharedCapability + | cannot subsume the capability h* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:17:24 ---------------------------------- @@ -69,7 +69,7 @@ | cap is the universal root capability | | Note that the existential capture root in B^ - | cannot subsume the capability h* since that capability is not a SharedCapability + | cannot subsume the capability h* since that capability is not a `Sharable` capability | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps.scala:26:19 ---------------------------------- diff --git a/tests/neg-custom-args/captures/scoped-caps.scala b/tests/neg-custom-args/captures/scoped-caps.scala index 184501d08288..9d11f9f00b7e 100644 --- a/tests/neg-custom-args/captures/scoped-caps.scala +++ b/tests/neg-custom-args/captures/scoped-caps.scala @@ -1,6 +1,6 @@ class A class B -class S extends caps.SharedCapability +class S extends caps.Sharable def test(io: Object^): Unit = val f: (x: A^) -> B^ = ??? diff --git a/tests/neg-custom-args/captures/shared-capability.check b/tests/neg-custom-args/captures/shared-capability.check index 15355a9fc5b9..0c575cd69b78 100644 --- a/tests/neg-custom-args/captures/shared-capability.check +++ b/tests/neg-custom-args/captures/shared-capability.check @@ -1,6 +1,6 @@ -- Error: tests/neg-custom-args/captures/shared-capability.scala:9:13 -------------------------------------------------- 9 |def test2(a: Async^): Object^ = a // error | ^^^^^^ - | Async^ extends SharedCapability, so it cannot capture `cap` + | Async^ extends Sharable, so it cannot capture `cap` | | where: ^ refers to the universal root capability diff --git a/tests/neg-custom-args/captures/shared-capability.scala b/tests/neg-custom-args/captures/shared-capability.scala index 262a6db386ba..f10bc6f53444 100644 --- a/tests/neg-custom-args/captures/shared-capability.scala +++ b/tests/neg-custom-args/captures/shared-capability.scala @@ -1,8 +1,8 @@ -import caps.SharedCapability +import caps.Sharable -class Async extends SharedCapability +class Async extends Sharable def test1(a: Async): Object^ = a // OK diff --git a/tests/new/test.scala b/tests/new/test.scala index d350e15a8c9f..5a107ed2b5d1 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,15 +1,3 @@ - -package foo - -package object bar: - opaque type O[X] >: X = X - -class Test: - import bar.O - - val x = "abc" - val y: O[String] = x - //val z: String = y - - - +type C^ = {caps.cap} +type D^ >: {caps.cap} <: C +type E^ >: D <: C diff --git a/tests/pos-custom-args/captures/capt-capability.scala b/tests/pos-custom-args/captures/capt-capability.scala index d82f78263d18..21c63d3fa608 100644 --- a/tests/pos-custom-args/captures/capt-capability.scala +++ b/tests/pos-custom-args/captures/capt-capability.scala @@ -1,4 +1,4 @@ -import caps.{Capability, SharedCapability} +import caps.{Capability, Sharable} def f1(c: Capability): () ->{c} c.type = () => c // ok @@ -14,7 +14,7 @@ def f3: Int = x def foo() = - val x: SharedCapability = ??? + val x: Sharable = ??? val y: Capability = x val x2: () ->{x} Capability = ??? val y2: () ->{x} Capability = x2 diff --git a/tests/pos-custom-args/captures/cc-poly-source-capability.scala b/tests/pos-custom-args/captures/cc-poly-source-capability.scala index c76e6067fbef..7d06edd36415 100644 --- a/tests/pos-custom-args/captures/cc-poly-source-capability.scala +++ b/tests/pos-custom-args/captures/cc-poly-source-capability.scala @@ -1,11 +1,11 @@ import language.experimental.captureChecking import annotation.experimental -import caps.{CapSet, SharedCapability} +import caps.{CapSet, Sharable} import caps.use @experimental object Test: - class Async extends SharedCapability + class Async extends Sharable def listener(async: Async): Listener^{async} = ??? diff --git a/tests/pos-custom-args/captures/i16226.scala b/tests/pos-custom-args/captures/i16226.scala index af0a44e6bdfc..79893d7266ba 100644 --- a/tests/pos-custom-args/captures/i16226.scala +++ b/tests/pos-custom-args/captures/i16226.scala @@ -1,4 +1,4 @@ -class Cap extends caps.SharedCapability +class Cap extends caps.Sharable class LazyRef[T](val elem: () => T): val get: () ->{elem} T = elem diff --git a/tests/pos-custom-args/captures/reach-capability.scala b/tests/pos-custom-args/captures/reach-capability.scala index 7160b280ce4f..77bd91957fa0 100644 --- a/tests/pos-custom-args/captures/reach-capability.scala +++ b/tests/pos-custom-args/captures/reach-capability.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking import annotation.experimental -import caps.SharedCapability +import caps.Sharable import caps.use @experimental object Test2: @@ -8,7 +8,7 @@ import caps.use class List[+A]: def map[B](f: A => B): List[B] = ??? - class Label extends SharedCapability + class Label extends Sharable class Listener diff --git a/tests/pos-custom-args/captures/restrict-try.scala b/tests/pos-custom-args/captures/restrict-try.scala new file mode 100644 index 000000000000..14848ba98066 --- /dev/null +++ b/tests/pos-custom-args/captures/restrict-try.scala @@ -0,0 +1,10 @@ +import caps.{Capability, Control} + +class Try[+T] +case class Ok[T](x: T) extends Try[T] +case class Fail(ex: Exception) extends Try[Nothing] + +object Try: + def apply[T](body: => T): Try[T]^{body.only[Control]} = + try Ok(body) + catch case ex: Exception => Fail(ex) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index fd0281c5fffc..f43c71dfb9da 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -33,12 +33,14 @@ val experimentalDefinitionInLibrary = Set( "scala.Pure", "scala.caps.CapSet", "scala.caps.Capability", + "scala.caps.Classifier", "scala.caps.Contains", "scala.caps.Contains$", "scala.caps.Contains$.containsImpl", "scala.caps.Exists", "scala.caps.Mutable", - "scala.caps.SharedCapability", + "scala.caps.Sharable", + "scala.caps.Control", "scala.caps.consume", "scala.caps.internal", "scala.caps.internal$",