diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index c42e8f71246d..b7ad12369b88 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -521,12 +521,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def captureRoot(using Context): Select = Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) - def captureRootIn(using Context): Select = - Select(scalaDot(nme.caps), nme.capIn) - def makeRetaining(parent: Tree, refs: List[Tree], annotName: TypeName)(using Context): Annotated = Annotated(parent, New(scalaAnnotationDot(annotName), List(refs))) + def makeCapsOf(id: Ident)(using Context): Tree = + TypeApply(Select(scalaDot(nme.caps), nme.capsOf), id :: Nil) + + def makeCapsBound()(using Context): Tree = + makeRetaining( + Select(scalaDot(nme.caps), tpnme.CapSet), + Nil, tpnme.retainsCap) + 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/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index c272183b6dfb..8fb7d1492080 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -14,6 +14,8 @@ import tpd.* import StdNames.nme import config.Feature import collection.mutable +import CCState.* +import reporting.Message private val Captures: Key[CaptureSet] = Key() @@ -25,11 +27,19 @@ object ccConfig: */ inline val allowUnsoundMaps = false - /** If true, use `sealed` as encapsulation mechanism instead of the - * previous global retriction that `cap` can't be boxed or unboxed. + /** If true, use existential capture set variables */ + def useExistentials(using Context) = + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) + + /** If true, use "sealed" as encapsulation mechanism, meaning that we + * check that type variable instantiations don't have `cap` in any of + * their capture sets. This is an alternative of the original restriction + * that `cap` can't be boxed or unboxed. It is used in 3.3 and 3.4 but + * dropped again in 3.5. */ - def allowUniversalInBoxed(using Context) = - Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) + def useSealed(using Context) = + Feature.sourceVersion.stable == SourceVersion.`3.3` + || Feature.sourceVersion.stable == SourceVersion.`3.4` end ccConfig @@ -54,7 +64,7 @@ def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames mt.toFunctionType(alwaysDependent = true) /** An exception thrown if a @retains argument is not syntactically a CaptureRef */ -class IllegalCaptureRef(tpe: Type) extends Exception(tpe.toString) +class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show) /** Capture checking state, which is known to other capture checking components */ class CCState: @@ -64,11 +74,52 @@ class CCState: */ var levelError: Option[CaptureSet.CompareResult.LevelError] = None + /** Warnings relating to upper approximations of capture sets with + * existentially bound variables. + */ + val approxWarnings: mutable.ListBuffer[Message] = mutable.ListBuffer() + + private var curLevel: Level = outermostLevel + private val symLevel: mutable.Map[Symbol, Int] = mutable.Map() + +object CCState: + + opaque type Level = Int + + val undefinedLevel: Level = -1 + + val outermostLevel: Level = 0 + + /** The level of the current environment. Levels start at 0 and increase for + * each nested function or class. -1 means the level is undefined. + */ + def currentLevel(using Context): Level = ccState.curLevel + + inline def inNestedLevel[T](inline op: T)(using Context): T = + val ccs = ccState + val saved = ccs.curLevel + ccs.curLevel = ccs.curLevel.nextInner + try op finally ccs.curLevel = saved + + inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T = + val ccs = ccState + val saved = ccs.curLevel + if !p then ccs.curLevel = ccs.curLevel.nextInner + try op finally ccs.curLevel = saved + + extension (x: Level) + def isDefined: Boolean = x >= 0 + def <= (y: Level) = (x: Int) <= y + def nextInner: Level = if isDefined then x + 1 else x + + extension (sym: Symbol)(using Context) + def ccLevel: Level = ccState.symLevel.getOrElse(sym, -1) + def recordLevel() = ccState.symLevel(sym) = currentLevel end CCState /** The currently valid CCState */ def ccState(using Context) = - Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState + Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( i"No common capture root nested in ${rs.mkString(" and ")}" @@ -76,13 +127,21 @@ class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( extension (tree: Tree) - /** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */ - def toCaptureRef(using Context): CaptureRef = tree match + /** Map tree with CaptureRef type to its type, + * map CapSet^{refs} to the `refs` references, + * throw IllegalCaptureRef otherwise + */ + def toCaptureRefs(using Context): List[CaptureRef] = tree match case ReachCapabilityApply(arg) => - arg.toCaptureRef.reach - case _ => tree.tpe match + arg.toCaptureRefs.map(_.reach) + case CapsOfApply(arg) => + arg.toCaptureRefs + case _ => tree.tpe.dealiasKeepAnnots match case ref: CaptureRef if ref.isTrackableRef => - ref + ref :: Nil + case AnnotatedType(parent, ann) + if ann.symbol.isRetains && parent.derivesFrom(defn.Caps_CapSet) => + ann.tree.toCaptureSet.elems.toList case tpe => throw IllegalCaptureRef(tpe) // if this was compiled from cc syntax, problem should have been reported at Typer @@ -93,8 +152,8 @@ extension (tree: Tree) tree.getAttachment(Captures) match case Some(refs) => refs case None => - val refs = CaptureSet(tree.retainedElems.map(_.toCaptureRef)*) - .showing(i"toCaptureSet $tree --> $result", capt) + val refs = CaptureSet(tree.retainedElems.flatMap(_.toCaptureRefs)*) + //.showing(i"toCaptureSet $tree --> $result", capt) tree.putAttachment(Captures, refs) refs @@ -109,6 +168,60 @@ extension (tree: Tree) extension (tp: Type) + /** Is this type a CaptureRef that can be tracked? + * This is true for + * - all ThisTypes and all TermParamRef, + * - stable TermRefs with NoPrefix or ThisTypes as prefixes, + * - the root capability `caps.cap` + * - abstract or parameter TypeRefs that derive from caps.CapSet + * - annotated types that represent reach or maybe capabilities + */ + final def isTrackableRef(using Context): Boolean = tp match + case _: (ThisType | TermParamRef) => + true + case tp: TermRef => + ((tp.prefix eq NoPrefix) + || tp.symbol.is(ParamAccessor) && tp.prefix.isThisTypeOf(tp.symbol.owner) + || tp.isRootCapability + ) && !tp.symbol.isOneOf(UnstableValueFlags) + case tp: TypeRef => + tp.symbol.isAbstractOrParamType && tp.derivesFrom(defn.Caps_CapSet) + case tp: TypeParamRef => + tp.derivesFrom(defn.Caps_CapSet) + case AnnotatedType(parent, annot) => + annot.symbol == defn.ReachCapabilityAnnot + || annot.symbol == defn.MaybeCapabilityAnnot + case _ => + false + + /** The capture set of a type. This is: + * - For trackable capture references: The singleton capture set consisting of + * just the reference, provided the underlying capture set of their info is not empty. + * - For other capture references: The capture set of their info + * - For all other types: The result of CaptureSet.ofType + */ + final def captureSet(using Context): CaptureSet = tp match + case tp: CaptureRef if tp.isTrackableRef => + val cs = tp.captureSetOfInfo + if cs.isAlwaysEmpty then cs else tp.singletonCaptureSet + case tp: SingletonCaptureRef => tp.captureSetOfInfo + case _ => CaptureSet.ofType(tp, followResult = false) + + /** A type capturing `ref` */ + def capturing(ref: CaptureRef)(using Context): Type = + if tp.captureSet.accountsFor(ref) then tp + else CapturingType(tp, ref.singletonCaptureSet) + + /** A type capturing the capture set `cs`. If this type is already a capturing type + * the two capture sets are combined. + */ + def capturing(cs: CaptureSet)(using Context): Type = + if cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, frozen = true).isOK + then tp + else tp match + case CapturingType(parent, cs1) => parent.capturing(cs1 ++ cs) + case _ => CapturingType(tp, cs) + /** @pre `tp` is a CapturingType */ def derivedCapturingType(parent: Type, refs: CaptureSet)(using Context): Type = tp match case tp @ CapturingType(p, r) => @@ -158,7 +271,15 @@ extension (tp: Type) getBoxed(tp) /** Is the boxedCaptureSet of this type nonempty? */ - def isBoxedCapturing(using Context) = !tp.boxedCaptureSet.isAlwaysEmpty + def isBoxedCapturing(using Context): Boolean = + tp match + case tp @ CapturingType(parent, refs) => + tp.isBoxed && !refs.isAlwaysEmpty || parent.isBoxedCapturing + case tp: TypeRef if tp.symbol.isAbstractOrParamType => false + case tp: TypeProxy => tp.superType.isBoxedCapturing + case tp: AndType => tp.tp1.isBoxedCapturing && tp.tp2.isBoxedCapturing + case tp: OrType => tp.tp1.isBoxedCapturing || tp.tp2.isBoxedCapturing + case _ => false /** If this type is a capturing type, the version with boxed statues as given by `boxed`. * If it is a TermRef of a capturing type, and the box status flips, widen to a capturing @@ -211,7 +332,7 @@ extension (tp: Type) val sym = tp.typeSymbol if sym.isClass then sym.derivesFrom(defn.Caps_Capability) else tp.superType.derivesFromCapability - case tp: TypeProxy => + case tp: (TypeProxy & ValueType) => tp.superType.derivesFromCapability case tp: AndType => tp.tp1.derivesFromCapability || tp.tp2.derivesFromCapability @@ -307,6 +428,7 @@ extension (tp: Type) ok = false case _ => traverseChildren(t) + end CheckContraCaps object narrowCaps extends TypeMap: /** Has the variance been flipped at this point? */ @@ -319,16 +441,25 @@ extension (tp: Type) t.dealias match case t1 @ CapturingType(p, cs) if cs.isUniversal && !isFlipped => t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) + case t1 @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + // Also map existentials in results to reach capabilities if all + // preceding arguments are known to be always pure + apply(t1.derivedFunctionOrMethod(args, Existential.toCap(res))) + case Existential(_, _) => + t case _ => t match case t @ CapturingType(p, cs) => t.derivedCapturingType(apply(p), cs) // don't map capture set variables case t => mapOver(t) finally isFlipped = saved + end narrowCaps + ref match case ref: CaptureRef if ref.isTrackableRef => val checker = new CheckContraCaps - checker.traverse(tp) + if !ccConfig.useExistentials then checker.traverse(tp) if checker.ok then val tp1 = narrowCaps(tp) if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") @@ -339,6 +470,12 @@ extension (tp: Type) case _ => tp + def level(using Context): Level = + tp match + case tp: TermRef => tp.symbol.ccLevel + case tp: ThisType => tp.cls.ccLevel.nextInner + case _ => undefinedLevel + extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = @@ -414,43 +551,20 @@ extension (sym: Symbol) && !sym.allowsRootCapture && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox + && !defn.isPolymorphicAfterErasure(sym) - /** Does this symbol define a level where we do not want to let local variables - * escape into outer capture sets? - */ - def isLevelOwner(using Context): Boolean = - sym.isClass - || sym.is(Method, butNot = Accessor) - - /** The owner of the current level. Qualifying owners are - * - methods other than constructors and anonymous functions - * - anonymous functions, provided they either define a local - * root of type caps.Capability, or they are the rhs of a val definition. - * - classes, if they are not staticOwners - * - _root_ - */ - def levelOwner(using Context): Symbol = - def recur(sym: Symbol): Symbol = - if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass - else if sym.isLevelOwner then sym - else recur(sym.owner) - recur(sym) - - /** The outermost symbol owned by both `sym` and `other`. if none exists - * since the owning scopes of `sym` and `other` are not nested, invoke - * `onConflict` to return a symbol. - */ - def maxNested(other: Symbol, onConflict: (Symbol, Symbol) => Context ?=> Symbol)(using Context): Symbol = - if !sym.exists || other.isContainedIn(sym) then other - else if !other.exists || sym.isContainedIn(other) then sym - else onConflict(sym, other) + def isRefiningParamAccessor(using Context): Boolean = + sym.is(ParamAccessor) + && { + val param = sym.owner.primaryConstructor.paramSymss + .nestedFind(_.name == sym.name) + .getOrElse(NoSymbol) + !param.hasAnnotation(defn.ConstructorOnlyAnnot) + && !param.hasAnnotation(defn.UntrackedCapturesAnnot) + } - /** The innermost symbol owning both `sym` and `other`. - */ - def minNested(other: Symbol)(using Context): Symbol = - if !other.exists || other.isContainedIn(sym) then sym - else if !sym.exists || sym.isContainedIn(other) then other - else sym.owner.minNested(other.owner) + def hasTrackedParts(using Context): Boolean = + !CaptureSet.deepCaptureSet(sym.info).isAlwaysEmpty extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ @@ -474,6 +588,14 @@ object ReachCapabilityApply: case Apply(reach, arg :: Nil) if reach.symbol == defn.Caps_reachCapability => Some(arg) case _ => None +/** An extractor for `caps.capsOf[X]`, which is used to express a generic capture set + * as a tree in a @retains annotation. + */ +object CapsOfApply: + def unapply(tree: TypeApply)(using Context): Option[Tree] = tree match + case TypeApply(capsOf, arg :: Nil) if capsOf.symbol == defn.Caps_capsOf => Some(arg) + case _ => None + class AnnotatedCapability(annot: Context ?=> ClassSymbol): def apply(tp: Type)(using Context) = AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) @@ -491,7 +613,35 @@ object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot) */ object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot) +/** Offers utility method to be used for type maps that follow aliases */ +trait ConservativeFollowAliasMap(using Context) extends TypeMap: + + /** If `mapped` is a type alias, apply the map to the alias, while keeping + * annotations. If the result is different, return it, otherwise return `mapped`. + * Furthermore, if `original` is a LazyRef or TypeVar and the mapped result is + * the same as the underlying type, keep `original`. This avoids spurious differences + * which would lead to spurious dealiasing in the result + */ + protected def applyToAlias(original: Type, mapped: Type) = + val mapped1 = mapped match + case t: (TypeRef | AppliedType) => + val t1 = t.dealiasKeepAnnots + if t1 eq t then t + else + // If we see a type alias, map the alias type and keep it if it's different + val t2 = apply(t1) + if t2 ne t1 then t2 else t + case _ => + mapped + original match + case original: (LazyRef | TypeVar) if mapped1 eq original.underlying => + original + case _ => + mapped1 +end ConservativeFollowAliasMap + /** 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 * 2nd half: The result type */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala new file mode 100644 index 000000000000..6578da89bbf8 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -0,0 +1,124 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Types.*, Symbols.*, Contexts.*, Decorators.* +import util.{SimpleIdentitySet, Property} +import typer.ErrorReporting.Addenda +import TypeComparer.subsumesExistentially +import util.common.alwaysTrue +import scala.collection.mutable +import CCState.* +import Periods.NoRunId +import compiletime.uninitialized +import StdNames.nme + +/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, + * as well as two kinds of AnnotatedTypes representing reach and maybe capabilities. + */ +trait CaptureRef extends TypeProxy, ValueType: + private var myCaptureSet: CaptureSet | Null = uninitialized + private var myCaptureSetRunId: Int = NoRunId + private var mySingletonCaptureSet: CaptureSet.Const | Null = null + + /** 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 && (isMaxCapability || !captureSetOfInfo.isAlwaysEmpty) + + /** Is this a reach reference of the form `x*`? */ + final def isReach(using Context): Boolean = this match + case AnnotatedType(_, annot) => annot.symbol == defn.ReachCapabilityAnnot + case _ => false + + /** Is this a maybe reference of the form `x?`? */ + final def isMaybe(using Context): Boolean = this match + case AnnotatedType(_, annot) => annot.symbol == defn.MaybeCapabilityAnnot + case _ => false + + final def stripReach(using Context): CaptureRef = + if isReach then + val AnnotatedType(parent: CaptureRef, _) = this: @unchecked + parent + else this + + final def stripMaybe(using Context): CaptureRef = + if isMaybe then + val AnnotatedType(parent: CaptureRef, _) = this: @unchecked + parent + else this + + /** Is this reference the generic root capability `cap` ? */ + final def isRootCapability(using Context): Boolean = this match + case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot + case _ => false + + /** Is this reference capability that does not derive from another capability ? */ + final def isMaxCapability(using Context): Boolean = this match + case tp: TermRef => tp.isRootCapability || tp.info.derivesFrom(defn.Caps_Exists) + case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists) + case _ => false + + /** 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.normalizedRef) + mySingletonCaptureSet.uncheckedNN + + /** The capture set of the type underlying this reference */ + final def captureSetOfInfo(using Context): CaptureSet = + if ctx.runId == myCaptureSetRunId 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 || underlying.isProvisional then + myCaptureSet = null + else + myCaptureSet = computed + myCaptureSetRunId = ctx.runId + computed + + final def invalidateCaches() = + myCaptureSetRunId = NoRunId + + /** x subsumes x + * this subsumes this.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 + */ + final def subsumes(y: CaptureRef)(using Context): Boolean = + (this eq y) + || this.isRootCapability + || y.match + case y: TermRef => + (y.prefix eq this) + || y.info.match + case y1: SingletonCaptureRef => this.subsumes(y1) + case _ => false + case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) + case _ => false + || this.match + case ReachCapability(x1) => x1.subsumes(y.stripReach) + case x: TermRef => + x.info match + case x1: SingletonCaptureRef => x1.subsumes(y) + case _ => false + case x: TermParamRef => subsumesExistentially(x, y) + case _ => false + +end CaptureRef + +trait SingletonCaptureRef extends SingletonType, CaptureRef + diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index f78ed1a91bd6..aa65db2375e8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -14,8 +14,10 @@ import printing.{Showable, Printer} import printing.Texts.* import util.{SimpleIdentitySet, Property} import typer.ErrorReporting.Addenda +import TypeComparer.subsumesExistentially import util.common.alwaysTrue import scala.collection.mutable +import CCState.* /** A class for capture sets. Capture sets can be constants or variables. * Capture sets support inclusion constraints <:< where <:< is subcapturing. @@ -55,10 +57,14 @@ sealed abstract class CaptureSet extends Showable: */ def isAlwaysEmpty: Boolean - /** An optional level limit, or NoSymbol if none exists. All elements of the set - * must be in scopes visible from the level limit. + /** An optional level limit, or undefinedLevel if none exists. All elements of the set + * must be at levels equal or smaller than the level of the set, if it is defined. */ - def levelLimit: Symbol + def level: Level + + /** An optional owner, or NoSymbol if none exists. Used for diagnstics + */ + def owner: Symbol /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -79,6 +85,9 @@ sealed abstract class CaptureSet extends Showable: final def isUniversal(using Context) = elems.exists(_.isRootCapability) + final def isUnboxable(using Context) = + elems.exists(elem => elem.isRootCapability || Existential.isExistentialVar(elem)) + /** Try to include an element in this capture set. * @param elem The element to be added * @param origin The set that originated the request, or `empty` if the request came from outside. @@ -143,32 +152,6 @@ sealed abstract class CaptureSet extends Showable: cs.addDependent(this)(using ctx, UnrecordedState) this - /** x subsumes x - * this subsumes this.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 - */ - extension (x: CaptureRef) - private def subsumes(y: CaptureRef)(using Context): Boolean = - (x eq y) - || x.isRootCapability - || y.match - case y: TermRef => - (y.prefix eq x) - || y.info.match - case y1: CaptureRef => x.subsumes(y1) - case _ => false - case MaybeCapability(y1) => x.stripMaybe.subsumes(y1) - case _ => false - || x.match - case ReachCapability(x1) => x1.subsumes(y.stripReach) - case x: TermRef => - x.info match - case x1: CaptureRef => x1.subsumes(y) - case _ => false - case _ => false - /** {x} <:< this where <:< is subcapturing, but treating all variables * as frozen. */ @@ -239,10 +222,7 @@ sealed abstract class CaptureSet extends Showable: if this.subCaptures(that, frozen = true).isOK then that else if that.subCaptures(this, frozen = true).isOK then this else if this.isConst && that.isConst then Const(this.elems ++ that.elems) - else Var( - this.levelLimit.maxNested(that.levelLimit, onConflict = (sym1, sym2) => sym1), - this.elems ++ that.elems) - .addAsDependentTo(this).addAsDependentTo(that) + else Union(this, that) /** The smallest superset (via <:<) of this capture set that also contains `ref`. */ @@ -255,7 +235,7 @@ sealed abstract class CaptureSet extends Showable: if this.subCaptures(that, frozen = true).isOK then this else if that.subCaptures(this, frozen = true).isOK then that else if this.isConst && that.isConst then Const(elemIntersection(this, that)) - else Intersected(this, that) + else Intersection(this, that) /** The largest subset (via <:<) of this capture set that does not account for * any of the elements in the constant capture set `that` @@ -326,7 +306,7 @@ sealed abstract class CaptureSet extends Showable: /** Invoke handler if this set has (or later aquires) the root capability `cap` */ def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = - if isUniversal then handler() + if isUnboxable then handler() this /** Invoke handler on the elements to ensure wellformedness of the capture set. @@ -411,7 +391,9 @@ object CaptureSet: def withDescription(description: String): Const = Const(elems, description) - def levelLimit = NoSymbol + def level = undefinedLevel + + def owner = NoSymbol override def toString = elems.toString end Const @@ -431,7 +413,7 @@ object CaptureSet: end Fluid /** The subclass of captureset variables with given initial elements */ - class Var(directOwner: Symbol, initialElems: Refs = emptySet)(using @constructorOnly ictx: Context) extends CaptureSet: + class Var(override val owner: Symbol = NoSymbol, initialElems: Refs = emptySet, val level: Level = undefinedLevel, underBox: Boolean = false)(using @constructorOnly ictx: Context) extends CaptureSet: /** A unique identification number for diagnostics */ val id = @@ -440,9 +422,6 @@ object CaptureSet: //assert(id != 40) - override val levelLimit = - if directOwner.exists then directOwner.levelOwner else NoSymbol - /** A variable is solved if it is aproximated to a from-then-on constant set. */ private var isSolved: Boolean = false @@ -455,7 +434,7 @@ object CaptureSet: var deps: Deps = emptySet def isConst = isSolved - def isAlwaysEmpty = false + def isAlwaysEmpty = isSolved && elems.isEmpty def isMaybeSet = false // overridden in BiMapped @@ -494,10 +473,13 @@ object CaptureSet: deps = state.deps(this) final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = - if isConst || !recordElemsState() then - CompareResult.Fail(this :: Nil) // fail if variable is solved or given VarState is frozen + if isConst // Fail if variable is solved, + || !recordElemsState() // or given VarState is frozen, + || Existential.isBadExistential(elem) // or `elem` is an out-of-scope existential, + then + CompareResult.Fail(this :: Nil) else if !levelOK(elem) then - CompareResult.LevelError(this, elem) + CompareResult.LevelError(this, elem) // or `elem` is not visible at the level of the set. else //if id == 34 then assert(!elem.isUniversalRootCapability) assert(elem.isTrackableRef, elem) @@ -514,14 +496,13 @@ object CaptureSet: res.addToTrace(this) private def levelOK(elem: CaptureRef)(using Context): Boolean = - if elem.isRootCapability then !noUniversal + if elem.isRootCapability || Existential.isExistentialVar(elem) then + !noUniversal else elem match - case elem: TermRef if levelLimit.exists => - var sym = elem.symbol - if sym.isLevelOwner then sym = sym.owner - levelLimit.isContainedIn(sym.levelOwner) - case elem: ThisType if levelLimit.exists => - levelLimit.isContainedIn(elem.cls.levelOwner) + case elem: TermRef if level.isDefined => + elem.symbol.ccLevel <= level + case elem: ThisType if level.isDefined => + elem.cls.ccLevel.nextInner <= level case ReachCapability(elem1) => levelOK(elem1) case MaybeCapability(elem1) => @@ -560,7 +541,14 @@ object CaptureSet: universal else computingApprox = true - try computeApprox(origin).ensuring(_.isConst) + try + val approx = computeApprox(origin).ensuring(_.isConst) + if approx.elems.exists(Existential.isExistentialVar(_)) then + ccState.approxWarnings += + em"""Capture set variable $this gets upper-approximated + |to existential variable from $approx, using {cap} instead.""" + universal + else approx finally computingApprox = false /** The intersection of all upper approximations of dependent sets */ @@ -599,8 +587,8 @@ object CaptureSet: val debugInfo = if !isConst && ctx.settings.YccDebug.value then ids else "" val limitInfo = - if ctx.settings.YprintLevel.value && levelLimit.exists - then i"" + if ctx.settings.YprintLevel.value && level.isDefined + then i"" else "" debugInfo ++ limitInfo @@ -623,7 +611,7 @@ object CaptureSet: * capture set, since they represent only what is the result of the constructor. * Test case: Without that tweak, logger.scala would not compile. */ - class RefiningVar(directOwner: Symbol)(using Context) extends Var(directOwner): + class RefiningVar(owner: Symbol)(using Context) extends Var(owner): override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context) = this /** A variable that is derived from some other variable via a map or filter. */ @@ -654,7 +642,7 @@ object CaptureSet: */ class Mapped private[CaptureSet] (val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context) - extends DerivedVar(source.levelLimit, initial.elems): + extends DerivedVar(source.owner, initial.elems): addAsDependentTo(initial) // initial mappings could change by propagation private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap] @@ -692,19 +680,10 @@ object CaptureSet: if cond then propagate else CompareResult.OK val mapped = extrapolateCaptureRef(elem, tm, variance) + def isFixpoint = mapped.isConst && mapped.elems.size == 1 && mapped.elems.contains(elem) - def addMapped = - val added = mapped.elems.filter(!accountsFor(_)) - addNewElems(added) - .andAlso: - if mapped.isConst then CompareResult.OK - else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult.OK } - else CompareResult.Fail(this :: Nil) - .andAlso: - propagateIf(!added.isEmpty) - def failNoFixpoint = val reason = if variance <= 0 then i"the set's variance is $variance" @@ -714,11 +693,14 @@ object CaptureSet: CompareResult.Fail(this :: Nil) if origin eq source then // elements have to be mapped - addMapped + val added = mapped.elems.filter(!accountsFor(_)) + addNewElems(added) .andAlso: if mapped.isConst then CompareResult.OK else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult.OK } else CompareResult.Fail(this :: Nil) + .andAlso: + propagateIf(!added.isEmpty) else if accountsFor(elem) then CompareResult.OK else if variance > 0 then @@ -751,7 +733,7 @@ object CaptureSet: */ final class BiMapped private[CaptureSet] (val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) - extends DerivedVar(source.levelLimit, initialElems): + extends DerivedVar(source.owner, initialElems): override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if origin eq source then @@ -762,9 +744,8 @@ object CaptureSet: CompareResult.OK else source.tryInclude(bimap.backward(elem), this) - .showing(i"propagating new elem $elem backward from $this to $source = $result", capt) - .andAlso: - addNewElem(elem) + .showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug) + .andAlso(addNewElem(elem)) /** For a BiTypeMap, supertypes of the mapped type also constrain * the source via the inverse type mapping and vice versa. That is, if @@ -785,7 +766,7 @@ object CaptureSet: /** A variable with elements given at any time as { x <- source.elems | p(x) } */ class Filtered private[CaptureSet] (val source: Var, p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) - extends DerivedVar(source.levelLimit, source.elems.filter(p)): + extends DerivedVar(source.owner, source.elems.filter(p)): override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if accountsFor(elem) then @@ -814,8 +795,30 @@ object CaptureSet: class Diff(source: Var, other: Const)(using Context) extends Filtered(source, !other.accountsFor(_)) - class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context) - extends Var(cs1.levelLimit.minNested(cs2.levelLimit), elemIntersection(cs1, cs2)): + class Union(cs1: CaptureSet, cs2: CaptureSet)(using Context) + extends Var(initialElems = cs1.elems ++ cs2.elems): + addAsDependentTo(cs1) + addAsDependentTo(cs2) + + override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + if accountsFor(elem) then CompareResult.OK + else + val res = super.tryInclude(elem, origin) + // If this is the union of a constant and a variable, + // propagate `elem` to the variable part to avoid slack + // between the operands and the union. + if res.isOK && (origin ne cs1) && (origin ne cs2) then + if cs1.isConst then cs2.tryInclude(elem, origin) + else if cs2.isConst then cs1.tryInclude(elem, origin) + else res + else res + + override def propagateSolved()(using Context) = + if cs1.isConst && cs2.isConst && !isConst then markSolved() + end Union + + class Intersection(cs1: CaptureSet, cs2: CaptureSet)(using Context) + extends Var(initialElems = elemIntersection(cs1, cs2)): addAsDependentTo(cs1) addAsDependentTo(cs2) deps += cs1 @@ -839,7 +842,7 @@ object CaptureSet: override def propagateSolved()(using Context) = if cs1.isConst && cs2.isConst && !isConst then markSolved() - end Intersected + end Intersection def elemIntersection(cs1: CaptureSet, cs2: CaptureSet)(using Context): Refs = cs1.elems.filter(cs2.mightAccountFor) ++ cs2.elems.filter(cs1.mightAccountFor) @@ -856,7 +859,9 @@ object CaptureSet: val r1 = tm(r) val upper = r1.captureSet def isExact = - upper.isAlwaysEmpty || upper.isConst && upper.elems.size == 1 && upper.elems.contains(r1) + 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.empty else upper.maybe @@ -905,7 +910,7 @@ object CaptureSet: if ctx.settings.YccDebug.value then printer.toText(trace, ", ") else blocking.show case LevelError(cs: CaptureSet, elem: CaptureRef) => - Str(i"($elem at wrong level for $cs in ${cs.levelLimit})") + Str(i"($elem at wrong level for $cs at level ${cs.level.toString})") /** The result is OK */ def isOK: Boolean = this == OK @@ -1057,9 +1062,8 @@ object CaptureSet: tp.captureSet case tp: TermParamRef => tp.captureSet - case tp: TypeRef => - if tp.derivesFromCapability then universal // TODO: maybe return another value that indicates that the underltinf ref is maximal? - else empty + case _: TypeRef => + empty case _: TypeParamRef => empty case CapturingType(parent, refs) => @@ -1089,7 +1093,7 @@ object CaptureSet: recur(tp) //.showing(i"capture set of $tp = $result", captDebug) - private def deepCaptureSet(tp: Type)(using Context): CaptureSet = + def deepCaptureSet(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: def apply(cs: CaptureSet, t: Type) = t.dealias match case t @ CapturingType(p, cs1) => @@ -1148,6 +1152,6 @@ object CaptureSet: i""" | |Note that reference ${ref}$levelStr - |cannot be included in outer capture set $cs which is associated with ${cs.levelLimit}""" + |cannot be included in outer capture set $cs""" end CaptureSet diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index ee0cad4d4d03..f859b0d110aa 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -28,7 +28,6 @@ object CapturingType: /** Smart constructor that * - drops empty capture sets - * - drops a capability class expansion if it is further refined with another capturing type * - fuses compatible capturing types. * An outer type capturing type A can be fused with an inner capturing type B if their * boxing status is the same or if A is boxed. @@ -36,8 +35,6 @@ object CapturingType: def apply(parent: Type, refs: CaptureSet, boxed: Boolean = false)(using Context): Type = if refs.isAlwaysEmpty then parent else parent match - case parent @ CapturingType(parent1, refs1) if refs1 eq defn.expandedUniversalSet => - apply(parent1, refs, boxed) case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed => apply(parent1, refs ++ refs1, boxed) case _ => diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index e41f32cab672..8eb2f2420369 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -12,16 +12,17 @@ import ast.{tpd, untpd, Trees} import Trees.* import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} -import typer.ErrorReporting.{Addenda, err} +import typer.ErrorReporting.{Addenda, NothingToAdd, err} import typer.ProtoTypes.{AnySelectionProto, LhsProto} import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import transform.{Recheck, PreRecheck, CapturedVars} import Recheck.* import scala.collection.mutable import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult} +import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} -import reporting.trace +import reporting.{trace, Message} /** The capture checker */ object CheckCaptures: @@ -122,16 +123,24 @@ object CheckCaptures: case _: SingletonType => report.error(em"Singleton type $parent cannot have capture set", parent.srcPos) case _ => + def check(elem: Tree, pos: SrcPos): Unit = elem.tpe match + case ref: CaptureRef => + if !ref.isTrackableRef 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) for elem <- ann.retainedElems do - val elem1 = elem match - case ReachCapabilityApply(arg) => arg - case _ => elem - elem1.tpe match - case ref: CaptureRef => - if !ref.isTrackableRef then - report.error(em"$elem cannot be tracked since it is not a parameter or local value", elem.srcPos) - case tpe => - report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) + elem match + case CapsOfApply(arg) => + def isLegalCapsOfArg = + arg.symbol.isAbstractOrParamType && arg.symbol.info.derivesFrom(defn.Caps_CapSet) + if !isLegalCapsOfArg then + report.error( + em"""$arg is not a legal prefix for `^` here, + |is must be a type parameter or abstract type with a caps.CapSet upper bound.""", + elem.srcPos) + case ReachCapabilityApply(arg) => check(arg, elem.srcPos) + case _ => check(elem, elem.srcPos) /** Report an error if some part of `tp` contains the root capability in its capture set * or if it refers to an unsealed type parameter that could possibly be instantiated with @@ -169,7 +178,7 @@ object CheckCaptures: traverse(parent) case t => traverseChildren(t) - check.traverse(tp) + if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn /** Attachment key for bodies of closures, provided they are values */ @@ -191,7 +200,7 @@ class CheckCaptures extends Recheck, SymTransformer: if Feature.ccEnabled then super.run - val ccState = new CCState + val ccState1 = new CCState // Dotty problem: Rename to ccState ==> Crash in ExplicitOuter class CaptureChecker(ictx: Context) extends Rechecker(ictx): @@ -222,6 +231,9 @@ class CheckCaptures extends Recheck, SymTransformer: if tpt.isInstanceOf[InferredTypeTree] then interpolator().traverse(tpt.knownType) .showing(i"solved vars in ${tpt.knownType}", capt) + for msg <- ccState.approxWarnings do + report.warning(msg, tpt.srcPos) + ccState.approxWarnings.clear() /** Assert subcapturing `cs1 <: cs2` */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = @@ -255,7 +267,7 @@ class CheckCaptures extends Recheck, SymTransformer: ctx.printer.toTextCaptureRef(ref).show // Uses 4-space indent as a trial - def checkReachCapsIsolated(tpe: Type, pos: SrcPos)(using Context): Unit = + private def checkReachCapsIsolated(tpe: Type, pos: SrcPos)(using Context): Unit = object checker extends TypeTraverser: var refVariances: Map[Boolean, Int] = Map.empty @@ -311,7 +323,7 @@ class CheckCaptures extends Recheck, SymTransformer: def capturedVars(sym: Symbol)(using Context): CaptureSet = myCapturedVars.getOrElseUpdate(sym, if sym.ownersIterator.exists(_.isTerm) - then CaptureSet.Var(sym.owner) + then CaptureSet.Var(sym.owner, level = sym.ccLevel) else CaptureSet.empty) /** For all nested environments up to `limit` or a closed environment perform `op`, @@ -491,7 +503,7 @@ class CheckCaptures extends Recheck, SymTransformer: tp.derivedCapturingType(forceBox(parent), refs) mapArgUsing(forceBox) else - super.recheckApply(tree, pt) match + Existential.toCap(super.recheckApply(tree, pt)) match case appType @ CapturingType(appType1, refs) => tree.fun match case Select(qual, _) @@ -504,7 +516,7 @@ class CheckCaptures extends Recheck, SymTransformer: val callCaptures = tree.args.foldLeft(qual.tpe.captureSet): (cs, arg) => cs ++ arg.tpe.captureSet appType.derivedCapturingType(appType1, callCaptures) - .showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt) + .showing(i"narrow $tree: $appType, refs = $refs, qual-cs = ${qual.tpe.captureSet} = $result", capt) case _ => appType case appType => appType end recheckApply @@ -549,8 +561,8 @@ class CheckCaptures extends Recheck, SymTransformer: var allCaptures: CaptureSet = if core.derivesFromCapability then CaptureSet.universal else initCs for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do - val getter = cls.info.member(getterName).suchThat(_.is(ParamAccessor)).symbol - if getter.termRef.isTracked && !getter.is(Private) then + val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol + if !getter.is(Private) && getter.hasTrackedParts then refined = RefinedType(refined, getterName, argType) allCaptures ++= argType.captureSet (refined, allCaptures) @@ -577,7 +589,7 @@ class CheckCaptures extends Recheck, SymTransformer: end instantiate override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = - if ccConfig.allowUniversalInBoxed then + if ccConfig.useSealed then val TypeApply(fn, args) = tree val polyType = atPhase(thisPhase.prev): fn.tpe.widen.asInstanceOf[TypeLambda] @@ -590,7 +602,10 @@ class CheckCaptures extends Recheck, SymTransformer: i"Sealed type variable $pname", "be instantiated to", i"This is often caused by a local capability$where\nleaking as part of its result.", tree.srcPos) - super.recheckTypeApply(tree, pt) + Existential.toCap(super.recheckTypeApply(tree, pt)) + + override def recheckBlock(tree: Block, pt: Type)(using Context): Type = + inNestedLevel(super.recheckBlock(tree, pt)) override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type = val cs = capturedVars(tree.meth.symbol) @@ -608,10 +623,10 @@ class CheckCaptures extends Recheck, SymTransformer: mdef.rhs.putAttachment(ClosureBodyValue, ()) case _ => - // Constrain closure's parameters and result from the expected type before - // rechecking the body. openClosures = (mdef.symbol, pt) :: openClosures try + // Constrain closure's parameters and result from the expected type before + // rechecking the body. val res = recheckClosure(expr, pt, forceDependent = true) if !isEtaExpansion(mdef) then // If closure is an eta expanded method reference it's better to not constrain @@ -620,7 +635,12 @@ class CheckCaptures extends Recheck, SymTransformer: // Example is the line `a = x` in neg-custom-args/captures/vars.scala. // For all other closures, early constraints are preferred since they // give more localized error messages. - checkConformsExpr(res, pt, expr) + val res1 = Existential.toCapDeeply(res) + val pt1 = Existential.toCapDeeply(pt) + // We need to open existentials here in order not to get vars mixed up in them + // We do the proper check with existentials when we are finished with the closure block. + capt.println(i"pre-check closure $expr of type $res1 against $pt1") + checkConformsExpr(res1, pt1, expr) recheckDef(mdef, mdef.symbol) res finally @@ -695,13 +715,14 @@ class CheckCaptures extends Recheck, SymTransformer: val localSet = capturedVars(sym) if !localSet.isAlwaysEmpty then curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) - try checkInferredResult(super.recheckDefDef(tree, sym), tree) - finally - if !sym.isAnonymousFunction then - // Anonymous functions propagate their type to the enclosing environment - // so it is not in general sound to interpolate their types. - interpolateVarsIn(tree.tpt) - curEnv = saved + inNestedLevel: // TODO: needed here? + try checkInferredResult(super.recheckDefDef(tree, sym), tree) + finally + if !sym.isAnonymousFunction then + // Anonymous functions propagate their type to the enclosing environment + // so it is not in general sound to interpolate their types. + interpolateVarsIn(tree.tpt) + curEnv = saved /** If val or def definition with inferred (result) type is visible * in other compilation units, check that the actual inferred type @@ -759,7 +780,8 @@ class CheckCaptures extends Recheck, SymTransformer: val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls") checkSubset(localSet, thisSet, tree.srcPos) // (2) for param <- cls.paramGetters do - if !param.hasAnnotation(defn.ConstructorOnlyAnnot) then + if !param.hasAnnotation(defn.ConstructorOnlyAnnot) + && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3) for pureBase <- cls.pureBaseClass do // (4) def selfType = impl.body @@ -771,7 +793,8 @@ class CheckCaptures extends Recheck, SymTransformer: checkSubset(thisSet, CaptureSet.empty.withDescription(i"of pure base class $pureBase"), selfType.srcPos, cs1description = " captured by this self type") - super.recheckClassDef(tree, impl, cls) + inNestedLevelUnless(cls.is(Module)): + super.recheckClassDef(tree, impl, cls) finally curEnv = saved @@ -791,7 +814,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckTry(tree: Try, pt: Type)(using Context): Type = val tp = super.recheckTry(tree, pt) - if ccConfig.allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then + if ccConfig.useSealed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, "result of `try`", "have type", "This is often caused by a locally generated exception capability leaking as part of its result.", @@ -823,9 +846,9 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => - curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv) + curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) case _ if tree.hasAttachment(ClosureBodyValue) => - curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv) + curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) case _ => val res = try @@ -839,7 +862,8 @@ class CheckCaptures extends Recheck, SymTransformer: tree.tpe finally curEnv = saved if tree.isTerm then - checkReachCapsIsolated(res.widen, tree.srcPos) + if !ccConfig.useExistentials then + checkReachCapsIsolated(res.widen, tree.srcPos) if !pt.isBoxedCapturing then markFree(res.boxedCaptureSet, tree.srcPos) res @@ -859,7 +883,10 @@ class CheckCaptures extends Recheck, SymTransformer: } checkNotUniversal(parent) case _ => - if !ccConfig.allowUniversalInBoxed && needsUniversalCheck then + if !ccConfig.useSealed + && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) + && needsUniversalCheck + then checkNotUniversal(tpe) super.recheckFinish(tpe, tree, pt) end recheckFinish @@ -877,6 +904,17 @@ class CheckCaptures extends Recheck, SymTransformer: private inline val debugSuccesses = false + type BoxErrors = mutable.ListBuffer[Message] | Null + + private def boxErrorAddenda(boxErrors: BoxErrors) = + if boxErrors == null then NothingToAdd + else new Addenda: + override def toAdd(using Context): List[String] = + boxErrors.toList.map: msg => + i""" + | + |Note that ${msg.toString}""" + /** Massage `actual` and `expected` types before checking conformance. * Massaging is done by the methods following this one: * - align dependent function types and add outer references in the expected type @@ -886,7 +924,8 @@ class CheckCaptures extends Recheck, SymTransformer: */ override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type = var expected1 = alignDependentFunction(expected, actual.stripCapturing) - val actualBoxed = adapt(actual, expected1, tree.srcPos) + val boxErrors = new mutable.ListBuffer[Message] + val actualBoxed = adapt(actual, expected1, tree.srcPos, boxErrors) //println(i"check conforms $actualBoxed <<< $expected1") if actualBoxed eq actual then @@ -900,7 +939,8 @@ class CheckCaptures extends Recheck, SymTransformer: actualBoxed else capt.println(i"conforms failed for ${tree}: $actual vs $expected") - err.typeMismatch(tree.withType(actualBoxed), expected1, addenda ++ CaptureSet.levelErrors) + err.typeMismatch(tree.withType(actualBoxed), expected1, + addenda ++ CaptureSet.levelErrors ++ boxErrorAddenda(boxErrors)) actual end checkConformsExpr @@ -914,8 +954,7 @@ class CheckCaptures extends Recheck, SymTransformer: case expected @ defn.FunctionOf(args, resultType, isContextual) if defn.isNonRefinedFunction(expected) => actual match - case RefinedType(parent, nme.apply, rinfo: MethodType) - if defn.isFunctionNType(actual) => + case defn.RefinedFunctionOf(rinfo: MethodType) => depFun(args, resultType, isContextual, rinfo.paramNames) case _ => expected case _ => expected @@ -985,37 +1024,52 @@ class CheckCaptures extends Recheck, SymTransformer: * * @param alwaysConst always make capture set variables constant after adaptation */ - def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean)(using Context): Type = - - /** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation - * @param boxed if true we adapt to a boxed expected type - */ - def adaptShape(actualShape: Type, boxed: Boolean): (Type, CaptureSet) = actualShape match - case FunctionOrMethod(aargs, ares) => - val saved = curEnv - curEnv = Env( - curEnv.owner, EnvKind.NestedInOwner, - CaptureSet.Var(curEnv.owner), - if boxed then null else curEnv) - try - val (eargs, eres) = expected.dealias.stripCapturing match - case FunctionOrMethod(eargs, eres) => (eargs, eres) - case _ => (aargs.map(_ => WildcardType), WildcardType) - val aargs1 = aargs.zipWithConserve(eargs): - adaptBoxed(_, _, pos, !covariant, alwaysConst) - val ares1 = adaptBoxed(ares, eres, pos, covariant, alwaysConst) - val resTp = - if (aargs1 eq aargs) && (ares1 eq ares) then actualShape // optimize to avoid redundant matches - else actualShape.derivedFunctionOrMethod(aargs1, ares1) - (resTp, CaptureSet(curEnv.captured.elems)) - finally curEnv = saved - case _ => - (actualShape, CaptureSet()) + def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type = + + def recur(actual: Type, expected: Type, covariant: Boolean): Type = + + /** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation + * @param boxed if true we adapt to a boxed expected type + */ + def adaptShape(actualShape: Type, boxed: Boolean): (Type, CaptureSet) = actualShape match + case FunctionOrMethod(aargs, ares) => + val saved = curEnv + curEnv = Env( + curEnv.owner, EnvKind.NestedInOwner, + CaptureSet.Var(curEnv.owner, level = currentLevel), + if boxed then null else curEnv) + try + val (eargs, eres) = expected.dealias.stripCapturing match + case FunctionOrMethod(eargs, eres) => (eargs, eres) + case _ => (aargs.map(_ => WildcardType), WildcardType) + val aargs1 = aargs.zipWithConserve(eargs): + recur(_, _, !covariant) + val ares1 = recur(ares, eres, covariant) + val resTp = + if (aargs1 eq aargs) && (ares1 eq ares) then actualShape // optimize to avoid redundant matches + else actualShape.derivedFunctionOrMethod(aargs1, ares1) + (resTp, CaptureSet(curEnv.captured.elems)) + finally curEnv = saved + case _ => + (actualShape, CaptureSet()) + end adaptShape - def adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected" + def adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected" + + actual match + case actual @ Existential(_, actualUnpacked) => + return Existential.derivedExistentialType(actual): + recur(actualUnpacked, expected, covariant) + case _ => + expected match + case expected @ Existential(_, expectedUnpacked) => + return recur(actual, expectedUnpacked, covariant) + case _: WildcardType => + return actual + case _ => + + trace(adaptStr, capt, show = true) { - if expected.isInstanceOf[WildcardType] then actual - else trace(adaptStr, recheckr, show = true): // Decompose the actual type into the inner shape type, the capture set and the box status val actualShape = if actual.isFromJavaObject then actual else actual.stripCapturing val actualIsBoxed = actual.isBoxedCapturing @@ -1051,41 +1105,37 @@ class CheckCaptures extends Recheck, SymTransformer: val criticalSet = // the set which is not allowed to have `cap` if covariant then captures // can't box with `cap` else expected.captureSet // can't unbox with `cap` - if criticalSet.isUniversal && expected.isValueType && !ccConfig.allowUniversalInBoxed then + def msg = em"""$actual cannot be box-converted to $expected + |since at least one of their capture sets contains the root capability `cap`""" + def allowUniversalInBoxed = + ccConfig.useSealed + || expected.hasAnnotation(defn.UncheckedCapturesAnnot) + || actual.widen.hasAnnotation(defn.UncheckedCapturesAnnot) + if criticalSet.isUnboxable && expected.isValueType && !allowUniversalInBoxed then // We can't box/unbox the universal capability. Leave `actual` as it is - // so we get an error in checkConforms. This tends to give better error + // so we get an error in checkConforms. Add the error message generated + // from boxing as an addendum. This tends to give better error // messages than disallowing the root capability in `criticalSet`. + if boxErrors != null then boxErrors += msg if ctx.settings.YccDebug.value then println(i"cannot box/unbox $actual vs $expected") actual else - if !ccConfig.allowUniversalInBoxed then + if !allowUniversalInBoxed then // Disallow future addition of `cap` to `criticalSet`. - criticalSet.disallowRootCapability { () => - report.error( - em"""$actual cannot be box-converted to $expected - |since one of their capture sets contains the root capability `cap`""", - pos) - } + criticalSet.disallowRootCapability: () => + report.error(msg, pos) if !insertBox then // unboxing //debugShowEnvs() markFree(criticalSet, pos) adaptedType(!actualIsBoxed) else adaptedType(actualIsBoxed) - end adaptBoxed + } + end recur - /** If actual derives from caps.Capability, yet is not a capturing type itself, - * make its capture set explicit. - */ - private def makeCaptureSetExplicit(actual: Type)(using Context): Type = actual match - case CapturingType(_, _) => actual - case _ if actual.derivesFromCapability => - val cap: CaptureRef = actual match - case ref: CaptureRef if ref.isTracked => ref - case _ => defn.captureRoot.termRef // TODO: skolemize? - CapturingType(actual, cap.singletonCaptureSet) - case _ => actual + recur(actual, expected, covariant) + end adaptBoxed /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C, * improve `T^C` to `T^{a}`, following the VAR rule of CC. @@ -1103,15 +1153,16 @@ class CheckCaptures extends Recheck, SymTransformer: * * @param alwaysConst always make capture set variables constant after adaptation */ - def adapt(actual: Type, expected: Type, pos: SrcPos)(using Context): Type = + def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type = if expected == LhsProto || expected.isSingleton && actual.isSingleton then actual else - val normalized = makeCaptureSetExplicit(actual) - val widened = improveCaptures(normalized.widenDealias, actual) - val adapted = adaptBoxed(widened.withReachCaptures(actual), expected, pos, covariant = true, alwaysConst = false) - if adapted eq widened then normalized - else adapted.showing(i"adapt boxed $actual vs $expected ===> $adapted", capt) + val widened = improveCaptures(actual.widen.dealiasKeepAnnots, actual) + val adapted = adaptBoxed( + widened.withReachCaptures(actual), expected, pos, + covariant = true, alwaysConst = false, boxErrors) + if adapted eq widened then actual + else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt) end adapt /** Check overrides again, taking capture sets into account. @@ -1131,7 +1182,8 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv try curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) - val adapted = adaptBoxed(actual, expected1, srcPos, covariant = true, alwaysConst = true) + val adapted = + adaptBoxed(actual, expected1, srcPos, covariant = true, alwaysConst = true, null) actual match case _: MethodType => // We remove the capture set resulted from box adaptation for method types, @@ -1156,6 +1208,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) + private val completed = new mutable.HashSet[Symbol] + + override def skipRecheck(sym: Symbol)(using Context): Boolean = + completed.contains(sym) + /** Check a ValDef or DefDef as an action performed in a completer. Since * these checks can appear out of order, we need to firsty create the correct * environment for checking the definition. @@ -1176,7 +1233,8 @@ class CheckCaptures extends Recheck, SymTransformer: case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) curEnv = restoreEnvFor(sym.owner) capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") - recheckDef(tree, sym) + try recheckDef(tree, sym) + finally completed += sym finally curEnv = saved @@ -1284,7 +1342,7 @@ class CheckCaptures extends Recheck, SymTransformer: case ref: TermParamRef if !allowed.contains(ref) && !seen.contains(ref) => seen += ref - if ref.underlying.isRef(defn.Caps_Capability) then + if ref.isMaxCapability then report.error(i"escaping local reference $ref", tree.srcPos) else val widened = ref.captureSetOfInfo diff --git a/compiler/src/dotty/tools/dotc/cc/Existential.scala b/compiler/src/dotty/tools/dotc/cc/Existential.scala new file mode 100644 index 000000000000..732510789e28 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/Existential.scala @@ -0,0 +1,386 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* +import CaptureSet.IdempotentCaptRefMap +import StdNames.nme +import ast.tpd.* +import Decorators.* +import typer.ErrorReporting.errorType +import Names.TermName +import NameKinds.ExistentialBinderName +import NameOps.isImpureFunction +import reporting.Message + +/** + +Handling existentials in CC: + + - We generally use existentials only in function and method result types + - All occurrences of an EX-bound variable appear co-variantly in the bound type + +In Setup: + + - Convert occurrences of `cap` in function results to existentials. Precise rules below. + - Conversions are done in two places: + + + As part of mapping from local types of parameters and results to infos of methods. + The local types just use `cap`, whereas the result type in the info uses EX-bound variables. + + When converting functions or methods appearing in explicitly declared types. + Here again, we only replace cap's in fucntion results. + + - Conversion is done with a BiTypeMap in `Existential.mapCap`. + +In reckeckApply and recheckTypeApply: + + - If an EX is toplevel in the result type, replace its bound variable + occurrences with `cap`. + +Level checking and avoidance: + + - Environments, capture refs, and capture set variables carry levels + + + levels start at 0 + + The level of a block or template statement sequence is one higher than the level of + its environment + + The level of a TermRef is the level of the environment where its symbol is defined. + + The level of a ThisType is the level of the statements of the class to which it beloongs. + + The level of a TermParamRef is currently -1 (i.e. TermParamRefs are not yet checked using this system) + + The level of a capture set variable is the level of the environment where it is created. + + - Variables also carry info whether they accept `cap` or not. Variables introduced under a box + don't, the others do. + + - Capture set variables do not accept elements of level higher than the variable's level + + - We use avoidance to heal such cases: If the level-incorrect ref appears + + covariantly: widen to underlying capture set, reject if that is cap and the variable does not allow it + + contravariantly: narrow to {} + + invarianty: reject with error + +In cv-computation (markFree): + + - Reach capabilities x* of a parameter x cannot appear in the capture set of + the owning method. They have to be widened to dcs(x), or, where this is not + possible, it's an error. + +In box adaptation: + + - Check that existential variables are not boxed or unboxed. + +Subtype rules + + - new alphabet: existentially bound variables `a`. + - they can be stored in environments Gamma. + - they are alpha-renable, usual hygiene conditions apply + + Gamma |- EX a.T <: U + if Gamma, a |- T <: U + + Gamma |- T <: EX a.U + if exists capture set C consisting of capture refs and ex-bound variables + bound in Gamma such that Gamma |- T <: [a := C]U + +Representation: + + EX a.T[a] is represented as a dependent function type + + (a: Exists) => T[a]] + + where Exists is defined in caps like this: + + sealed trait Exists extends Capability + + The defn.RefinedFunctionOf extractor will exclude existential types from + its results, so only normal refined functions match. + + Let `boundvar(ex)` be the TermParamRef defined by the existential type `ex`. + +Subtype checking algorithm, general scheme: + + Maintain two structures in TypeComparer: + + openExistentials: List[TermParamRef] + assocExistentials: Map[TermParamRef, List[TermParamRef]] + + `openExistentials` corresponds to the list of existential variables stored in the environment. + `assocExistentials` maps existential variables bound by existentials appearing on the right + to the value of `openExistentials` at the time when the existential on the right was dropped. + +Subtype checking algorithm, steps to add for tp1 <:< tp2: + + If tp1 is an existential EX a.tp1a: + + val saved = openExistentials + openExistentials = boundvar(tp1) :: openExistentials + try tp1a <:< tp2 + finally openExistentials = saved + + If tp2 is an existential EX a.tp2a: + + val saved = assocExistentials + assocExistentials = assocExistentials + (boundvar(tp2) -> openExistentials) + try tp1 <:< tp2a + finally assocExistentials = saved + + If tp2 is an existentially bound variable: + assocExistentials(tp2).isDefined + && (assocExistentials(tp2).contains(tp1) || tp1 is not existentially bound) + +Subtype checking algorithm, comparing two capture sets CS1 <:< CS2: + + We need to map the (possibly to-be-added) existentials in CS1 to existentials + in CS2 so that we can compare them. We use `assocExistentals` for that: + To map an EX-variable V1 in CS1, pick the last (i.e. outermost, leading to the smallest + type) EX-variable in `assocExistentials` that has V1 in its possible instances. + To go the other way (and therby produce a BiTypeMap), map an EX-variable + V2 in CS2 to the first (i.e. innermost) EX-variable it can be instantiated to. + If either direction is not defined, we choose a special "bad-existetal" value + that represents and out-of-scope existential. This leads to failure + of the comparison. + +Existential source syntax: + + Existential types are ususally not written in source, since we still allow the `^` + syntax that can express most of them more concesely (see below for translation rules). + But we should also allow to write existential types explicity, even if it ends up mainly + for debugging. To express them, we use the encoding with `Exists`, so a typical + expression of an existential would be + + (x: Exists) => A ->{x} B + + Existential types can only at the top level of the result type + of a function or method. + +Restrictions on Existential Types: (to be implemented if we want to +keep the source syntax for users). + + - An existential capture ref must be the only member of its set. This is + intended to model the idea that existential variables effectibely range + over capture sets, not capture references. But so far our calculus + and implementation does not yet acoommodate first-class capture sets. + - Existential capture refs must appear co-variantly in their bound type + + So the following would all be illegal: + + EX x.C^{x, io} // error: multiple members + EX x.() => EX y.C^{x, y} // error: multiple members + EX x.C^{x} ->{x} D // error: contra-variant occurrence + EX x.Set[C^{x}] // error: invariant occurrence + +Expansion of ^: + + We expand all occurrences of `cap` in the result types of functions or methods + to existentially quantified types. Nested scopes are expanded before outer ones. + + The expansion algorithm is then defined as follows: + + 1. In a result type, replace every occurrence of ^ with a fresh existentially + bound variable and quantify over all variables such introduced. + + 2. After this step, type aliases are expanded. If aliases have aliases in arguments, + the outer alias is expanded before the aliases in the arguments. Each time an alias + is expanded that reveals a `^`, apply step (1). + + 3. The algorithm ends when no more alieases remain to be expanded. + + Examples: + + - `A => B` is an alias type that expands to `(A -> B)^`, therefore + `() -> A => B` expands to `() -> EX c. A ->{c} B`. + + - `() => Iterator[A => B]` expands to `() => EX c. Iterator[A ->{c} B]` + + - `A -> B^` expands to `A -> EX c.B^{c}`. + + - If we define `type Fun[T] = A -> T`, then `() -> Fun[B^]` expands to `() -> EX c.Fun[B^{c}]`, which + dealiases to `() -> EX c.A -> B^{c}`. + + - If we define + + type F = A -> Fun[B^] + + then the type alias expands to + + type F = A -> EX c.A -> B^{c} +*/ +object Existential: + + type Carrier = RefinedType + + def unapply(tp: Carrier)(using Context): Option[(TermParamRef, Type)] = + tp.refinedInfo match + case mt: MethodType + if isExistentialMethod(mt) && defn.isNonRefinedFunction(tp.parent) => + Some(mt.paramRefs.head, mt.resultType) + case _ => None + + /** Create method type in the refinement of an existential type */ + private def exMethodType(using Context)( + mk: TermParamRef => Type, + boundName: TermName = ExistentialBinderName.fresh() + ): MethodType = + MethodType(boundName :: Nil)( + mt => defn.Caps_Exists.typeRef :: Nil, + mt => mk(mt.paramRefs.head)) + + /** Create existential */ + def apply(mk: TermParamRef => Type)(using Context): Type = + exMethodType(mk).toFunctionType(alwaysDependent = true) + + /** Create existential if bound variable appears in result of `mk` */ + def wrap(mk: TermParamRef => Type)(using Context): Type = + val mt = exMethodType(mk) + if mt.isResultDependent then mt.toFunctionType() else mt.resType + + extension (tp: Carrier) + def derivedExistentialType(core: Type)(using Context): Type = tp match + case Existential(boundVar, unpacked) => + if core eq unpacked then tp + else apply(bv => core.substParam(boundVar, bv)) + case _ => + core + + /** Map top-level existentials to `cap`. Do the same for existentials + * in function results if all preceding arguments are known to be always pure. + */ + def toCap(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match + case Existential(boundVar, unpacked) => + val transformed = unpacked.substParam(boundVar, defn.captureRoot.termRef) + transformed match + case FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + transformed.derivedFunctionOrMethod(args, toCap(res)) + case _ => + transformed + case tp1 @ CapturingType(parent, refs) => + tp1.derivedCapturingType(toCap(parent), refs) + case tp1 @ AnnotatedType(parent, ann) => + tp1.derivedAnnotatedType(toCap(parent), ann) + case _ => tp + + /** Map existentials at the top-level and in all nested result types to `cap` + */ + def toCapDeeply(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match + case Existential(boundVar, unpacked) => + toCapDeeply(unpacked.substParam(boundVar, defn.captureRoot.termRef)) + case tp1 @ FunctionOrMethod(args, res) => + val tp2 = tp1.derivedFunctionOrMethod(args, toCapDeeply(res)) + if tp2 ne tp1 then tp2 else tp + case tp1 @ CapturingType(parent, refs) => + tp1.derivedCapturingType(toCapDeeply(parent), refs) + case tp1 @ AnnotatedType(parent, ann) => + tp1.derivedAnnotatedType(toCapDeeply(parent), ann) + case _ => tp + + /** Knowing that `tp` is a function type, is an alias to a function other + * than `=>`? + */ + private def isAliasFun(tp: Type)(using Context) = tp match + case AppliedType(tycon, _) => !defn.isFunctionSymbol(tycon.typeSymbol) + case _ => false + + /** Replace all occurrences of `cap` in parts of this type by an existentially bound + * variable. If there are such occurrences, or there might be in the future due to embedded + * capture set variables, create an existential with the variable wrapping the type. + * Stop at function or method types since these have been mapped before. + */ + def mapCap(tp: Type, fail: Message => Unit)(using Context): Type = + var needsWrap = false + + abstract class CapMap extends BiTypeMap: + override def mapOver(t: Type): Type = t match + case t @ FunctionOrMethod(args, res) if variance > 0 && !isAliasFun(t) => + t // `t` should be mapped in this case by a different call to `mapCap`. + case Existential(_, _) => + t + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + super.mapOver(t) + + class Wrap(boundVar: TermParamRef) extends CapMap: + def apply(t: Type) = t match + case t: TermRef if t.isRootCapability => + if variance > 0 then + needsWrap = true + boundVar + else + if variance == 0 then + fail(em"""$tp captures the root capability `cap` in invariant position""") + // we accept variance < 0, and leave the cap as it is + super.mapOver(t) + case t @ CapturingType(parent, refs: CaptureSet.Var) => + if variance > 0 then needsWrap = true + super.mapOver(t) + case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction => + if variance > 0 then + needsWrap = true + super.mapOver: + defn.FunctionNOf(args, res, contextual).capturing(boundVar.singletonCaptureSet) + else mapOver(t) + case _ => + mapOver(t) + //.showing(i"mapcap $t = $result") + + lazy val inverse = new BiTypeMap: + def apply(t: Type) = t match + case t: TermParamRef if t eq boundVar => defn.captureRoot.termRef + case _ => mapOver(t) + def inverse = Wrap.this + override def toString = "Wrap.inverse" + end Wrap + + if ccConfig.useExistentials then + val wrapped = apply(Wrap(_)(tp)) + if needsWrap then wrapped else tp + else tp + end mapCap + + def mapCapInResults(fail: Message => Unit)(using Context): TypeMap = new: + + def mapFunOrMethod(tp: Type, args: List[Type], res: Type): Type = + val args1 = atVariance(-variance)(args.map(this)) + val res1 = res match + case res: MethodType => mapFunOrMethod(res, res.paramInfos, res.resType) + case res: PolyType => mapFunOrMethod(res, Nil, res.resType) // TODO: Also map bounds of PolyTypes + case _ => mapCap(apply(res), fail) + //.showing(i"map cap res $res / ${apply(res)} of $tp = $result") + tp.derivedFunctionOrMethod(args1, res1) + + def apply(t: Type): Type = t match + case FunctionOrMethod(args, res) if variance > 0 && !isAliasFun(t) => + mapFunOrMethod(t, args, res) + case CapturingType(parent, refs) => + t.derivedCapturingType(this(parent), refs) + case Existential(_, _) => + t + case t: (LazyRef | TypeVar) => + mapConserveSuper(t) + case _ => + mapOver(t) + end mapCapInResults + + /** Is `mt` a method represnting an existential type when used in a refinement? */ + def isExistentialMethod(mt: TermLambda)(using Context): Boolean = mt.paramInfos match + case (info: TypeRef) :: rest => info.symbol == defn.Caps_Exists && rest.isEmpty + case _ => false + + /** Is `ref` this an existentially bound variable? */ + def isExistentialVar(ref: CaptureRef)(using Context) = ref match + case ref: TermParamRef => isExistentialMethod(ref.binder) + case _ => false + + /** An value signalling an out-of-scope existential that should + * lead to a compare failure. + */ + def badExistential(using Context): TermParamRef = + exMethodType(identity, nme.OOS_EXISTENTIAL).paramRefs.head + + def isBadExistential(ref: CaptureRef) = ref match + case ref: TermParamRef => ref.paramName == nme.OOS_EXISTENTIAL + case _ => false + +end Existential diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 0175d40c186c..cb74e2c71e73 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -14,8 +14,10 @@ import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded import util.Property +import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable +import CCState.* /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -67,12 +69,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => foldOver(x, tp) def apply(tp: Type): Boolean = apply(false, tp) - if symd.isAllOf(PrivateParamAccessor) + if symd.symbol.isRefiningParamAccessor + && symd.is(Private) && symd.owner.is(CaptureChecked) - && !symd.hasAnnotation(defn.ConstructorOnlyAnnot) && containsCovarRetains(symd.symbol.originDenotation.info) then symd.flags &~ Private else symd.flags + end newFlagsFor def isPreCC(sym: Symbol)(using Context): Boolean = sym.isTerm && sym.maybeOwner.isClass @@ -183,13 +186,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case cls: ClassSymbol if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) => cls.paramGetters.foldLeft(tp) { (core, getter) => - if atPhase(thisPhase.next)(getter.termRef.isTracked) + if atPhase(thisPhase.next)(getter.hasTrackedParts) + && getter.isRefiningParamAccessor && !getter.is(Tracked) then val getterType = mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias RefinedType(core, getter.name, - CapturingType(getterType, CaptureSet.RefiningVar(ctx.owner))) + CapturingType(getterType, new CaptureSet.RefiningVar(ctx.owner))) .showing(i"add capture refinement $tp --> $result", capt) else core @@ -237,6 +241,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val rinfo1 = apply(rinfo) if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true) else tp + case Existential(_, unpacked) => + // drop the existential, the bound variables will be replaced by capture set variables + apply(unpacked) case tp: MethodType => tp.derivedLambdaType( paramInfos = mapNested(tp.paramInfos), @@ -252,11 +259,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end apply end mapInferred - mapInferred(refine = true)(tp) + try + val tp1 = mapInferred(refine = true)(tp) + val tp2 = Existential.mapCapInResults(_ => assert(false))(tp1) + if tp2 ne tp then capt.println(i"expanded implicit in ${ctx.owner}: $tp --> $tp1 --> $tp2") + tp2 + catch case ex: AssertionError => + println(i"error while mapping inferred $tp") + throw ex end transformInferredType private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = - val expandAliases = new DeepTypeMap: + val toCapturing = new DeepTypeMap: override def toString = "expand aliases" /** Expand $throws aliases. This is hard-coded here since $throws aliases in stdlib @@ -284,8 +298,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(fntpe, cs, boxed = false) else fntpe - private def recur(t: Type): Type = normalizeCaptures(mapOver(t)) - def apply(t: Type) = t match case t @ CapturingType(parent, refs) => @@ -300,18 +312,20 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: t.derivedAnnotatedType(parent1, ann) case throwsAlias(res, exc) => this(expandThrowsAlias(res, exc, Nil)) - case t: LazyRef => - val t1 = this(t.ref) - if t1 ne t.ref then t1 else t - case t: TypeVar => - this(t.underlying) case t => - recur(t) - end expandAliases - - val tp1 = expandAliases(tp) // TODO: Do we still need to follow aliases? - if tp1 ne tp then capt.println(i"expanded in ${ctx.owner}: $tp --> $tp1") - tp1 + // Map references to capability classes C to C^ + if t.derivesFromCapability && !t.isSingleton && t.typeSymbol != defn.Caps_Exists + then CapturingType(t, CaptureSet.universal, boxed = false) + else normalizeCaptures(mapOver(t)) + end toCapturing + + def fail(msg: Message) = + for tree <- tptToCheck do report.error(msg, tree.srcPos) + + val tp1 = toCapturing(tp) + val tp2 = Existential.mapCapInResults(fail)(tp1) + if tp2 ne tp then capt.println(i"expanded explicit in ${ctx.owner}: $tp --> $tp1 --> $tp2") + tp2 end transformExplicitType /** Transform type of type tree, and remember the transformed type as the type the tree */ @@ -367,7 +381,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: sym.updateInfo(thisPhase, info, newFlagsFor(sym)) toBeUpdated -= sym sym.namedType match - case ref: CaptureRef => ref.invalidateCaches() // TODO: needed? + case ref: CaptureRef if ref.isTrackableRef => ref.invalidateCaches() // TODO: needed? case _ => extension (sym: Symbol) def nextInfo(using Context): Type = @@ -378,8 +392,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = try transformTT(tpt, - boxed = !ccConfig.allowUniversalInBoxed && sym.is(Mutable, butNot = Method), - // types of mutable variables are boxed in pre 3.3 codee + boxed = + sym.is(Mutable, butNot = Method) + && !ccConfig.useSealed + && !sym.hasAnnotation(defn.UncheckedCapturesAnnot), + // types of mutable variables are boxed in pre 3.3 code exact = sym.allOverriddenSymbols.hasNext, // types of symbols that override a parent don't get a capture set TODO drop ) @@ -389,7 +406,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val addDescription = new TypeTraverser: def traverse(tp: Type) = tp match case tp @ CapturingType(parent, refs) => - if !refs.isConst then refs.withDescription(i"of $sym") + if !refs.isConst && refs.description.isEmpty then + refs.withDescription(i"of $sym") traverse(parent) case _ => traverseChildren(tp) @@ -402,14 +420,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if isExcluded(meth) then return - inContext(ctx.withOwner(meth)): - paramss.foreach(traverse) - transformResultType(tpt, meth) - traverse(tree.rhs) - //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") + meth.recordLevel() + inNestedLevel: + inContext(ctx.withOwner(meth)): + paramss.foreach(traverse) + transformResultType(tpt, meth) + traverse(tree.rhs) + //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") case tree @ ValDef(_, tpt: TypeTree, _) => val sym = tree.symbol + sym.recordLevel() val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) @@ -426,13 +447,19 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed case tree: TypeDef if tree.symbol.isClass => - inContext(ctx.withOwner(tree.symbol)): - traverseChildren(tree) + val sym = tree.symbol + sym.recordLevel() + inNestedLevelUnless(sym.is(Module)): + inContext(ctx.withOwner(sym)) + traverseChildren(tree) case tree @ SeqLiteral(elems, tpt: TypeTree) => traverse(elems) tpt.rememberType(box(transformInferredType(tpt.tpe))) + case tree: Block => + inNestedLevel(traverseChildren(tree)) + case _ => traverseChildren(tree) postProcess(tree) @@ -463,11 +490,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else tree.tpt.knownType def paramSignatureChanges = tree.match - case tree: DefDef => tree.paramss.nestedExists: - case param: ValDef => param.tpt.hasRememberedType - case param: TypeDef => param.rhs.hasRememberedType + case tree: DefDef => + tree.paramss.nestedExists: + case param: ValDef => param.tpt.hasRememberedType + case param: TypeDef => param.rhs.hasRememberedType case _ => false + // A symbol's signature changes if some of its parameter types or its result type + // have a new type installed here (meaning hasRememberedType is true) def signatureChanges = tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges @@ -502,7 +532,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else SubstParams(prevPsymss, prevLambdas)(resType) if sym.exists && signatureChanges then - val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) + val newInfo = + Existential.mapCapInResults(report.error(_, tree.srcPos)): + integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) .showing(i"update info $sym: ${sym.info} = $result", capt) if newInfo ne sym.info then val updatedInfo = @@ -531,36 +563,37 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: TypeDef => tree.symbol match case cls: ClassSymbol => - val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - def innerModule = cls.is(ModuleClass) && !cls.isStatic - val selfInfo1 = - if (selfInfo ne NoType) && !innerModule then - // if selfInfo is explicitly given then use that one, except if - // self info applies to non-static modules, these still need to be inferred - selfInfo - else if cls.isPureClass then - // is cls is known to be pure, nothing needs to be added to self type - selfInfo - else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitSelfType then - // assume {cap} for completely unconstrained self types of publicly extensible classes - CapturingType(cinfo.selfType, CaptureSet.universal) - else - // Infer the self type for the rest, which is all classes without explicit - // self types (to which we also add nested module classes), provided they are - // neither pure, nor are publicily extensible with an unconstrained self type. - CapturingType(cinfo.selfType, CaptureSet.Var(cls)) - val ps1 = inContext(ctx.withOwner(cls)): - ps.mapConserve(transformExplicitType(_)) - if (selfInfo1 ne selfInfo) || (ps1 ne ps) then - val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1) - updateInfo(cls, newInfo) - capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo") - cls.thisType.asInstanceOf[ThisType].invalidateCaches() - if cls.is(ModuleClass) then - // if it's a module, the capture set of the module reference is the capture set of the self type - val modul = cls.sourceModule - updateInfo(modul, CapturingType(modul.info, selfInfo1.asInstanceOf[Type].captureSet)) - modul.termRef.invalidateCaches() + inNestedLevelUnless(cls.is(Module)): + val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo + def innerModule = cls.is(ModuleClass) && !cls.isStatic + val selfInfo1 = + if (selfInfo ne NoType) && !innerModule then + // if selfInfo is explicitly given then use that one, except if + // self info applies to non-static modules, these still need to be inferred + selfInfo + else if cls.isPureClass then + // is cls is known to be pure, nothing needs to be added to self type + selfInfo + else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitSelfType then + // assume {cap} for completely unconstrained self types of publicly extensible classes + CapturingType(cinfo.selfType, CaptureSet.universal) + else + // Infer the self type for the rest, which is all classes without explicit + // self types (to which we also add nested module classes), provided they are + // neither pure, nor are publicily extensible with an unconstrained self type. + CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = currentLevel)) + val ps1 = inContext(ctx.withOwner(cls)): + ps.mapConserve(transformExplicitType(_)) + if (selfInfo1 ne selfInfo) || (ps1 ne ps) then + val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1) + updateInfo(cls, newInfo) + capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo") + cls.thisType.asInstanceOf[ThisType].invalidateCaches() + if cls.is(ModuleClass) then + // if it's a module, the capture set of the module reference is the capture set of the self type + val modul = cls.sourceModule + updateInfo(modul, CapturingType(modul.info, selfInfo1.asInstanceOf[Type].captureSet)) + modul.termRef.invalidateCaches() case _ => case _ => end postProcess @@ -575,10 +608,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: !refs.isEmpty case tp: (TypeRef | AppliedType) => val sym = tp.typeSymbol - if sym.isClass then - !sym.isPureClass - else - sym != defn.Caps_Capability && instanceCanBeImpure(tp.superType) + if sym.isClass then !sym.isPureClass + else instanceCanBeImpure(tp.superType) case tp: (RefinedOrRecType | MatchType) => instanceCanBeImpure(tp.underlying) case tp: AndType => @@ -672,11 +703,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Add a capture set variable to `tp` if necessary, or maybe pull out * an embedded capture set variable from a part of `tp`. */ - def addVar(tp: Type, owner: Symbol)(using Context): Type = + private def addVar(tp: Type, owner: Symbol)(using Context): Type = decorate(tp, addedSet = _.dealias.match - case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems) - case _ => CaptureSet.Var(owner)) + case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems, level = currentLevel) + case _ => CaptureSet.Var(owner, level = currentLevel)) def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) @@ -698,26 +729,28 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: var retained = ann.retainedElems.toArray for i <- 0 until retained.length do val refTree = retained(i) - val ref = refTree.toCaptureRef - - def pos = - if refTree.span.exists then refTree.srcPos - else if ann.span.exists then ann.srcPos - else tpt.srcPos - - def check(others: CaptureSet, dom: Type | CaptureSet): Unit = - if others.accountsFor(ref) then - report.warning(em"redundant capture: $dom already accounts for $ref", pos) - - if ref.captureSetOfInfo.elems.isEmpty then - report.error(em"$ref cannot be tracked since its capture set is empty", pos) - if parent.captureSet ne defn.expandedUniversalSet then + for ref <- refTree.toCaptureRefs do + def pos = + if refTree.span.exists then refTree.srcPos + else if ann.span.exists then ann.srcPos + else tpt.srcPos + + def check(others: CaptureSet, dom: Type | CaptureSet): Unit = + if others.accountsFor(ref) then + report.warning(em"redundant capture: $dom already accounts for $ref", pos) + + if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then + report.error(em"$ref cannot be tracked since its capture set is empty", pos) check(parent.captureSet, parent) - val others = - for j <- 0 until retained.length if j != i yield retained(j).toCaptureRef - val remaining = CaptureSet(others*) - check(remaining, remaining) + val others = + for + j <- 0 until retained.length if j != i + r <- retained(j).toCaptureRefs + yield r + val remaining = CaptureSet(others*) + check(remaining, remaining) + end for end for end checkWellformedPost diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index ee8ed4b215d7..e8a234ff821f 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -229,7 +229,7 @@ object Config { inline val reuseSymDenotations = true /** If `checkLevelsOnConstraints` is true, check levels of type variables - * and create fresh ones as needed when bounds are first entered intot he constraint. + * and create fresh ones as needed when bounds are first entered into the constraint. * If `checkLevelsOnInstantiation` is true, allow level-incorrect constraints but * fix levels on type variable instantiation. */ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 1f0a673f90b1..e8a853469f6f 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} +import cc.{CaptureSet, RetainingType, Existential} import ast.tpd.ref import scala.annotation.tailrec @@ -991,14 +991,16 @@ class Definitions { @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap") - @tu lazy val Caps_Capability: ClassSymbol = requiredClass("scala.caps.Capability") + @tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability") + @tu lazy val Caps_CapSet = requiredClass("scala.caps.CapSet") @tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability") + @tu lazy val Caps_capsOf: TermSymbol = CapsModule.requiredMethod("capsOf") + @tu lazy val Caps_Exists = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox") @tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox") @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") - @tu lazy val expandedUniversalSet: CaptureSet = CaptureSet(captureRoot.termRef) @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") @@ -1052,6 +1054,7 @@ class Definitions { @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") + @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @@ -1160,6 +1163,8 @@ class Definitions { if mt.hasErasedParams then RefinedType(PolyFunctionClass.typeRef, nme.apply, mt) else FunctionNOf(args, resultType, isContextual) + // Unlike PolyFunctionOf and RefinedFunctionOf this extractor follows aliases. + // Can we do without? Same for FunctionNOf and isFunctionNType. def unapply(ft: Type)(using Context): Option[(List[Type], Type, Boolean)] = { ft match case PolyFunctionOf(mt: MethodType) => @@ -1189,11 +1194,17 @@ class Definitions { /** Matches a refined `PolyFunction`/`FunctionN[...]`/`ContextFunctionN[...]`. * Extracts the method type type and apply info. + * Will NOT math an existential type encoded as a dependent function. */ def unapply(tpe: RefinedType)(using Context): Option[MethodOrPoly] = tpe.refinedInfo match - case mt: MethodOrPoly - if tpe.refinedName == nme.apply && isFunctionType(tpe.parent) => Some(mt) + case mt: MethodType + if tpe.refinedName == nme.apply + && isFunctionType(tpe.parent) + && !Existential.isExistentialMethod(mt) => Some(mt) + case mt: PolyType + if tpe.refinedName == nme.apply + && isFunctionType(tpe.parent) => Some(mt) case _ => None end RefinedFunctionOf diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 74d440562824..e9575c7d6c4a 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -325,6 +325,7 @@ object NameKinds { val TailLocalName: UniqueNameKind = new UniqueNameKind("$tailLocal") val TailTempName: UniqueNameKind = new UniqueNameKind("$tmp") val ExceptionBinderName: UniqueNameKind = new UniqueNameKind("ex") + val ExistentialBinderName: UniqueNameKind = new UniqueNameKind("ex$") val SkolemName: UniqueNameKind = new UniqueNameKind("?") val SuperArgName: UniqueNameKind = new UniqueNameKind("$superArg$") val DocArtifactName: UniqueNameKind = new UniqueNameKind("$doc") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 3753d1688399..d3e198a7e7a7 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -294,6 +294,7 @@ object StdNames { val EVT2U: N = "evt2u$" val EQEQ_LOCAL_VAR: N = "eqEqTemp$" val LAZY_FIELD_OFFSET: N = "OFFSET$" + val OOS_EXISTENTIAL: N = "" val OUTER: N = "$outer" val REFINE_CLASS: N = "" val ROOTPKG: N = "_root_" @@ -357,6 +358,7 @@ object StdNames { val AppliedTypeTree: N = "AppliedTypeTree" val ArrayAnnotArg: N = "ArrayAnnotArg" val CAP: N = "CAP" + val CapSet: N = "CapSet" val Constant: N = "Constant" val ConstantType: N = "ConstantType" val Eql: N = "Eql" @@ -440,8 +442,8 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" - val capIn: N = "capIn" val caps: N = "caps" + val capsOf: N = "capsOf" val captureChecking: N = "captureChecking" val checkInitialized: N = "checkInitialized" val classOf: N = "classOf" diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index da0ecac47b7d..1c5d1941c524 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -846,7 +846,8 @@ object Symbols extends SymUtils { /** Map given symbols, subjecting their attributes to the mappings * defined in the given TreeTypeMap `ttmap`. * Cross symbol references are brought over from originals to copies. - * Do not copy any symbols if all attributes of all symbols stay the same. + * Do not copy any symbols if all attributes of all symbols stay the same + * and mapAlways is false. */ def mapSymbols(originals: List[Symbol], ttmap: TreeTypeMap, mapAlways: Boolean = false)(using Context): List[Symbol] = if (originals.forall(sym => diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 140b42e0e9a9..6a17a888772c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -11,7 +11,7 @@ import collection.mutable import util.{Stats, NoSourcePosition, EqHashMap} import config.Config import config.Feature.{betterMatchTypeExtractorsEnabled, migrateTo3, sourceVersion} -import config.Printers.{subtyping, gadts, matchTypes, noPrinter} +import config.Printers.{subtyping, gadts, matchTypes, capt, noPrinter} import config.SourceVersion import TypeErasure.{erasedLub, erasedGlb} import TypeApplications.* @@ -46,6 +46,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling monitored = false GADTused = false opaquesUsed = false + openedExistentials = Nil + assocExistentials = Nil recCount = 0 needsGc = false if Config.checkTypeComparerReset then checkReset() @@ -64,6 +66,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** Indicates whether the subtype check used opaque types */ private var opaquesUsed: Boolean = false + /** In capture checking: The existential types that are open because they + * appear in an existential type on the left in an enclosing comparison. + */ + private var openedExistentials: List[TermParamRef] = Nil + + /** In capture checking: A map from existential types that are appear + * in an existential type on the right in an enclosing comparison. + * Each existential gets mapped to the opened existentials to which it + * may resolve at this point. + */ + private var assocExistentials: ExAssoc = Nil + private var myInstance: TypeComparer = this def currentInstance: TypeComparer = myInstance @@ -325,14 +339,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling isSubPrefix(tp1.prefix, tp2.prefix) || thirdTryNamed(tp2) else - ( (tp1.name eq tp2.name) + (tp1.name eq tp2.name) && !sym1.is(Private) && tp2.isPrefixDependentMemberRef && isSubPrefix(tp1.prefix, tp2.prefix) && tp1.signature == tp2.signature && !(sym1.isClass && sym2.isClass) // class types don't subtype each other - ) || - thirdTryNamed(tp2) + || thirdTryNamed(tp2) case _ => secondTry end compareNamed @@ -344,7 +357,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp2: ProtoType => isMatchedByProto(tp2, tp1) case tp2: BoundType => - tp2 == tp1 || secondTry + tp2 == tp1 + || secondTry case tp2: TypeVar => recur(tp1, typeVarInstance(tp2)) case tp2: WildcardType => @@ -546,6 +560,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if reduced.exists then recur(reduced, tp2) && recordGadtUsageIf { MatchType.thatReducesUsingGadt(tp1) } else thirdTry + case Existential(boundVar, tp1unpacked) => + compareExistentialLeft(boundVar, tp1unpacked, tp2) case _: FlexType => true case _ => @@ -627,6 +643,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling thirdTryNamed(tp2) case tp2: TypeParamRef => compareTypeParamRef(tp2) + case Existential(boundVar, tp2unpacked) => + compareExistentialRight(tp1, boundVar, tp2unpacked) case tp2: RefinedType => def compareRefinedSlow: Boolean = val name2 = tp2.refinedName @@ -1419,20 +1437,21 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling canConstrain(param2) && canInstantiate(param2) || compareLower(bounds(param2), tyconIsTypeRef = false) case tycon2: TypeRef => - isMatchingApply(tp1) || - byGadtBounds || - defn.isCompiletimeAppliedType(tycon2.symbol) && compareCompiletimeAppliedType(tp2, tp1, fromBelow = true) || { - tycon2.info match { - case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) - case info2: ClassInfo => - tycon2.name.startsWith("Tuple") && - defn.isTupleNType(tp2) && recur(tp1, tp2.toNestedPairs) || - tryBaseType(info2.cls) - case _ => - fourthTry - } - } || tryLiftedToThis2 + isMatchingApply(tp1) + || byGadtBounds + || defn.isCompiletimeAppliedType(tycon2.symbol) + && compareCompiletimeAppliedType(tp2, tp1, fromBelow = true) + || tycon2.info.match + case info2: TypeBounds => + compareLower(info2, tyconIsTypeRef = true) + case info2: ClassInfo => + tycon2.name.startsWith("Tuple") + && defn.isTupleNType(tp2) + && recur(tp1, tp2.toNestedPairs) + || tryBaseType(info2.cls) + case _ => + fourthTry + || tryLiftedToThis2 case tv: TypeVar => if tv.isInstantiated then @@ -1469,12 +1488,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling inFrozenGadt { isSubType(bounds1.hi.applyIfParameterized(args1), tp2, approx.addLow) } } && recordGadtUsageIf(true) - !sym.isClass && { defn.isCompiletimeAppliedType(sym) && compareCompiletimeAppliedType(tp1, tp2, fromBelow = false) || { recur(tp1.superTypeNormalized, tp2) && recordGadtUsageIf(MatchType.thatReducesUsingGadt(tp1)) } || tryLiftedToThis1 - } || byGadtBounds + } + || byGadtBounds case tycon1: TypeProxy => recur(tp1.superTypeNormalized, tp2) case _ => @@ -2767,7 +2786,109 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling false } + // ----------- Capture checking ----------------------------------------------- + + /** A type associating instantiatable existentials on the right of a comparison + * with the existentials they can be instantiated with. + */ + type ExAssoc = List[(TermParamRef, List[TermParamRef])] + + private def compareExistentialLeft(boundVar: TermParamRef, tp1unpacked: Type, tp2: Type)(using Context): Boolean = + val saved = openedExistentials + try + openedExistentials = boundVar :: openedExistentials + recur(tp1unpacked, tp2) + finally + openedExistentials = saved + + private def compareExistentialRight(tp1: Type, boundVar: TermParamRef, tp2unpacked: Type)(using Context): Boolean = + val saved = assocExistentials + try + assocExistentials = (boundVar, openedExistentials) :: assocExistentials + recur(tp1, tp2unpacked) + finally + assocExistentials = saved + + /** Is `tp1` an existential var that subsumes `tp2`? This is the case if `tp1` is + * instantiatable (i.e. it's a key in `assocExistentials`) and one of the + * following is true: + * - `tp2` is not an existential var, + * - `tp1` is associated via `assocExistentials` with `tp2`, + * - `tp2` appears as key in `assocExistentials` further out than `tp1`. + * The third condition allows to instantiate c2 to c1 in + * EX c1: A -> Ex c2. B + */ + def subsumesExistentially(tp1: TermParamRef, tp2: CaptureRef)(using Context): Boolean = + def canInstantiateWith(assoc: ExAssoc): Boolean = assoc match + case (bv, bvs) :: assoc1 => + if bv == tp1 then + !Existential.isExistentialVar(tp2) + || bvs.contains(tp2) + || assoc1.exists(_._1 == tp2) + else + canInstantiateWith(assoc1) + case Nil => + false + Existential.isExistentialVar(tp1) && canInstantiateWith(assocExistentials) + + /** bi-map taking existentials to the left of a comparison to matching + * existentials on the right. This is not a bijection. However + * we have `forwards(backwards(bv)) == bv` for an existentially bound `bv`. + * That's enough to qualify as a BiTypeMap. + */ + private class MapExistentials(assoc: ExAssoc)(using Context) extends BiTypeMap: + + private def bad(t: Type) = + Existential.badExistential + .showing(i"existential match not found for $t in $assoc", capt) + + def apply(t: Type) = t match + case t: TermParamRef if Existential.isExistentialVar(t) => + // Find outermost existential on the right that can be instantiated to `t`, + // or `badExistential` if none exists. + def findMapped(assoc: ExAssoc): CaptureRef = assoc match + case (bv, assocBvs) :: assoc1 => + val outer = findMapped(assoc1) + if !Existential.isBadExistential(outer) then outer + else if assocBvs.contains(t) then bv + else bad(t) + case Nil => + bad(t) + findMapped(assoc) + case _ => + mapOver(t) + + /** The inverse takes existentials on the right to the innermost existential + * on the left to which they can be instantiated. + */ + lazy val inverse = new BiTypeMap: + def apply(t: Type) = t match + case t: TermParamRef if Existential.isExistentialVar(t) => + assoc.find(_._1 == t) match + case Some((_, bvs)) if bvs.nonEmpty => bvs.head + case _ => bad(t) + case _ => + mapOver(t) + + def inverse = MapExistentials.this + override def toString = "MapExistentials.inverse" + end inverse + end MapExistentials + protected def subCaptures(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult = + try + if assocExistentials.isEmpty then + refs1.subCaptures(refs2, frozen) + else + val mapped = refs1.map(MapExistentials(assocExistentials)) + if mapped.elems.exists(Existential.isBadExistential) + then CaptureSet.CompareResult.Fail(refs2 :: Nil) + else subCapturesMapped(mapped, refs2, frozen) + catch case ex: AssertionError => + println(i"fail while subCaptures $refs1 <:< $refs2") + throw ex + + protected def subCapturesMapped(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult = refs1.subCaptures(refs2, frozen) /** Is the boxing status of tp1 and tp2 the same, or alternatively, is @@ -3306,6 +3427,9 @@ object TypeComparer { def subCaptures(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult = comparing(_.subCaptures(refs1, refs2, frozen)) + + def subsumesExistentially(tp1: TermParamRef, tp2: CaptureRef)(using Context) = + comparing(_.subsumesExistentially(tp1, tp2)) } object MatchReducer: @@ -3746,6 +3870,11 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa private val b = new StringBuilder private var lastForwardGoal: String | Null = null + private def appendFailure(x: String) = + if lastForwardGoal != null then // last was deepest goal that failed + b.append(s" = $x") + lastForwardGoal = null + override def traceIndented[T](str: String)(op: => T): T = val str1 = str.replace('\n', ' ') if short && str1 == lastForwardGoal then @@ -3757,12 +3886,13 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa b.append("\n").append(" " * indent).append("==> ").append(str1) val res = op if short then - if res == false then - if lastForwardGoal != null then // last was deepest goal that failed - b.append(" = false") - lastForwardGoal = null - else - b.length = curLength // don't show successful subtraces + res match + case false => + appendFailure("false") + case res: CaptureSet.CompareResult if res != CaptureSet.CompareResult.OK => + appendFailure(show(res)) + case _ => + b.length = curLength // don't show successful subtraces else b.append("\n").append(" " * indent).append("<== ").append(str1).append(" = ").append(show(res)) indent -= 2 @@ -3815,5 +3945,10 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa super.subCaptures(refs1, refs2, frozen) } + override def subCapturesMapped(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult = + traceIndented(i"subcaptures mapped $refs1 <:< $refs2 ${if frozen then "frozen" else ""}") { + super.subCapturesMapped(refs1, refs2, frozen) + } + def lastTrace(header: String): String = header + { try b.toString finally b.clear() } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 3bc7a7223abb..8089735bdb0f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -18,7 +18,7 @@ import typer.ForceDegree import typer.Inferencing.* import typer.IfBottom import reporting.TestingReporter -import cc.{CapturingType, derivedCapturingType, CaptureSet, isBoxed, isBoxedCapturing} +import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index cb47bd92352e..0c348ad458e4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -38,7 +38,8 @@ import config.Printers.{core, typr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized -import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike} +import cc.{CapturingType, CaptureRef, CaptureSet, SingletonCaptureRef, isTrackableRef, + derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -522,11 +523,6 @@ object Types extends TypeUtils { */ def isDeclaredVarianceLambda: Boolean = false - /** Is this type a CaptureRef that can be tracked? - * This is true for all ThisTypes or ParamRefs but only for some NamedTypes. - */ - def isTrackableRef(using Context): Boolean = false - /** Does this type contain wildcard types? */ final def containsWildcardTypes(using Context) = existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) @@ -1653,9 +1649,6 @@ object Types extends TypeUtils { case _ => if (isRepeatedParam) this.argTypesHi.head else this } - /** The capture set of this type. Overridden and cached in CaptureRef */ - def captureSet(using Context): CaptureSet = CaptureSet.ofType(this, followResult = false) - // ----- Normalizing typerefs over refined types ---------------------------- /** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed @@ -2046,20 +2039,6 @@ object Types extends TypeUtils { case _ => this - /** A type capturing `ref` */ - def capturing(ref: CaptureRef)(using Context): Type = - if captureSet.accountsFor(ref) then this - else CapturingType(this, ref.singletonCaptureSet) - - /** A type capturing the capture set `cs`. If this type is already a capturing type - * the two capture sets are combined. - */ - def capturing(cs: CaptureSet)(using Context): Type = - if cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(captureSet, frozen = true).isOK then this - else this match - case CapturingType(parent, cs1) => parent.capturing(cs1 ++ cs) - case _ => CapturingType(this, cs) - /** The set of distinct symbols referred to by this type, after all aliases are expanded */ def coveringSet(using Context): Set[Symbol] = (new CoveringSetAccumulator).apply(Set.empty[Symbol], this) @@ -2258,67 +2237,6 @@ object Types extends TypeUtils { def isOverloaded(using Context): Boolean = false } - /** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs */ - trait CaptureRef extends TypeProxy, ValueType: - private var myCaptureSet: CaptureSet | Null = uninitialized - private var myCaptureSetRunId: Int = NoRunId - private var mySingletonCaptureSet: CaptureSet.Const | Null = null - - /** 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 = - isTrackableRef && (isMaxCapability || !captureSetOfInfo.isAlwaysEmpty) - - /** Is this a reach reference of the form `x*`? */ - def isReach(using Context): Boolean = false // overridden in AnnotatedType - - /** Is this a maybe reference of the form `x?`? */ - def isMaybe(using Context): Boolean = false // overridden in AnnotatedType - - def stripReach(using Context): CaptureRef = this // overridden in AnnotatedType - def stripMaybe(using Context): CaptureRef = this // overridden in AnnotatedType - - /** Is this reference the generic root capability `cap` ? */ - def isRootCapability(using Context): Boolean = false - - /** Is this reference capability that does not derive from another capability ? */ - def isMaxCapability(using Context): Boolean = false - - /** Normalize reference so that it can be compared with `eq` for equality */ - def normalizedRef(using Context): CaptureRef = this - - /** The capture set consisting of exactly this reference */ - def singletonCaptureSet(using Context): CaptureSet.Const = - if mySingletonCaptureSet == null then - mySingletonCaptureSet = CaptureSet(this.normalizedRef) - mySingletonCaptureSet.uncheckedNN - - /** The capture set of the type underlying this reference */ - def captureSetOfInfo(using Context): CaptureSet = - if ctx.runId == myCaptureSetRunId 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 || underlying.isProvisional then - myCaptureSet = null - else - myCaptureSet = computed - myCaptureSetRunId = ctx.runId - computed - - def invalidateCaches() = - myCaptureSetRunId = NoRunId - - override def captureSet(using Context): CaptureSet = - val cs = captureSetOfInfo - if isTrackableRef && !cs.isAlwaysEmpty then singletonCaptureSet else cs - - end CaptureRef - - trait SingletonCaptureRef extends SingletonType, CaptureRef - /** A trait for types that bind other types that refer to them. * Instances are: LambdaType, RecType. */ @@ -3007,32 +2925,11 @@ object Types extends TypeUtils { def implicitName(using Context): TermName = name def underlyingRef: TermRef = this - /** A term reference can be tracked if it is a local term ref to a value - * or a method term parameter. References to term parameters of classes - * cannot be tracked individually. - * They are subsumed in the capture sets of the enclosing class. - * TODO: ^^^ What about call-by-name? - */ - override def isTrackableRef(using Context) = - ((prefix eq NoPrefix) - || symbol.is(ParamAccessor) && prefix.isThisTypeOf(symbol.owner) - || isRootCapability - ) && !symbol.isOneOf(UnstableValueFlags) - - override def isRootCapability(using Context): Boolean = - name == nme.CAPTURE_ROOT && symbol == defn.captureRoot - - override def isMaxCapability(using Context): Boolean = - import cc.* - this.derivesFromCapability && symbol.isStableMember - - override def normalizedRef(using Context): CaptureRef = - if isTrackableRef then symbol.termRef else this } abstract case class TypeRef(override val prefix: Type, private var myDesignator: Designator) - extends NamedType { + extends NamedType, CaptureRef { type ThisType = TypeRef type ThisName = TypeName @@ -3081,6 +2978,7 @@ object Types extends TypeUtils { /** Hook that can be called from creation methods in TermRef and TypeRef */ def validated(using Context): this.type = this + } final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) { @@ -3182,8 +3080,6 @@ object Types extends TypeUtils { // can happen in IDE if `cls` is stale } - override def isTrackableRef(using Context) = true - override def computeHash(bs: Binders): Int = doHash(bs, tref) override def eql(that: Type): Boolean = that match { @@ -4830,10 +4726,6 @@ object Types extends TypeUtils { type BT = TermLambda def kindString: String = "Term" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) - override def isTrackableRef(using Context) = true - override def isMaxCapability(using Context) = - import cc.* - this.derivesFromCapability } private final class TermParamRefImpl(binder: TermLambda, paramNum: Int) extends TermParamRef(binder, paramNum) @@ -4841,7 +4733,8 @@ object Types extends TypeUtils { /** Only created in `binder.paramRefs`. Use `binder.paramRefs(paramNum)` to * refer to `TypeParamRef(binder, paramNum)`. */ - abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) extends ParamRef { + abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) + extends ParamRef, CaptureRef { type BT = TypeLambda def kindString: String = "Type" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -5823,30 +5716,6 @@ object Types extends TypeUtils { isRefiningCache } - override def isTrackableRef(using Context) = - (isReach || isMaybe) && parent.isTrackableRef - - /** Is this a reach reference of the form `x*`? */ - override def isReach(using Context): Boolean = - annot.symbol == defn.ReachCapabilityAnnot - - /** Is this a reach reference of the form `x*`? */ - override def isMaybe(using Context): Boolean = - annot.symbol == defn.MaybeCapabilityAnnot - - override def stripReach(using Context): CaptureRef = - if isReach then parent.asInstanceOf[CaptureRef] else this - - override def stripMaybe(using Context): CaptureRef = - if isMaybe then parent.asInstanceOf[CaptureRef] else this - - override def normalizedRef(using Context): CaptureRef = - if isReach then AnnotatedType(stripReach.normalizedRef, annot) else this - - override def captureSet(using Context): CaptureSet = - if isReach then super.captureSet - else CaptureSet.ofType(this, followResult = false) - // equals comes from case class; no matching override is needed override def computeHash(bs: Binders): Int = @@ -6273,6 +6142,15 @@ object Types extends TypeUtils { try derivedCapturingType(tp, this(parent), refs.map(this)) finally variance = saved + /** 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. + */ + protected def mapConserveSuper(t: TypeProxy): Type = + val t1 = t.superType + val t2 = apply(t1) + if t2 ne t1 then t2 else t + /** Map this function over given type */ def mapOver(tp: Type): Type = { record(s"TypeMap mapOver ${getClass}") diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4c13934f3473..5c0374ceba87 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1541,7 +1541,7 @@ object Parsers { case _ => None } - /** CaptureRef ::= ident | `this` | `cap` [`[` ident `]`] + /** CaptureRef ::= ident [`*` | `^`] | `this` */ def captureRef(): Tree = if in.token == THIS then simpleRef() @@ -1551,6 +1551,10 @@ object Parsers { in.nextToken() atSpan(startOffset(id)): PostfixOp(id, Ident(nme.CC_REACH)) + else if isIdent(nme.UPARROW) then + in.nextToken() + atSpan(startOffset(id)): + makeCapsOf(cpy.Ident(id)(id.name.toTypeName)) else id /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking @@ -1968,7 +1972,7 @@ object Parsers { } /** SimpleType ::= SimpleLiteral - * | ‘?’ SubtypeBounds + * | ‘?’ TypeBounds * | SimpleType1 * | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer * Singletons ::= Singleton {‘,’ Singleton} @@ -2188,9 +2192,15 @@ object Parsers { inBraces(refineStatSeq()) /** TypeBounds ::= [`>:' Type] [`<:' Type] + * | `^` -- under captureChecking */ def typeBounds(): TypeBoundsTree = - atSpan(in.offset) { TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) } + atSpan(in.offset): + if in.isIdent(nme.UPARROW) && Feature.ccEnabled then + in.nextToken() + TypeBoundsTree(EmptyTree, makeCapsBound()) + else + TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE)) private def bound(tok: Int): Tree = if (in.token == tok) { in.nextToken(); toplevelTyp() } @@ -3384,7 +3394,6 @@ object Parsers { * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ * DefTypeParam ::= {Annotation} - * [`sealed`] -- under captureChecking * id [HkTypeParamClause] TypeParamBounds * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index c06b43cafe17..aca5972d4516 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,7 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.{Config, Feature} -import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, MaybeCapability, isBoxed, levelOwner, retainedElems, isRetainsLike} +import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, MaybeCapability, isBoxed, retainedElems, isRetainsLike} class PlainPrinter(_ctx: Context) extends Printer { @@ -165,6 +165,8 @@ class PlainPrinter(_ctx: Context) extends Printer { private def toTextRetainedElem[T <: Untyped](ref: Tree[T]): Text = ref match case ref: RefTree[?] if ref.typeOpt.exists => toTextCaptureRef(ref.typeOpt) + case TypeApply(fn, arg :: Nil) if fn.symbol == defn.Caps_capsOf => + toTextRetainedElem(arg) case _ => toText(ref) @@ -416,6 +418,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: SingletonType => toTextRef(tp) case ReachCapability(tp1) => toTextRef(tp1) ~ "*" case MaybeCapability(tp1) => toTextRef(tp1) ~ "?" + case tp: (TypeRef | TypeParamRef) => toText(tp) ~ "^" case _ => toText(tp) protected def isOmittablePrefix(sym: Symbol): Boolean = diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0c6e36c8f18f..9852dfc1170d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -564,7 +564,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SingletonTypeTree(ref) => toTextLocal(ref) ~ "." ~ keywordStr("type") case RefinedTypeTree(tpt, refines) => - toTextLocal(tpt) ~ " " ~ blockText(refines) + if defn.isFunctionSymbol(tpt.symbol) && tree.hasType && !printDebug + then changePrec(GlobalPrec) { toText(tree.typeOpt) } + else toTextLocal(tpt) ~ blockText(refines) case AppliedTypeTree(tpt, args) => if (tpt.symbol == defn.orType && args.length == 2) changePrec(OrTypePrec) { toText(args(0)) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(args(1)) } } diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 79dfe3393578..3aec18dc2bd0 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -264,7 +264,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckClassDef(tree: TypeDef, impl: Template, sym: ClassSymbol)(using Context): Type = recheck(impl.constr) - impl.parentsOrDerived.foreach(recheck(_)) + impl.parents.foreach(recheck(_)) recheck(impl.self) recheckStats(impl.body) sym.typeRef @@ -454,12 +454,16 @@ abstract class Recheck extends Phase, SymTransformer: case _ => traverse(stats) + /** A hook to prevent rechecking a ValDef or DefDef. + * Typycally used when definitions are completed on first use. + */ + def skipRecheck(sym: Symbol)(using Context) = false + def recheckDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = - inContext(ctx.localContext(tree, sym)) { + inContext(ctx.localContext(tree, sym)): tree match case tree: ValDef => recheckValDef(tree, sym) case tree: DefDef => recheckDefDef(tree, sym) - } /** Recheck tree without adapting it, returning its new type. * @param tree the original tree @@ -476,10 +480,8 @@ abstract class Recheck extends Phase, SymTransformer: case tree: ValOrDefDef => if tree.isEmpty then NoType else - if sym.isUpdatedAfter(preRecheckPhase) then - sym.ensureCompleted() // in this case the symbol's completer should recheck the right hand side - else - recheckDef(tree, sym) + sym.ensureCompleted() + if !skipRecheck(sym) then recheckDef(tree, sym) sym.termRef case tree: TypeDef => // TODO: Should we allow for completers as for ValDefs or DefDefs? diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 83964417a6f1..32467de77264 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1613,7 +1613,7 @@ class Namer { typer: Typer => else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then if pclazz.is(Sealed) && !pclazz.is(JavaDefined) then report.error(UnableToExtendSealedClass(pclazz), cls.srcPos) - else if sourceVersion.isAtLeast(future) then + else if sourceVersion.isAtLeast(`3.6`) then checkFeature(nme.adhocExtensions, i"Unless $pclazz is declared 'open', its extension in a separate file", cls.topLevelClass, diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a5380f73a2a5..6ca87127d3c9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1683,10 +1683,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else val resTpt = TypeTree(mt.nonDependentResultApprox).withSpan(body.span) val paramTpts = appDef.termParamss.head.map(p => TypeTree(p.tpt.tpe).withSpan(p.tpt.span)) - val funSym = defn.FunctionSymbol(numArgs, isContextual, isImpure) + val funSym = defn.FunctionSymbol(numArgs, isContextual) val tycon = TypeTree(funSym.typeRef) AppliedTypeTree(tycon, paramTpts :+ resTpt) - RefinedTypeTree(core, List(appDef), ctx.owner.asClass) + val res = RefinedTypeTree(core, List(appDef), ctx.owner.asClass) + if isImpure then + typed(untpd.makeRetaining(untpd.TypedSplice(res), Nil, tpnme.retainsCap), pt) + else + res end typedDependent args match { @@ -2324,7 +2328,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val res = Throw(expr1).withSpan(tree.span) if Feature.ccEnabled && !cap.isEmpty && !ctx.isAfterTyper then // Record access to the CanThrow capabulity recovered in `cap` by wrapping - // the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotatoon. + // the type of the `throw` (i.e. Nothing) in a `@requiresCapability` annotation. Typed(res, TypeTree( AnnotatedType(res.tpe, diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 808bdba34e3f..967246041082 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -1,6 +1,6 @@ package scala -import annotation.experimental +import annotation.{experimental, compileTimeOnly} @experimental object caps: @@ -16,12 +16,29 @@ import annotation.experimental @deprecated("Use `Capability` instead") type Cap = Capability + /** Carrier trait for capture set type parameters */ + trait CapSet extends Any + + @compileTimeOnly("Should be be used only internally by the Scala compiler") + def capsOf[CS]: Any = ??? + /** Reach capabilities x* which appear as terms in @retains annotations are encoded * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets * they are represented as `x.type @annotation.internal.reachCapability`. */ extension (x: Any) def reachCapability: Any = x + /** A trait to allow expressing existential types such as + * + * (x: Exists) => A ->{x} B + */ + sealed trait Exists extends Capability + + /** This should go into annotations. For now it is here, so that we + * can experiment with it quickly between minor releases + */ + final class untrackedCaptures extends annotation.StaticAnnotation + object unsafe: extension [T](x: T) @@ -32,22 +49,19 @@ import annotation.experimental def unsafeAssumePure: T = x /** If argument is of type `cs T`, converts to type `box cs T`. This - * avoids the error that would be raised when boxing `*`. + * avoids the error that would be raised when boxing `cap`. */ - @deprecated(since = "3.3") def unsafeBox: T = x /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `*`. + * avoids the error that would be raised when unboxing `cap`. */ - @deprecated(since = "3.3") def unsafeUnbox: T = x extension [T, U](f: T => U) /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `*`. + * avoids the error that would be raised when unboxing `cap`. */ - @deprecated(since = "3.3") def unsafeBoxFunArg: T => U = f end unsafe diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 58ef4beb930d..4d1b0ed4ff95 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -1008,7 +1008,7 @@ object Iterator extends IterableFactory[Iterator] { def newBuilder[A]: Builder[A, Iterator[A]] = new ImmutableBuilder[A, Iterator[A]](empty[A]) { override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this } - } + }.asInstanceOf // !!! CC unsafe op /** Creates iterator that produces the results of some element computation a number of times. * @@ -1160,7 +1160,7 @@ object Iterator extends IterableFactory[Iterator] { @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { val c = current.asInstanceOf[ConcatIterator[A]] - current = c.current + current = c.current.asInstanceOf // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { if (last == null) last = c.last diff --git a/scala2-library-cc/src/scala/collection/SeqView.scala b/scala2-library-cc/src/scala/collection/SeqView.scala index 34405e06eedb..c7af0077ce1a 100644 --- a/scala2-library-cc/src/scala/collection/SeqView.scala +++ b/scala2-library-cc/src/scala/collection/SeqView.scala @@ -186,12 +186,14 @@ object SeqView { } @SerialVersionUID(3L) - class Sorted[A, B >: A] private (private[this] var underlying: SomeSeqOps[A]^, + class Sorted[A, B >: A] private (underlying: SomeSeqOps[A]^, private[this] val len: Int, ord: Ordering[B]) extends SeqView[A] { outer: Sorted[A, B]^ => + private var myUnderlying: SomeSeqOps[A]^{underlying} = underlying + // force evaluation immediately by calling `length` so infinite collections // hang on `sorted`/`sortWith`/`sortBy` rather than on arbitrary method calls def this(underlying: SomeSeqOps[A]^, ord: Ordering[B]) = this(underlying, underlying.length, ord) @@ -221,10 +223,10 @@ object SeqView { val res = { val len = this.len if (len == 0) Nil - else if (len == 1) List(underlying.head) + else if (len == 1) List(myUnderlying.head) else { val arr = new Array[Any](len) // Array[Any] =:= Array[AnyRef] - underlying.copyToArray(arr) + myUnderlying.copyToArray(arr) java.util.Arrays.sort(arr.asInstanceOf[Array[AnyRef]], ord.asInstanceOf[Ordering[AnyRef]]) // casting the Array[AnyRef] to Array[A] and creating an ArraySeq from it // is safe because: @@ -238,12 +240,12 @@ object SeqView { } } evaluated = true - underlying = null + myUnderlying = null res } private[this] def elems: SomeSeqOps[A]^{this} = { - val orig = underlying + val orig = myUnderlying if (evaluated) _sorted else orig } diff --git a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala index ac24995e6892..2f7b017a6729 100644 --- a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala +++ b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala @@ -24,6 +24,7 @@ import scala.language.implicitConversions import scala.runtime.Statics import language.experimental.captureChecking import annotation.unchecked.uncheckedCaptures +import caps.untrackedCaptures /** This class implements an immutable linked list. We call it "lazy" * because it computes its elements only when they are needed. @@ -245,7 +246,7 @@ import annotation.unchecked.uncheckedCaptures * @define evaluatesAllElements This method evaluates all elements of the collection. */ @SerialVersionUID(3L) -final class LazyListIterable[+A] private(private[this] var lazyState: () => LazyListIterable.State[A]^) +final class LazyListIterable[+A] private(@untrackedCaptures lazyState: () => LazyListIterable.State[A]^) extends AbstractIterable[A] with Iterable[A] with IterableOps[A, LazyListIterable, LazyListIterable[A]] @@ -253,6 +254,8 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy with Serializable { import LazyListIterable._ + private var myLazyState = lazyState + @volatile private[this] var stateEvaluated: Boolean = false @inline private def stateDefined: Boolean = stateEvaluated private[this] var midEvaluation = false @@ -264,11 +267,11 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy throw new RuntimeException("self-referential LazyListIterable or a derivation thereof has no more elements") } midEvaluation = true - val res = try lazyState() finally midEvaluation = false + val res = try myLazyState() finally midEvaluation = false // if we set it to `true` before evaluating, we may infinite loop // if something expects `state` to already be evaluated stateEvaluated = true - lazyState = null // allow GC + myLazyState = null // allow GC res } @@ -755,7 +758,7 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy * The iterator returned by this method mostly preserves laziness; * a single element ahead of the iterator is evaluated. */ - override def grouped(size: Int): Iterator[LazyListIterable[A]] = { + override def grouped(size: Int): Iterator[LazyListIterable[A]]^{this} = { require(size > 0, "size must be positive, but was " + size) slidingImpl(size = size, step = size) } @@ -765,12 +768,12 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy * The iterator returned by this method mostly preserves laziness; * `size - step max 1` elements ahead of the iterator are evaluated. */ - override def sliding(size: Int, step: Int): Iterator[LazyListIterable[A]] = { + override def sliding(size: Int, step: Int): Iterator[LazyListIterable[A]]^{this} = { require(size > 0 && step > 0, s"size=$size and step=$step, but both must be positive") slidingImpl(size = size, step = step) } - @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyListIterable[A]] = + @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyListIterable[A]]^{this} = if (knownIsEmpty) Iterator.empty else new SlidingIterator[A](this, size = size, step = step) @@ -996,7 +999,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def filterImpl[A](ll: LazyListIterable[A]^, p: A => Boolean, isFlipped: Boolean): LazyListIterable[A]^{ll, p} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric newLL { var elem: A = null.asInstanceOf[A] var found = false @@ -1013,7 +1016,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def collectImpl[A, B](ll: LazyListIterable[A]^, pf: PartialFunction[A, B]^): LazyListIterable[B]^{ll, pf} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric newLL { val marker = Statics.pfMarker val toMarker = anyToMarker.asInstanceOf[A => B] // safe because Function1 is erased @@ -1032,7 +1035,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def flatMapImpl[A, B](ll: LazyListIterable[A]^, f: A => IterableOnce[B]^): LazyListIterable[B]^{ll, f} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric newLL { var it: Iterator[B]^{ll, f} = null var itHasNext = false @@ -1056,7 +1059,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def dropImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric var iRef = n // val iRef = new IntRef(n) newLL { var rest = restRef // var rest = restRef.elem @@ -1073,7 +1076,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def dropWhileImpl[A](ll: LazyListIterable[A]^, p: A => Boolean): LazyListIterable[A]^{ll, p} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric newLL { var rest = restRef // var rest = restRef.elem while (!rest.isEmpty && p(rest.head)) { @@ -1086,8 +1089,8 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { private def takeRightImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric - var scoutRef: LazyListIterable[A]^{ll*} = ll // same situation + var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + var scoutRef: LazyListIterable[A]^{ll} = ll // same situation var remainingRef = n // val remainingRef = new IntRef(n) newLL { var scout = scoutRef // var scout = scoutRef.elem @@ -1236,33 +1239,35 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { */ def newBuilder[A]: Builder[A, LazyListIterable[A]] = new LazyBuilder[A] - private class LazyIterator[+A](private[this] var lazyList: LazyListIterable[A]^) extends AbstractIterator[A] { - override def hasNext: Boolean = !lazyList.isEmpty + private class LazyIterator[+A](lazyList: LazyListIterable[A]^) extends AbstractIterator[A] { + private var myLazyList = lazyList + override def hasNext: Boolean = !myLazyList.isEmpty override def next(): A = - if (lazyList.isEmpty) Iterator.empty.next() + if (myLazyList.isEmpty) Iterator.empty.next() else { - val res = lazyList.head - lazyList = lazyList.tail + val res = myLazyList.head + myLazyList = myLazyList.tail res } } - private class SlidingIterator[A](private[this] var lazyList: LazyListIterable[A]^, size: Int, step: Int) + private class SlidingIterator[A](lazyList: LazyListIterable[A]^, size: Int, step: Int) extends AbstractIterator[LazyListIterable[A]] { + private var myLazyList = lazyList private val minLen = size - step max 0 private var first = true def hasNext: Boolean = - if (first) !lazyList.isEmpty - else lazyList.lengthGt(minLen) + if (first) !myLazyList.isEmpty + else myLazyList.lengthGt(minLen) def next(): LazyListIterable[A] = { if (!hasNext) Iterator.empty.next() else { first = false - val list = lazyList - lazyList = list.drop(step) + val list = myLazyList + myLazyList = list.drop(step) list.take(size) } } @@ -1281,7 +1286,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { import LazyBuilder._ private[this] var next: DeferredState[A] = _ - private[this] var list: LazyListIterable[A] = _ + @uncheckedCaptures private[this] var list: LazyListIterable[A]^ = _ clear() diff --git a/tests/neg-custom-args/captures/box-adapt-cases.scala b/tests/neg-custom-args/captures/box-adapt-cases.scala index 3dac26a98318..681d699842ed 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -4,7 +4,7 @@ def test1(): Unit = { type Id[X] = [T] -> (op: X => T) -> T val x: Id[Cap^] = ??? - x(cap => cap.use()) // was error, now OK + x(cap => cap.use()) // error, OK under sealed } def test2(io: Cap^): Unit = { diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index b9e5c81b721d..c9530f6aad50 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,8 +1,13 @@ -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ - | reference (cap2 : Cap) is not included in the allowed capture set {cap1} + | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ?->{cap1} I +-- Error: tests/neg-custom-args/captures/byname.scala:22:12 ------------------------------------------------------------ +22 | h2(() => g())() // error + | ^^^ + | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} + | of an enclosing function literal with expected type () ->{cap1} I -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 ----------------------------------------- 4 | def f() = if cap1 == cap1 then g else g // error | ^ diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index 0ed3a09cb414..75ad527dbd2d 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -17,6 +17,9 @@ def test2(cap1: Cap, cap2: Cap): I^{cap1} = def h(x: ->{cap1} I) = x // ok h(f()) // OK h(g()) // error + def h2(x: () ->{cap1} I) = x // ok + h2(() => f()) // OK + h2(() => g())() // error diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index 80ee1aba84e1..b202a14d0940 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { // error + val b = handle[Exception, () => Nothing] { (x: CanThrow[Exception]) => () => raise(new Exception)(using x) - } { + } { // error (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 0e99d1876d3c..3d0ed538b2e5 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,52 +1,52 @@ --- Error: tests/neg-custom-args/captures/capt1.scala:4:11 -------------------------------------------------------------- -4 | () => if x == null then y else y // error +-- Error: tests/neg-custom-args/captures/capt1.scala:6:11 -------------------------------------------------------------- +6 | () => if x == null then y else y // error | ^ | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C --- Error: tests/neg-custom-args/captures/capt1.scala:7:11 -------------------------------------------------------------- -7 | () => if x == null then y else y // error +-- Error: tests/neg-custom-args/captures/capt1.scala:9:11 -------------------------------------------------------------- +9 | () => if x == null then y else y // error | ^ | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:14:2 ----------------------------------------- -14 | def f(y: Int) = if x == null then y else y // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:16:2 ----------------------------------------- +16 | def f(y: Int) = if x == null then y else y // error | ^ | Found: (y: Int) ->{x} Int | Required: Matchable -15 | f +17 | f | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:21:2 ----------------------------------------- -21 | class F(y: Int) extends A: // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:23:2 ----------------------------------------- +23 | class F(y: Int) extends A: // error | ^ | Found: A^{x} | Required: A -22 | def m() = if x == null then y else y -23 | F(22) +24 | def m() = if x == null then y else y +25 | F(22) | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:26:2 ----------------------------------------- -26 | new A: // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:28:2 ----------------------------------------- +28 | new A: // error | ^ | Found: A^{x} | Required: A -27 | def m() = if x == null then y else y +29 | def m() = if x == null then y else y | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt1.scala:32:12 ------------------------------------------------------------- -32 | val z2 = h[() -> Cap](() => x) // error // error +-- Error: tests/neg-custom-args/captures/capt1.scala:34:12 ------------------------------------------------------------- +34 | val z2 = h[() -> Cap](() => x) // error // error | ^^^^^^^^^^^^ | Sealed type variable X cannot be instantiated to () -> box C^ since | the part box C^ of that type captures the root capability `cap`. | This is often caused by a local capability in an argument of method h | leaking as part of its result. --- Error: tests/neg-custom-args/captures/capt1.scala:32:30 ------------------------------------------------------------- -32 | val z2 = h[() -> Cap](() => x) // error // error +-- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- +34 | val z2 = h[() -> Cap](() => x) // error // error | ^ | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> box C^ --- Error: tests/neg-custom-args/captures/capt1.scala:34:12 ------------------------------------------------------------- -34 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error +-- Error: tests/neg-custom-args/captures/capt1.scala:36:12 ------------------------------------------------------------- +36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | Sealed type variable X cannot be instantiated to box () ->{x} Cap since | the part Cap of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index 48c4d889bf8d..cad0bad4ba56 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import annotation.retains class C def f(x: C @retains(caps.cap), y: C): () -> C = diff --git a/tests/neg-custom-args/captures/cc-this5.check b/tests/neg-custom-args/captures/cc-this5.check index 1329734ce37d..8affe7005e2e 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ---------------------------------------------------------- 16 | def f = println(c) // error | ^ - | (c : Cap) cannot be referenced here; it is not included in the allowed capture set {} + | (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {} | of the enclosing class A -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 ------------------------------------- 21 | val x: A = this // error diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 8c4d1f315fd8..47559ab97568 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -1,29 +1,29 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:62:8 ------------------------- -61 | Result: -62 | Future: // error, type mismatch +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:64:8 ------------------------- +63 | Result: +64 | Future: // error, type mismatch | ^ | Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}] | Required: Result[Future[T], Nothing] -63 | fr.await.ok +65 | fr.await.ok |-------------------------------------------------------------------------------------------------------------------- |Inline stack trace |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from effect-swaps-explicit.scala:39 -39 | boundary(Ok(body)) + |This location contains code that was inlined from effect-swaps-explicit.scala:41 +41 | boundary(Ok(body)) | ^^^^^^^^ -------------------------------------------------------------------------------------------------------------------- | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:72:10 ------------------------ -72 | Future: fut ?=> // error: type mismatch +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:74:10 ------------------------ +74 | Future: fut ?=> // error: type mismatch | ^ | Found: Future[box T^?]^{fr, lbl} | Required: Future[box T^?]^? -73 | fr.await.ok +75 | fr.await.ok | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:66:15 --------------------------------------------- -66 | Result.make: //lbl ?=> // error, escaping label from Result +-- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 --------------------------------------------- +68 | Result.make: //lbl ?=> // error, escaping label from Result | ^^^^^^^^^^^ |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 052beaab01b2..7474e1711b34 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) object boundary: final class Label[-T] // extends caps.Capability diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index d4eed2bae2f2..4bafd6421af3 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -63,7 +63,7 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: //lbl ?=> // should be error, escaping label from Result but infers Result[Any, Any] + Result.make: // should be errorm but inders Result[Any, Any] Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/extending-cap-classes.check b/tests/neg-custom-args/captures/extending-cap-classes.check index 3bdddfd9dd3c..0936f48576e5 100644 --- a/tests/neg-custom-args/captures/extending-cap-classes.check +++ b/tests/neg-custom-args/captures/extending-cap-classes.check @@ -15,7 +15,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/extending-cap-classes.scala:13:15 ------------------------ 13 | val z2: C1 = y2 // error | ^^ - | Found: (y2 : C2)^{y2} + | Found: (y2 : C2^) | Required: C1 | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index 0d9cbed164e3..e54f161ef124 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,8 +5,8 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // error - def log = file.write("log") + var file: File^ = uninitialized // OK, was error under sealed + def log = file.write("log") // error, was OK under sealed def withFile[T](op: (l: caps.Capability) ?-> (f: File^{l}) => T): T = op(using caps.cap)(new File) diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index 498292166297..fde4b93e196c 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -11,12 +11,12 @@ def main(io: Capp^, net: Capp^): Unit = { } val test2: (c: Capp^) -> () => Unit = - localCap { c => // should work + localCap { c => // error (c1: Capp^) => () => { c1.use() } } val test3: (c: Capp^{io}) -> () ->{io} Unit = - localCap { c => // should work + localCap { c => // error (c1: Capp^{io}) => () => { c1.use() } } diff --git a/tests/neg-custom-args/captures/i15749.scala b/tests/neg-custom-args/captures/i15749.scala new file mode 100644 index 000000000000..c5b59042085a --- /dev/null +++ b/tests/neg-custom-args/captures/i15749.scala @@ -0,0 +1,15 @@ +class Unit +object unit extends Unit + +type Top = Any^ + +type LazyVal[T] = Unit => T + +class Foo[T](val x: T) + +// Foo[□ Unit => T] +type BoxedLazyVal[T] = Foo[LazyVal[T]] + +def force[A](v: BoxedLazyVal[A]): A = + // Γ ⊢ v.x : □ {cap} Unit -> A + v.x(unit) // error: (unbox v.x)(unit), was ok under the sealed policy \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 0f8f0bf6eac5..58582423b101 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -25,11 +25,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^ - | Found: box C^ - | Required: box C{val arg: C^?}^? + | Found: C^ + | Required: box C{val arg: C^?}^ | - | Note that the universal capability `cap` - | cannot be included in capture set ? + | Note that C^ cannot be box-converted to box C{val arg: C^?}^ + | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 974870cd769c..89bf91493fcd 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to force sealed encapsulation checking) trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) diff --git a/tests/neg-custom-args/captures/i15923-cases.scala b/tests/neg-custom-args/captures/i15923-cases.scala new file mode 100644 index 000000000000..83cfa554e8b9 --- /dev/null +++ b/tests/neg-custom-args/captures/i15923-cases.scala @@ -0,0 +1,7 @@ +trait Cap { def use(): Int } +type Id[X] = [T] -> (op: X => T) -> T +def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) + +def foo(x: Id[Cap^]) = { + x(_.use()) // error, was OK under sealed policy +} diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index d363bb665dc3..ec04fe9c9827 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) trait Cap { def use(): Int; def close(): Unit } def mkCap(): Cap^ = ??? diff --git a/tests/neg-custom-args/captures/i16725.scala b/tests/neg-custom-args/captures/i16725.scala index 733c2c562bbc..1accf197c626 100644 --- a/tests/neg-custom-args/captures/i16725.scala +++ b/tests/neg-custom-args/captures/i16725.scala @@ -7,8 +7,8 @@ type Wrapper[T] = [R] -> (f: T => R) -> R def mk[T](x: T): Wrapper[T] = [R] => f => f(x) def useWrappedIO(wrapper: Wrapper[IO]): () -> Unit = () => - wrapper: io => + wrapper: io => // error io.brewCoffee() def main(): Unit = - val escaped = usingIO(io => useWrappedIO(mk(io))) // error + val escaped = usingIO(io => useWrappedIO(mk(io))) escaped() // boom diff --git a/tests/neg-custom-args/captures/i19330-alt2.scala b/tests/neg-custom-args/captures/i19330-alt2.scala index b49dce4b71ef..86634b45dbe3 100644 --- a/tests/neg-custom-args/captures/i19330-alt2.scala +++ b/tests/neg-custom-args/captures/i19330-alt2.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i19330.scala b/tests/neg-custom-args/captures/i19330.scala index 8acb0dd8f66b..5fbdc00db311 100644 --- a/tests/neg-custom-args/captures/i19330.scala +++ b/tests/neg-custom-args/captures/i19330.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to force sealed encapsulation checking) import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 09352ec648ce..643ef78841f0 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{cap1}) - | Required: lazylists.LazyList[Int] + | Found: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- @@ -29,8 +29,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:48 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3, cap3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1, cap1}) - | Required: lazylists.LazyList[Int]^{cap1, ref3, cap3} + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, cap2, ref1}) + | Required: lazylists.LazyList[Int]^{cap1, ref3, cap3} | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/lazylist.scala:22:6 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 3095c1f2f4f9..4a8738118609 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,9 +1,8 @@ -- Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:36:2 ----------------------------------------------- 36 | try // error | ^ - | result of `try` cannot have type LazyList[Int]^ since - | that type captures the root capability `cap`. - | This is often caused by a locally generated exception capability leaking as part of its result. + | The expression's type LazyList[Int]^ is not allowed to capture the root capability `cap`. + | This usually means that a capability persists longer than its allowed lifetime. 37 | tabulate(10) { i => 38 | if i > 9 then throw Ex1() 39 | i * i diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index a5f8d73ccf7a..2dae3ec3bbc6 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,17 +1,17 @@ --- Error: tests/neg-custom-args/captures/levels.scala:17:13 ------------------------------------------------------------ -17 | val _ = Ref[String => String]((x: String) => x) // error +-- Error: tests/neg-custom-args/captures/levels.scala:19:13 ------------------------------------------------------------ +19 | val _ = Ref[String => String]((x: String) => x) // error | ^^^^^^^^^^^^^^^^^^^^^ | Sealed type variable T cannot be instantiated to box String => String since | that type captures the root capability `cap`. | This is often caused by a local capability in an argument of constructor Ref | leaking as part of its result. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- -22 | r.setV(g) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:24:11 --------------------------------------- +24 | r.setV(g) // error | ^ | Found: box (x: String) ->{cap3} String | Required: box (x$0: String) ->? String | | Note that reference (cap3 : CC^), defined in method scope - | cannot be included in outer capture set ? of value r which is associated with method test2 + | cannot be included in outer capture set ? of value r | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index b28e87f03ef7..4709fd80d9b8 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) class CC def test1(cap1: CC^) = diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index b9f1f57be769..32351a179eab 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -1,8 +1,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- 11 | x = q // error | ^ - | Found: (q : Proc) - | Required: () ->{p, q²} Unit + | Found: box () ->{q} Unit + | Required: box () ->{p, q²} Unit | | where: q is a parameter in method inner | q² is a parameter in method test @@ -12,33 +12,19 @@ 12 | x = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: () ->{p, q} Unit + | Required: box () ->{p, q} Unit + | + | Note that () => Unit cannot be box-converted to box () ->{p, q} Unit + | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- 13 | y = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: () ->{p} Unit - | - | Note that the universal capability `cap` - | cannot be included in capture set {p} of variable y - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- -14 | y = q // error - | ^ - | Found: (q : Proc) - | Required: () ->{p} Unit + | Required: box () => Unit | - | Note that reference (q : Proc), defined in method inner - | cannot be included in outer capture set {p} of variable y which is associated with method test + | Note that () => Unit cannot be box-converted to box () => Unit + | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/outer-var.scala:16:53 --------------------------------------------------------- -16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable A cannot be instantiated to box () => Unit since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method apply - | leaking as part of its result. diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index 39c3a6da4ca3..e26cd631602a 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -11,8 +11,8 @@ def test(p: Proc, q: () => Unit) = x = q // error x = (q: Proc) // error y = (q: Proc) // error - y = q // error + y = q // OK, was error under sealed - var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // OK, was error under sealed diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index a1c5a56369e9..f20dbdf311ad 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -1,48 +1,48 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:21:11 -------------------------------------- -21 | cur = (() => f.write()) :: Nil // error since {f*} !<: {xs*} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:23:11 -------------------------------------- +23 | cur = (() => f.write()) :: Nil // error since {f*} !<: {xs*} | ^^^^^^^^^^^^^^^^^^^^^^^ | Found: List[box () ->{f} Unit] | Required: List[box () ->{xs*} Unit] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:7 --------------------------------------- -32 | (() => f.write()) :: Nil // error since {f*} !<: {xs*} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:34:7 --------------------------------------- +34 | (() => f.write()) :: Nil // error since {f*} !<: {xs*} | ^^^^^^^^^^^^^^^^^^^^^^^ | Found: List[box () ->{f} Unit] | Required: box List[box () ->{xs*} Unit]^? | | Note that reference (f : File^), defined in method $anonfun - | cannot be included in outer capture set {xs*} of value cur which is associated with method runAll1 + | cannot be included in outer capture set {xs*} of value cur | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:35:6 ------------------------------------------------------------ -35 | var cur: List[Proc] = xs // error: Illegal type for var +-- Error: tests/neg-custom-args/captures/reaches.scala:37:6 ------------------------------------------------------------ +37 | var cur: List[Proc] = xs // error: Illegal type for var | ^ | Mutable variable cur cannot have type List[box () => Unit] since | the part box () => Unit of that type captures the root capability `cap`. --- Error: tests/neg-custom-args/captures/reaches.scala:42:15 ----------------------------------------------------------- -42 | val cur = Ref[List[Proc]](xs) // error: illegal type for type argument to Ref +-- Error: tests/neg-custom-args/captures/reaches.scala:44:15 ----------------------------------------------------------- +44 | val cur = Ref[List[Proc]](xs) // error: illegal type for type argument to Ref | ^^^^^^^^^^^^^^^ | Sealed type variable T cannot be instantiated to List[box () => Unit] since | the part box () => Unit of that type captures the root capability `cap`. | This is often caused by a local capability in an argument of constructor Ref | leaking as part of its result. --- Error: tests/neg-custom-args/captures/reaches.scala:52:31 ----------------------------------------------------------- -52 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error +-- Error: tests/neg-custom-args/captures/reaches.scala:54:31 ----------------------------------------------------------- +54 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error | ^^^^^^^^^^^^^^^^^^^^ | Sealed type variable A cannot be instantiated to box () => Unit since | that type captures the root capability `cap`. | This is often caused by a local capability in an argument of constructor Id | leaking as part of its result. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:60:27 -------------------------------------- -60 | val f1: File^{id*} = id(f) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:63:27 -------------------------------------- +63 | val f1: File^{id*} = id(f) // error, since now id(f): File^ | ^^^^^ | Found: File^{id, f} | Required: File^{id*} | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:77:5 ------------------------------------------------------------ -77 | ps.map((x, y) => compose1(x, y)) // error: cannot mix cap and * +-- Error: tests/neg-custom-args/captures/reaches.scala:80:5 ------------------------------------------------------------ +80 | ps.map((x, y) => compose1(x, y)) // error: cannot mix cap and * (should work now) | ^^^^^^ | Reach capability cap and universal capability cap cannot both | appear in the type [B](f: ((box A ->{ps*} A, box A ->{ps*} A)) => B): List[B] of this expression diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index de5e4362cdf2..eadb76c69e5b 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) class File: def write(): Unit = ??? @@ -55,9 +57,10 @@ def test = def attack2 = val id: File^ -> File^ = x => x + // val id: File^ -> EX C.File^C val leaked = usingFile[File^{id*}]: f => - val f1: File^{id*} = id(f) // error + val f1: File^{id*} = id(f) // error, since now id(f): File^ f1 class List[+A]: @@ -74,6 +77,4 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = z => g(f(z)) def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = - ps.map((x, y) => compose1(x, y)) // error: cannot mix cap and * - - + ps.map((x, y) => compose1(x, y)) // error: cannot mix cap and * (should work now) diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index 504955b220ad..f646a9736395 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -2,9 +2,9 @@ 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference (ps : List[(box A => A, box A => A)]) @reachCapability is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? -- Error: tests/neg-custom-args/captures/reaches2.scala:8:13 ----------------------------------------------------------- 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference (ps : List[(box A => A, box A => A)]) @reachCapability is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 50dcc16f5f54..7f8ab50bc222 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,46 +1,46 @@ --- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:36:4 ---------------------------------- -36 | b.x +-- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:4 ---------------------------------- +38 | b.x | ^^^ | Discarded non-Unit value of type () -> Unit. You may want to use `()`. | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/real-try.scala:12:2 ----------------------------------------------------------- -12 | try // error +-- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- +14 | try // error | ^ | result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. -13 | () => foo(1) -14 | catch -15 | case _: Ex1 => ??? -16 | case _: Ex2 => ??? --- Error: tests/neg-custom-args/captures/real-try.scala:18:10 ---------------------------------------------------------- -18 | val x = try // error +15 | () => foo(1) +16 | catch +17 | case _: Ex1 => ??? +18 | case _: Ex2 => ??? +-- Error: tests/neg-custom-args/captures/real-try.scala:20:10 ---------------------------------------------------------- +20 | val x = try // error | ^ | result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. -19 | () => foo(1) -20 | catch -21 | case _: Ex1 => ??? -22 | case _: Ex2 => ??? --- Error: tests/neg-custom-args/captures/real-try.scala:24:10 ---------------------------------------------------------- -24 | val y = try // error +21 | () => foo(1) +22 | catch +23 | case _: Ex1 => ??? +24 | case _: Ex2 => ??? +-- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- +26 | val y = try // error | ^ | result of `try` cannot have type () => Cell[Unit]^? since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. -25 | () => Cell(foo(1)) -26 | catch -27 | case _: Ex1 => ??? -28 | case _: Ex2 => ??? --- Error: tests/neg-custom-args/captures/real-try.scala:30:10 ---------------------------------------------------------- -30 | val b = try // error +27 | () => Cell(foo(1)) +28 | catch +29 | case _: Ex1 => ??? +30 | case _: Ex2 => ??? +-- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- +32 | val b = try // error | ^ | result of `try` cannot have type Cell[box () => Unit]^? since | the part box () => Unit of that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. -31 | Cell(() => foo(1)) -32 | catch -33 | case _: Ex1 => ??? -34 | case _: Ex2 => ??? +33 | Cell(() => foo(1)) +34 | catch +35 | case _: Ex1 => ??? +36 | case _: Ex2 => ??? diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 23961e884ea3..51f1a0fdea5a 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import language.experimental.saferExceptions class Ex1 extends Exception("Ex1") diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index 9f4b28ce52e3..525d33fdb7c5 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -14,5 +14,5 @@ def test4(): Unit = val ys: List[IO^{xs*}] = xs // ok def test5(): Unit = val f: [R] -> (IO^ -> R) -> IO^ = ??? - val g: [R] -> (IO^ -> R) -> IO^{f*} = f // ok + val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 3b96927de738..77a5fc06e05a 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,10 +1,12 @@ --- Error: tests/neg-custom-args/captures/try.scala:23:16 --------------------------------------------------------------- -23 | val a = handle[Exception, CanThrow[Exception]] { // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable R cannot be instantiated to box CT[Exception]^ since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method handle - | leaking as part of its result. +-- Error: tests/neg-custom-args/captures/try.scala:25:3 ---------------------------------------------------------------- +23 | val a = handle[Exception, CanThrow[Exception]] { +24 | (x: CanThrow[Exception]) => x +25 | }{ // error (but could be better) + | ^ + | The expression's type box CT[Exception]^ is not allowed to capture the root capability `cap`. + | This usually means that a capability persists longer than its allowed lifetime. +26 | (ex: Exception) => ??? +27 | } -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 3d25dff4cd2c..45a1b346a512 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,9 +20,9 @@ def handle[E <: Exception, R <: Top](op: CT[E]^ => R)(handler: E => R): R = catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { // error + val a = handle[Exception, CanThrow[Exception]] { (x: CanThrow[Exception]) => x - }{ + }{ // error (but could be better) (ex: Exception) => ??? } diff --git a/tests/neg/unsound-reach-2.scala b/tests/neg-custom-args/captures/unsound-reach-2.scala similarity index 89% rename from tests/neg/unsound-reach-2.scala rename to tests/neg-custom-args/captures/unsound-reach-2.scala index 083cec6ee5b2..384af31ee1fc 100644 --- a/tests/neg/unsound-reach-2.scala +++ b/tests/neg-custom-args/captures/unsound-reach-2.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import language.experimental.captureChecking trait Consumer[-T]: def apply(x: T): Unit diff --git a/tests/neg/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala similarity index 89% rename from tests/neg/unsound-reach-3.scala rename to tests/neg-custom-args/captures/unsound-reach-3.scala index 71c27fe5007d..985beb7ae55d 100644 --- a/tests/neg/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import language.experimental.captureChecking trait File: def close(): Unit diff --git a/tests/neg/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check similarity index 55% rename from tests/neg/unsound-reach-4.check rename to tests/neg-custom-args/captures/unsound-reach-4.check index 47256baf408a..9abf86c772d5 100644 --- a/tests/neg/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -1,5 +1,5 @@ --- Error: tests/neg/unsound-reach-4.scala:20:19 ------------------------------------------------------------------------ -20 | escaped = boom.use(f) // error +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:22:19 --------------------------------------------------- +22 | escaped = boom.use(f) // error | ^^^^^^^^ | Reach capability backdoor* and universal capability cap cannot both | appear in the type (x: F): box File^{backdoor*} of this expression diff --git a/tests/neg/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala similarity index 85% rename from tests/neg/unsound-reach-4.scala rename to tests/neg-custom-args/captures/unsound-reach-4.scala index fa395fa117ca..14050b4afff2 100644 --- a/tests/neg/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) import language.experimental.captureChecking trait File: def close(): Unit diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check new file mode 100644 index 000000000000..f0e4c4deeb41 --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -0,0 +1,7 @@ +-- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach.scala:10:8 ----------------------------------- +10 | def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking + | ^ + | error overriding method use in trait Foo of type (x: File^)(op: box File^ => Unit): Unit; + | method use of type (x: File^)(op: File^ => Unit): Unit has incompatible type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/unsound-reach.scala b/tests/neg-custom-args/captures/unsound-reach.scala similarity index 71% rename from tests/neg/unsound-reach.scala rename to tests/neg-custom-args/captures/unsound-reach.scala index 48a74f86d311..22ed4614b71b 100644 --- a/tests/neg/unsound-reach.scala +++ b/tests/neg-custom-args/captures/unsound-reach.scala @@ -7,7 +7,7 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^)(op: X => Unit): Unit class Bar extends Foo[File^]: - def use(x: File^)(op: File^ => Unit): Unit = op(x) + def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking def bad(): Unit = val backdoor: Foo[File^] = new Bar @@ -15,6 +15,6 @@ def bad(): Unit = var escaped: File^{backdoor*} = null withFile("hello.txt"): f => - boom.use(f): (f1: File^{backdoor*}) => // error + boom.use(f): (f1: File^{backdoor*}) => // was error before existentials escaped = f1 diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index 2bc014e9a4e7..2ef301b6ec1f 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -2,14 +2,17 @@ 15 | a = (g: String => String) // error | ^^^^^^^^^^^^^^^^^^^ | Found: String => String - | Required: String ->{cap1, cap2} String + | Required: box String ->{cap1, cap2} String + | + | Note that String => String cannot be box-converted to box String ->{cap1, cap2} String + | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ - | Found: (x: String) ->{cap3} String - | Required: (x: String) ->{cap1, cap2} String + | Found: box (x: String) ->{cap3} String + | Required: box (x: String) ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 22d13d8e26e7..e4b1e71a2000 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,28 +1,28 @@ --- Error: tests/neg-custom-args/captures/vars.scala:22:14 -------------------------------------------------------------- -22 | a = x => g(x) // error +-- Error: tests/neg-custom-args/captures/vars.scala:24:14 -------------------------------------------------------------- +24 | a = x => g(x) // error | ^^^^ | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a | | Note that reference (cap3 : Cap), defined in method scope - | cannot be included in outer capture set {cap1} of variable a which is associated with method test --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:23:8 ------------------------------------------ -23 | a = g // error + | cannot be included in outer capture set {cap1} of variable a +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ +25 | a = g // error | ^ | Found: (x: String) ->{cap3} String | Required: (x$0: String) ->{cap1} String | | Note that reference (cap3 : Cap), defined in method scope - | cannot be included in outer capture set {cap1} of variable a which is associated with method test + | cannot be included in outer capture set {cap1} of variable a | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:12 ----------------------------------------- -25 | b = List(g) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:27:12 ----------------------------------------- +27 | b = List(g) // error | ^^^^^^^ | Found: List[box (x$0: String) ->{cap3} String] | Required: List[box String ->{cap1, cap2} String] | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:34:2 --------------------------------------------------------------- -34 | local { cap3 => // error +-- Error: tests/neg-custom-args/captures/vars.scala:36:2 --------------------------------------------------------------- +36 | local { cap3 => // error | ^^^^^ | local reference cap3 leaks into outer capture set of type parameter T of method local diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index ab5a2f43acc7..5eb1e3fedda9 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) class CC type Cap = CC^ diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check new file mode 100644 index 000000000000..dbe811ab99ec --- /dev/null +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -0,0 +1,21 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ---------------------------------- +13 | val y2: IO^ -> IO^ = y1.foo // error + | ^^^^^^ + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> (ex$6: caps.Exists) -> IO^{ex$6} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ---------------------------------- +14 | val y3: IO^ -> IO^{x*} = y1.foo // error + | ^^^^^^ + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> IO^{x*} + | + | longer explanation available when compiling with `-explain` +-- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- +9 | val foo: IO^ -> IO^ = x => x // error + | ^ + | error overriding value foo in trait Foo of type IO^ -> box IO^; + | value foo of type IO^ -> (ex$3: caps.Exists) -> IO^{ex$3} has incompatible type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala new file mode 100644 index 000000000000..fa5eee1232df --- /dev/null +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -0,0 +1,14 @@ +import language.experimental.captureChecking + +trait IO + +trait Foo[+T]: + val foo: IO^ -> T + +trait Bar extends Foo[IO^]: + val foo: IO^ -> IO^ = x => x // error + +def test(x: Foo[IO^]): Unit = + val y1: Foo[IO^{x*}] = x + val y2: IO^ -> IO^ = y1.foo // error + val y3: IO^ -> IO^{x*} = y1.foo // error \ No newline at end of file diff --git a/tests/neg/cc-ex-conformance.scala b/tests/neg/cc-ex-conformance.scala new file mode 100644 index 000000000000..a953466daa9a --- /dev/null +++ b/tests/neg/cc-ex-conformance.scala @@ -0,0 +1,25 @@ +import language.experimental.captureChecking +import caps.{Exists, Capability} + +class C + +type EX1 = () => (c: Exists) => (C^{c}, C^{c}) + +type EX2 = () => (c1: Exists) => (c2: Exists) => (C^{c1}, C^{c2}) + +type EX3 = () => (c: Exists) => (x: Object^) => C^{c} + +type EX4 = () => (x: Object^) => (c: Exists) => C^{c} + +def Test = + val ex1: EX1 = ??? + val ex2: EX2 = ??? + val _: EX1 = ex1 + val _: EX2 = ex1 // ok + val _: EX1 = ex2 // ok + + val ex3: EX3 = ??? + val ex4: EX4 = ??? + val _: EX4 = ex3 // ok + val _: EX4 = ex4 + val _: EX3 = ex4 // error diff --git a/tests/neg/cc-poly-1.check b/tests/neg/cc-poly-1.check new file mode 100644 index 000000000000..abb507078bf4 --- /dev/null +++ b/tests/neg/cc-poly-1.check @@ -0,0 +1,12 @@ +-- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:12:6 ---------------------------------------------------------- +12 | f[Any](D()) // error + | ^ + | Type argument Any does not conform to upper bound caps.CapSet^ + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/cc-poly-1.scala:13:6 ---------------------------------------------------------- +13 | f[String](D()) // error + | ^ + | Type argument String does not conform to upper bound caps.CapSet^ + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/cc-poly-1.scala b/tests/neg/cc-poly-1.scala new file mode 100644 index 000000000000..580b124bc8f3 --- /dev/null +++ b/tests/neg/cc-poly-1.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.{CapSet, Capability} + +object Test: + + class C extends Capability + class D + + def f[X^](x: D^{X^}): D^{X^} = x + + def test(c1: C, c2: C) = + f[Any](D()) // error + f[String](D()) // error diff --git a/tests/neg/cc-poly-2.check b/tests/neg/cc-poly-2.check new file mode 100644 index 000000000000..0615ce19b5ea --- /dev/null +++ b/tests/neg/cc-poly-2.check @@ -0,0 +1,21 @@ +-- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:13:15 --------------------------------------------------------- +13 | f[Nothing](d) // error + | ^ + | Found: (d : Test.D^) + | Required: Test.D + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:14:19 --------------------------------------------------------- +14 | f[CapSet^{c1}](d) // error + | ^ + | Found: (d : Test.D^) + | Required: Test.D^{c1} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:16:20 --------------------------------------------------------- +16 | val _: D^{c1} = x // error + | ^ + | Found: (x : Test.D^{d}) + | Required: Test.D^{c1} + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/cc-poly-2.scala b/tests/neg/cc-poly-2.scala new file mode 100644 index 000000000000..c5e5df6540da --- /dev/null +++ b/tests/neg/cc-poly-2.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking +import caps.{CapSet, Capability} + +object Test: + + class C extends Capability + class D + + def f[X^](x: D^{X^}): D^{X^} = x + + def test(c1: C, c2: C) = + val d: D^ = D() + f[Nothing](d) // error + f[CapSet^{c1}](d) // error + val x = f(d) + val _: D^{c1} = x // error diff --git a/tests/neg/existential-mapping.check b/tests/neg/existential-mapping.check new file mode 100644 index 000000000000..edfce67f6eef --- /dev/null +++ b/tests/neg/existential-mapping.check @@ -0,0 +1,88 @@ +-- Error: tests/neg/existential-mapping.scala:44:13 -------------------------------------------------------------------- +44 | val z1: A^ => Array[C^] = ??? // error + | ^^^^^^^^^^^^^^^ + | Array[box C^] captures the root capability `cap` in invariant position +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:9:25 ------------------------------------------------ +9 | val _: (x: C^) -> C = x1 // error + | ^^ + | Found: (x1 : (x: C^) -> (ex$3: caps.Exists) -> C^{ex$3}) + | Required: (x: C^) -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:12:20 ----------------------------------------------- +12 | val _: C^ -> C = x2 // error + | ^^ + | Found: (x2 : C^ -> (ex$7: caps.Exists) -> C^{ex$7}) + | Required: C^ -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:15:30 ----------------------------------------------- +15 | val _: A^ -> (x: C^) -> C = x3 // error + | ^^ + | Found: (x3 : A^ -> (x: C^) -> (ex$11: caps.Exists) -> C^{ex$11}) + | Required: A^ -> (x: C^) -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:18:25 ----------------------------------------------- +18 | val _: A^ -> C^ -> C = x4 // error + | ^^ + | Found: (x4 : A^ -> C^ -> (ex$19: caps.Exists) -> C^{ex$19}) + | Required: A^ -> C^ -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:21:30 ----------------------------------------------- +21 | val _: A^ -> (x: C^) -> C = x5 // error + | ^^ + | Found: (x5 : A^ -> (ex$27: caps.Exists) -> Fun[C^{ex$27}]) + | Required: A^ -> (x: C^) -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:24:30 ----------------------------------------------- +24 | val _: A^ -> (x: C^) => C = x6 // error + | ^^ + | Found: (x6 : A^ -> (ex$33: caps.Exists) -> IFun[C^{ex$33}]) + | Required: A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:27:25 ----------------------------------------------- +27 | val _: (x: C^) => C = y1 // error + | ^^ + | Found: (y1 : (x: C^) => (ex$38: caps.Exists) -> C^{ex$38}) + | Required: (x: C^) => C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:30:20 ----------------------------------------------- +30 | val _: C^ => C = y2 // error + | ^^ + | Found: (y2 : C^ => (ex$42: caps.Exists) -> C^{ex$42}) + | Required: C^ => C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:33:30 ----------------------------------------------- +33 | val _: A^ => (x: C^) => C = y3 // error + | ^^ + | Found: (y3 : A^ => (ex$47: caps.Exists) -> (x: C^) ->{ex$47} (ex$46: caps.Exists) -> C^{ex$46}) + | Required: A^ => (ex$50: caps.Exists) -> (x: C^) ->{ex$50} C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:36:25 ----------------------------------------------- +36 | val _: A^ => C^ => C = y4 // error + | ^^ + | Found: (y4 : A^ => (ex$53: caps.Exists) -> C^ ->{ex$53} (ex$52: caps.Exists) -> C^{ex$52}) + | Required: A^ => (ex$56: caps.Exists) -> C^ ->{ex$56} C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:39:30 ----------------------------------------------- +39 | val _: A^ => (x: C^) -> C = y5 // error + | ^^ + | Found: (y5 : A^ => (ex$58: caps.Exists) -> Fun[C^{ex$58}]) + | Required: A^ => (x: C^) -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/existential-mapping.scala:42:30 ----------------------------------------------- +42 | val _: A^ => (x: C^) => C = y6 // error + | ^^ + | Found: (y6 : A^ => (ex$64: caps.Exists) -> IFun[C^{ex$64}]) + | Required: A^ => (ex$67: caps.Exists) -> (x: C^) ->{ex$67} C + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/existential-mapping.scala b/tests/neg/existential-mapping.scala new file mode 100644 index 000000000000..290f7dc767a6 --- /dev/null +++ b/tests/neg/existential-mapping.scala @@ -0,0 +1,46 @@ +import language.experimental.captureChecking + +class A +class C +type Fun[X] = (x: C^) -> X +type IFun[X] = (x: C^) => X +def Test = + val x1: (x: C^) -> C^ = ??? + val _: (x: C^) -> C = x1 // error + + val x2: C^ -> C^ = ??? + val _: C^ -> C = x2 // error + + val x3: A^ -> (x: C^) -> C^ = ??? + val _: A^ -> (x: C^) -> C = x3 // error + + val x4: A^ -> C^ -> C^ = ??? + val _: A^ -> C^ -> C = x4 // error + + val x5: A^ -> Fun[C^] = ??? + val _: A^ -> (x: C^) -> C = x5 // error + + val x6: A^ -> IFun[C^] = ??? + val _: A^ -> (x: C^) => C = x6 // error + + val y1: (x: C^) => C^ = ??? + val _: (x: C^) => C = y1 // error + + val y2: C^ => C^ = ??? + val _: C^ => C = y2 // error + + val y3: A^ => (x: C^) => C^ = ??? + val _: A^ => (x: C^) => C = y3 // error + + val y4: A^ => C^ => C^ = ??? + val _: A^ => C^ => C = y4 // error + + val y5: A^ => Fun[C^] = ??? + val _: A^ => (x: C^) -> C = y5 // error + + val y6: A^ => IFun[C^] = ??? + val _: A^ => (x: C^) => C = y6 // error + + val z1: A^ => Array[C^] = ??? // error + + diff --git a/tests/neg/i20503.scala b/tests/neg/i20503.scala index 7a1bffcff529..e8770b934ad1 100644 --- a/tests/neg/i20503.scala +++ b/tests/neg/i20503.scala @@ -9,8 +9,8 @@ class List[+A]: def runOps(ops: List[() => Unit]): Unit = // See i20156, due to limitation in expressiveness of current system, - // we cannot map over the list of impure elements. - ops.foreach(op => op()) // error + // we could map over the list of impure elements. OK with existentials. + ops.foreach(op => op()) def main(): Unit = val f: List[() => Unit] -> Unit = runOps // error diff --git a/tests/neg/unsound-reach.check b/tests/neg/unsound-reach.check deleted file mode 100644 index 8cabbe1571a0..000000000000 --- a/tests/neg/unsound-reach.check +++ /dev/null @@ -1,5 +0,0 @@ --- Error: tests/neg/unsound-reach.scala:18:13 -------------------------------------------------------------------------- -18 | boom.use(f): (f1: File^{backdoor*}) => // error - | ^^^^^^^^ - | Reach capability backdoor* and universal capability cap cannot both - | appear in the type (x: File^)(op: box File^{backdoor*} => Unit): Unit of this expression diff --git a/tests/new/test.scala b/tests/new/test.scala index 16a823547553..18644422ab06 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -2,8 +2,9 @@ import language.experimental.namedTuples type Person = (name: String, age: Int) -def test = - val bob = (name = "Bob", age = 33): (name: String, age: Int) +trait A: + type T + +class B: + type U =:= A { type T = U } - val silly = bob match - case (name = n, age = a) => n.length + a diff --git a/tests/pos-custom-args/captures/cap-problem.scala b/tests/pos-custom-args/captures/cap-problem.scala new file mode 100644 index 000000000000..483b4e938b1b --- /dev/null +++ b/tests/pos-custom-args/captures/cap-problem.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking + +trait Suspend: + type Suspension + + def resume(s: Suspension): Unit + +import caps.Capability + +trait Async(val support: Suspend) extends Capability + +class CancelSuspension(ac: Async, suspension: ac.support.Suspension): + ac.support.resume(suspension) diff --git a/tests/pos-custom-args/captures/capt-test.scala b/tests/pos-custom-args/captures/capt-test.scala index e229c685d846..49f199f106f1 100644 --- a/tests/pos-custom-args/captures/capt-test.scala +++ b/tests/pos-custom-args/captures/capt-test.scala @@ -36,3 +36,4 @@ def test(c: Cap, d: Cap) = val a4 = zs.map(identity) val a4c: LIST[Cap ->{d, y} Unit] = a4 + val a5: LIST[Cap ->{d, y} Unit] = zs.map(identity) diff --git a/tests/pos-custom-args/captures/casts.scala b/tests/pos-custom-args/captures/casts.scala new file mode 100644 index 000000000000..572b58d008f6 --- /dev/null +++ b/tests/pos-custom-args/captures/casts.scala @@ -0,0 +1,4 @@ +import language.experimental.captureChecking +def Test = + val x: Any = ??? + val y = x.asInstanceOf[Int => Int] diff --git a/tests/pos-custom-args/captures/curried-closures.scala b/tests/pos-custom-args/captures/curried-closures.scala index 0ad729375b3c..262dd4b66b92 100644 --- a/tests/pos-custom-args/captures/curried-closures.scala +++ b/tests/pos-custom-args/captures/curried-closures.scala @@ -1,6 +1,7 @@ -//> using options -experimental +import annotation.experimental +import language.experimental.captureChecking -object Test: +@experimental object Test: def map2(xs: List[Int])(f: Int => Int): List[Int] = xs.map(f) val f1 = map2 val fc1: List[Int] -> (Int => Int) -> List[Int] = f1 diff --git a/tests/pos-custom-args/captures/filevar-expanded.scala b/tests/pos-custom-args/captures/filevar-expanded.scala index 13051994f346..a883471e8d2e 100644 --- a/tests/pos-custom-args/captures/filevar-expanded.scala +++ b/tests/pos-custom-args/captures/filevar-expanded.scala @@ -32,5 +32,6 @@ object test2: def test(io3: IO^) = withFile(io3): f => val o = Service(io3) - o.file = f + o.file = f // this is a bit dubious. It's legal since we treat class refinements + // as capture set variables that can be made to include refs coming from outside. o.log diff --git a/tests/pos-custom-args/captures/i15749.scala b/tests/pos-custom-args/captures/i15749.scala index 0a552ae1a3c5..58274c7cc817 100644 --- a/tests/pos-custom-args/captures/i15749.scala +++ b/tests/pos-custom-args/captures/i15749.scala @@ -1,3 +1,5 @@ +//> using options -source 3.4 +// (to make sure we use the sealed policy) class Unit object unit extends Unit @@ -12,4 +14,4 @@ type BoxedLazyVal[T] = Foo[LazyVal[T]] def force[A](v: BoxedLazyVal[A]): A = // Γ ⊢ v.x : □ {cap} Unit -> A - v.x(unit) // was error: (unbox v.x)(unit), where (unbox v.x) should be untypable, now ok \ No newline at end of file + v.x(unit) // should be error: (unbox v.x)(unit), where (unbox v.x) should be untypable, now ok \ No newline at end of file diff --git a/tests/pos-custom-args/captures/i15923-cases.scala b/tests/pos-custom-args/captures/i15923-cases.scala index 7c5635f7b3dd..4b5a36f208ec 100644 --- a/tests/pos-custom-args/captures/i15923-cases.scala +++ b/tests/pos-custom-args/captures/i15923-cases.scala @@ -2,10 +2,6 @@ trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) -def foo(x: Id[Cap^]) = { - x(_.use()) // was error, now OK -} - def bar(io: Cap^, x: Id[Cap^{io}]) = { x(_.use()) } diff --git a/tests/pos-custom-args/captures/i15925.scala b/tests/pos-custom-args/captures/i15925.scala index 63b6962ff9f8..1c448c7377c2 100644 --- a/tests/pos-custom-args/captures/i15925.scala +++ b/tests/pos-custom-args/captures/i15925.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import annotation.unchecked.uncheckedCaptures class Unit object u extends Unit @@ -6,8 +7,8 @@ object u extends Unit type Foo[X] = [T] -> (op: X => T) -> T type Lazy[X] = Unit => X -def force[X](fx: Foo[Lazy[X]]): X = +def force[X](fx: Foo[Lazy[X] @uncheckedCaptures]): X = fx[X](f => f(u)) -def force2[X](fx: Foo[Unit => X]): X = +def force2[X](fx: Foo[(Unit => X) @uncheckedCaptures]): X = fx[X](f => f(u)) diff --git a/tests/pos-custom-args/captures/levels.scala b/tests/pos-custom-args/captures/levels.scala new file mode 100644 index 000000000000..cabd537442a5 --- /dev/null +++ b/tests/pos-custom-args/captures/levels.scala @@ -0,0 +1,23 @@ +class CC + +def test1(cap1: CC^) = + + class Ref[T](init: T): + private var v: T = init + def setV(x: T): Unit = v = x + def getV: T = v + +def test2(cap1: CC^) = + + class Ref[T](init: T): + private var v: T = init + def setV(x: T): Unit = v = x + def getV: T = v + + val _ = Ref[String => String]((x: String) => x) // ok + val r = Ref((x: String) => x) + + def scope(cap3: CC^) = + def g(x: String): String = if cap3 == cap3 then "" else "a" + r.setV(g) // error + () diff --git a/tests/pos-custom-args/captures/opaque-cap.scala b/tests/pos-custom-args/captures/opaque-cap.scala new file mode 100644 index 000000000000..dc3d48a2d311 --- /dev/null +++ b/tests/pos-custom-args/captures/opaque-cap.scala @@ -0,0 +1,6 @@ +import language.experimental.captureChecking + +trait A extends caps.Capability + +object O: + opaque type B = A \ No newline at end of file diff --git a/tests/pos-custom-args/captures/unsafe-captures.scala b/tests/pos-custom-args/captures/unsafe-captures.scala new file mode 100644 index 000000000000..5e0144331344 --- /dev/null +++ b/tests/pos-custom-args/captures/unsafe-captures.scala @@ -0,0 +1,8 @@ +import annotation.unchecked.uncheckedCaptures +class LL[+A] private (private var lazyState: (() => LL.State[A]^) @uncheckedCaptures): + private val res = lazyState() // without unchecked captures we get a van't unbox cap error + + +object LL: + + private trait State[+A] diff --git a/tests/pos-custom-args/captures/untracked-captures.scala b/tests/pos-custom-args/captures/untracked-captures.scala new file mode 100644 index 000000000000..7a090a5dd24f --- /dev/null +++ b/tests/pos-custom-args/captures/untracked-captures.scala @@ -0,0 +1,34 @@ +import caps.untrackedCaptures +class LL[+A] private (@untrackedCaptures lazyState: () => LL.State[A]^): + private val res = lazyState() + + +object LL: + + private trait State[+A] + private object State: + object Empty extends State[Nothing] + + private def newLL[A](state: () => State[A]^): LL[A]^{state} = ??? + + private def sCons[A](hd: A, tl: LL[A]^): State[A]^{tl} = ??? + + def filterImpl[A](ll: LL[A]^, p: A => Boolean): LL[A]^{ll, p} = + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef: LL[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric + + val cl = () => + var elem: A = null.asInstanceOf[A] + var found = false + var rest = restRef // Without untracked captures a type ascription would be needed here + // because the compiler tries to keep track of lazyState in refinements + // of LL and gets confused (c.f Setup.addCaptureRefinements) + + while !found do + found = p(elem) + rest = rest + restRef = rest + val res = if found then sCons(elem, filterImpl(rest, p)) else State.Empty + ??? : State[A]^{ll, p} + val nll = newLL(cl) + nll diff --git a/tests/pos/cc-ex-unpack.scala b/tests/pos/cc-ex-unpack.scala new file mode 100644 index 000000000000..ae9b4ea5d805 --- /dev/null +++ b/tests/pos/cc-ex-unpack.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.{Exists, Capability} + +class C + +type EX1 = (c: Exists) -> (C^{c}, C^{c}) + +type EX2 = () -> (c1: Exists) -> (c2: Exists) -> (C^{c1}, C^{c2}) + +type EX3 = () -> (c: Exists) -> () -> C^{c} + +type EX4 = () -> () -> (c: Exists) -> C^{c} + +def Test = + def f = + val ex1: EX1 = ??? + val c1 = ex1 + c1 diff --git a/tests/pos/cc-poly-1.scala b/tests/pos/cc-poly-1.scala new file mode 100644 index 000000000000..ed32d94f7a99 --- /dev/null +++ b/tests/pos/cc-poly-1.scala @@ -0,0 +1,26 @@ +import language.experimental.captureChecking +import annotation.experimental +import caps.{CapSet, Capability} + +@experimental object Test: + + class C extends Capability + class D + + def f[X^](x: D^{X^}): D^{X^} = x + def g[X^](x: D^{X^}, y: D^{X^}): D^{X^} = x + def h[X^](): D^{X^} = ??? + + def test(c1: C, c2: C) = + val d: D^{c1, c2} = D() + val x = f[CapSet^{c1, c2}](d) + val _: D^{c1, c2} = x + val d1: D^{c1} = D() + val d2: D^{c2} = D() + val y = g(d1, d2) + val _: D^{d1, d2} = y + val _: D^{c1, c2} = y + val z = h() + + + diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos/cc-poly-source-capability.scala new file mode 100644 index 000000000000..9a21b2d5b802 --- /dev/null +++ b/tests/pos/cc-poly-source-capability.scala @@ -0,0 +1,32 @@ +import language.experimental.captureChecking +import annotation.experimental +import caps.{CapSet, Capability} + +@experimental object Test: + + class Async extends Capability + + def listener(async: Async): Listener^{async} = ??? + + class Listener + + class Source[X^]: + private var listeners: Set[Listener^{X^}] = Set.empty + def register(x: Listener^{X^}): Unit = + listeners += x + + def allListeners: Set[Listener^{X^}] = listeners + + def test1(async1: Async, others: List[Async]) = + val src = Source[CapSet^{async1, others*}] + val lst1 = listener(async1) + val lsts = others.map(listener) + val _: List[Listener^{others*}] = lsts + src.register{lst1} + src.register(listener(async1)) + lsts.foreach(src.register) + others.map(listener).foreach(src.register) + val ls = src.allListeners + val _: Set[Listener^{async1, others*}] = ls + + diff --git a/tests/pos/cc-poly-source.scala b/tests/pos/cc-poly-source.scala new file mode 100644 index 000000000000..939f1f682dc8 --- /dev/null +++ b/tests/pos/cc-poly-source.scala @@ -0,0 +1,36 @@ +import language.experimental.captureChecking +import annotation.experimental +import caps.{CapSet, Capability} + +@experimental object Test: + + class Label //extends Capability + + class Listener + + class Source[X^]: + private var listeners: Set[Listener^{X^}] = Set.empty + def register(x: Listener^{X^}): Unit = + listeners += x + + def allListeners: Set[Listener^{X^}] = listeners + + def test1(lbl1: Label^, lbl2: Label^) = + val src = Source[CapSet^{lbl1, lbl2}] + def l1: Listener^{lbl1} = ??? + val l2: Listener^{lbl2} = ??? + src.register{l1} + src.register{l2} + val ls = src.allListeners + val _: Set[Listener^{lbl1, lbl2}] = ls + + def test2(lbls: List[Label^]) = + def makeListener(lbl: Label^): Listener^{lbl} = ??? + val listeners = lbls.map(makeListener) + val src = Source[CapSet^{lbls*}] + for l <- listeners do + src.register(l) + val ls = src.allListeners + val _: Set[Listener^{lbls*}] = ls + + diff --git a/tests/pos/infer-exists.scala b/tests/pos/infer-exists.scala new file mode 100644 index 000000000000..6d5225f75128 --- /dev/null +++ b/tests/pos/infer-exists.scala @@ -0,0 +1,12 @@ +import language.experimental.captureChecking + +class C extends caps.Capability +class D + +def test1 = + val a: (x: C) -> C = ??? + val b = a + +def test2 = + val a: (x: D^) -> D^ = ??? + val b = a diff --git a/tests/pos/reach-capability.scala b/tests/pos/reach-capability.scala new file mode 100644 index 000000000000..d551113eb05b --- /dev/null +++ b/tests/pos/reach-capability.scala @@ -0,0 +1,17 @@ +import language.experimental.captureChecking +import annotation.experimental +import caps.{Capability} + +@experimental object Test2: + + class List[+A]: + def map[B](f: A => B): List[B] = ??? + + class Label extends Capability + + class Listener + + def test2(lbls: List[Label]) = + def makeListener(lbl: Label): Listener^{lbl} = ??? + val listeners = lbls.map(makeListener) // should work + diff --git a/tests/pos/reach-problem.scala b/tests/pos/reach-problem.scala new file mode 100644 index 000000000000..60dd1d4667a7 --- /dev/null +++ b/tests/pos/reach-problem.scala @@ -0,0 +1,9 @@ +import language.experimental.captureChecking + +class Box[T](items: Seq[T^]): + def getOne: T^{items*} = ??? + +object Box: + def getOne[T](items: Seq[T^]): T^{items*} = + val bx = Box(items) + bx.getOne \ No newline at end of file diff --git a/tests/printing/dependent-annot.check b/tests/printing/dependent-annot.check index a8a7e8b0bfee..f2dd0f702884 100644 --- a/tests/printing/dependent-annot.check +++ b/tests/printing/dependent-annot.check @@ -11,12 +11,7 @@ package { def f(y: C, z: C): Unit = { def g(): C @ann([y,z : Any]*) = ??? - val ac: - (C => Array[String]) - { - def apply(x: C): Array[String @ann([x : Any]*)] - } - = ??? + val ac: (x: C) => Array[String @ann([x : Any]*)] = ??? val dc: Array[String] = ac.apply(g()) () } diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index 20a6a33d3e02..5443758afa72 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -552,7 +552,7 @@ object CollectionStrawMan5 { } def flatMap[B](f: A => IterableOnce[B]^): Iterator[B]^{this, f} = new Iterator[B] { - private var myCurrent: Iterator[B]^{this} = Iterator.empty + private var myCurrent: Iterator[B]^{this, f} = Iterator.empty private def current = { while (!myCurrent.hasNext && self.hasNext) myCurrent = f(self.next()).iterator