diff --git a/.gitignore b/.gitignore index f378adb24bc8..e61b324f617f 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,6 @@ docs/_spec/.jekyll-metadata # scaladoc related scaladoc/output/ +#coverage +coverage/ + diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 3e7bba86dcf4..9aaf12da3dcc 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -247,8 +247,9 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint profiler.afterPhase(phase, profileBefore) if (ctx.settings.Xprint.value.containsPhase(phase)) for (unit <- units) - lastPrintedTree = - printTree(lastPrintedTree)(using ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) + def printCtx(unit: CompilationUnit) = phase.printingContext( + ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) + lastPrintedTree = printTree(lastPrintedTree)(using printCtx(unit)) report.informTime(s"$phase ", start) Stats.record(s"total trees at end of $phase", ast.Trees.ntrees) for (unit <- units) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c104c603422d..6024eab29722 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -205,12 +205,12 @@ object desugar { def makeImplicitParameters( tpts: List[Tree], implicitFlag: FlagSet, - mkParamName: () => TermName, + mkParamName: Int => TermName, forPrimaryConstructor: Boolean = false )(using Context): List[ValDef] = for (tpt, i) <- tpts.zipWithIndex yield { val paramFlags: FlagSet = if (forPrimaryConstructor) LocalParamAccessor else Param - val epname = mkParamName() + val epname = mkParamName(i) ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | implicitFlag) } @@ -254,7 +254,7 @@ object desugar { // using clauses, we only need names that are unique among the // parameters of the method since shadowing does not affect // implicit resolution in Scala 3. - mkParamName = () => + mkParamName = i => val index = seenContextBounds + 1 // Start at 1 like FreshNameCreator. val ret = ContextBoundParamName(EmptyTermName, index) seenContextBounds += 1 @@ -1602,9 +1602,12 @@ object desugar { case vd: ValDef => vd } - def makeContextualFunction(formals: List[Tree], body: Tree, erasedParams: List[Boolean])(using Context): Function = { + def makeContextualFunction(formals: List[Tree], paramNamesOrNil: List[TermName], body: Tree, erasedParams: List[Boolean])(using Context): Function = { val mods = Given - val params = makeImplicitParameters(formals, mods, mkParamName = () => ContextFunctionParamName.fresh()) + val params = makeImplicitParameters(formals, mods, + mkParamName = i => + if paramNamesOrNil.isEmpty then ContextFunctionParamName.fresh() + else paramNamesOrNil(i)) FunctionWithMods(params, body, Modifiers(mods), erasedParams) } diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 6659818b333e..4aaef28b9e1e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -376,6 +376,17 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] => case _ => tree.tpe.isInstanceOf[ThisType] } + + /** Under capture checking, an extractor for qualified roots `cap[Q]`. + */ + object QualifiedRoot: + + def unapply(tree: Apply)(using Context): Option[String] = tree match + case Apply(fn, Literal(lit) :: Nil) if fn.symbol == defn.Caps_capIn => + Some(lit.value.asInstanceOf[String]) + case _ => + None + end QualifiedRoot } trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] => @@ -799,12 +810,37 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } + /** An extractor for def of a closure contained the block of the closure, + * possibly with type ascriptions. + */ + object possiblyTypedClosureDef: + def unapply(tree: Tree)(using Context): Option[DefDef] = tree match + case Typed(expr, _) => unapply(expr) + case _ => closureDef.unapply(tree) + /** If tree is a closure, its body, otherwise tree itself */ def closureBody(tree: Tree)(using Context): Tree = tree match { case closureDef(meth) => meth.rhs case _ => tree } + /** Is `mdef` an eta-expansion of a method reference? To recognize this, we use + * the following criterion: A method definition is an eta expansion, if + * it contains at least one term paramter, the parameter has a zero extent span, + * and the right hand side is either an application or a closure with' + * an anonymous method that's itself characterized as an eta expansion. + */ + def isEtaExpansion(mdef: DefDef)(using Context): Boolean = + !rhsOfEtaExpansion(mdef).isEmpty + + def rhsOfEtaExpansion(mdef: DefDef)(using Context): Tree = mdef.paramss match + case (param :: _) :: _ if param.asInstanceOf[Tree].span.isZeroExtent => + mdef.rhs match + case rhs: Apply => rhs + case closureDef(mdef1) => rhsOfEtaExpansion(mdef1) + case _ => EmptyTree + case _ => EmptyTree + /** The variables defined by a pattern, in reverse order of their appearance. */ def patVars(tree: Tree)(using Context): List[Symbol] = { val acc = new TreeAccumulator[List[Symbol]] { outer => diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 8cc0750de53c..e7d38da854a4 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -149,7 +149,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case Floating } - /** {x1, ..., xN} T (only relevant under captureChecking) */ + /** {x1, ..., xN} T (only relevant under captureChecking) + * Created when parsing function types so that capture set and result type + * is combined in a single node. + */ case class CapturesAndResult(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree /** A type tree appearing somewhere in the untyped DefDef of a lambda, it will be typed using `tpFun`. @@ -512,6 +515,9 @@ 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))) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index d6c6dd9ec2c0..dfaa3c701576 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -10,7 +10,9 @@ import config.SourceVersion import config.Printers.capt import util.Property.Key import tpd.* +import StdNames.nme import config.Feature +import collection.mutable private val Captures: Key[CaptureSet] = Key() private val BoxedType: Key[BoxedTypeCache] = Key() @@ -21,6 +23,11 @@ private val BoxedType: Key[BoxedTypeCache] = Key() */ private val adaptUnpickledFunctionTypes = false +/** Switch whether we constrain a root var that includes the source of a + * root map to be an alias of that source (so that it can be mapped) + */ +private val constrainRootsWhenMapping = true + /** The arguments of a @retains or @retainsByName annotation */ private[cc] def retainedElems(tree: Tree)(using Context): List[Tree] = tree match case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems @@ -32,12 +39,82 @@ def allowUniversalInBoxed(using Context) = /** An exception thrown if a @retains argument is not syntactically a CaptureRef */ class IllegalCaptureRef(tpe: Type) extends Exception +/** Capture checking state, which is stored in a context property */ +class CCState: + + val rhsClosure: mutable.HashSet[Symbol] = new mutable.HashSet + + val levelOwners: mutable.HashSet[Symbol] = new mutable.HashSet + + /** Associates certain symbols (the nesting level owners) with their ccNestingLevel */ + val nestingLevels: mutable.HashMap[Symbol, Int] = new mutable.HashMap + + /** Associates nesting level owners with the local roots valid in their scopes. */ + val localRoots: mutable.HashMap[Symbol, Symbol] = new mutable.HashMap + + /** The last pair of capture reference and capture set where + * the reference could not be added to the set due to a level conflict. + */ + var levelError: Option[(CaptureRef, CaptureSet)] = None + + /** Under saferExceptions: The symbol generated for a try. + * Installed by Setup, removed by CheckCaptures. + */ + val tryBlockOwner: mutable.HashMap[Try, Symbol] = new mutable.HashMap +end CCState + +/** Property key for capture checking state */ +val ccStateKey: Key[CCState] = Key() + +/** The currently valid CCState */ +def ccState(using Context) = ctx.property(ccStateKey).get + +trait FollowAliases extends TypeMap: + def mapOverFollowingAliases(t: Type): Type = t match + case t: LazyRef => + val t1 = this(t.ref) + if t1 ne t.ref then t1 else t + case _ => + val t1 = t.dealiasKeepAnnots + if t1 ne t then + val t2 = this(t1) + if t2 ne t1 then return t2 + mapOver(t) + +class mapRoots(from: CaptureRoot, to: CaptureRoot)(using Context) extends BiTypeMap, FollowAliases: + thisMap => + + def apply(t: Type): Type = + if t eq from then to + else t match + case t: CaptureRoot.Var => + val ta = t.followAlias + if ta ne t then apply(ta) + else from match + case from: TermRef + if t.upperLevel >= from.symbol.ccNestingLevel + && constrainRootsWhenMapping // next two lines do the constraining + && CaptureRoot.isEnclosingRoot(from, t) + && CaptureRoot.isEnclosingRoot(t, from) => to + case from: CaptureRoot.Var if from.followAlias eq t => to + case _ => t + case _ => + mapOverFollowingAliases(t) + + def inverse = mapRoots(to, from) +end mapRoots + extension (tree: Tree) /** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */ - def toCaptureRef(using Context): CaptureRef = tree.tpe match - case ref: CaptureRef => ref - case tpe => throw IllegalCaptureRef(tpe) + def toCaptureRef(using Context): CaptureRef = tree match + case QualifiedRoot(outer) => + ctx.owner.levelOwnerNamed(outer) + .orElse(defn.captureRoot) // non-existing outer roots are reported in Setup's checkQualifiedRoots + .localRoot.termRef + case _ => tree.tpe match + case ref: CaptureRef => ref + case tpe => throw IllegalCaptureRef(tpe) // if this was compiled from cc syntax, problem should have been reported at Typer /** Convert a @retains or @retainsByName annotation tree to the capture set it represents. * For efficience, the result is cached as an Attachment on the tree. @@ -164,7 +241,7 @@ extension (tp: Type) * a by name parameter type, turning the latter into an impure by name parameter type. */ def adaptByNameArgUnderPureFuns(using Context): Type = - if Feature.pureFunsEnabledSomewhere then + if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then AnnotatedType(tp, CaptureAnnotation(CaptureSet.universal, boxed = false)(defn.RetainsByNameAnnot)) else @@ -253,6 +330,91 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox + def isLevelOwner(using Context): Boolean = ccState.levelOwners.contains(sym) + + /** 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.Cap, or they are the rhs of a val definition. + * - classes, if they are not staticOwners + * - _root_ + */ + def levelOwner(using Context): Symbol = + if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass + else if sym.isLevelOwner then sym + else sym.owner.levelOwner + + /** The nesting level of `sym` for the purposes of `cc`, + * -1 for NoSymbol + */ + def ccNestingLevel(using Context): Int = + if sym.exists then + val lowner = sym.levelOwner + ccState.nestingLevels.getOrElseUpdate(lowner, + if lowner.isRoot then 0 else lowner.owner.ccNestingLevel + 1) + else -1 + + /** Optionally, the nesting level of `sym` for the purposes of `cc`, provided + * a capture checker is running. + */ + def ccNestingLevelOpt(using Context): Option[Int] = + if ctx.property(ccStateKey).isDefined then Some(ccNestingLevel) else None + + /** The parameter with type caps.Cap in the leading term parameter section, + * or NoSymbol, if none exists. + */ + def definedLocalRoot(using Context): Symbol = + sym.paramSymss.dropWhile(psyms => psyms.nonEmpty && psyms.head.isType) match + case psyms :: _ => psyms.find(_.info.typeSymbol == defn.Caps_Cap).getOrElse(NoSymbol) + case _ => NoSymbol + + /** The local root corresponding to sym's level owner */ + def localRoot(using Context): Symbol = + val owner = sym.levelOwner + assert(owner.exists) + def newRoot = newSymbol(if owner.isClass then newLocalDummy(owner) else owner, + nme.LOCAL_CAPTURE_ROOT, Synthetic, defn.Caps_Cap.typeRef, nestingLevel = owner.ccNestingLevel) + def lclRoot = + if owner.isTerm then owner.definedLocalRoot.orElse(newRoot) + else newRoot + ccState.localRoots.getOrElseUpdate(owner, lclRoot) + + /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. + * If name refers to a val that has a closure as rhs, we return the closure as level + * owner. + */ + def levelOwnerNamed(name: String)(using Context): Symbol = + def recur(owner: Symbol, prev: Symbol): Symbol = + if owner.name.toString == name then + if owner.isLevelOwner then owner + else if owner.isTerm && !owner.isOneOf(Method | Module) && prev.exists then prev + else NoSymbol + else if owner == defn.RootClass then + NoSymbol + else + val prev1 = if owner.isAnonymousFunction && owner.isLevelOwner then owner else NoSymbol + recur(owner.owner, prev1) + recur(sym, NoSymbol) + .showing(i"find outer $sym [ $name ] = $result", capt) + + def maxNested(other: Symbol)(using Context): Symbol = + if sym.ccNestingLevel < other.ccNestingLevel then other else sym + /* does not work yet, we do mix sets with different levels, for instance in cc-this.scala. + else if sym.ccNestingLevel > other.ccNestingLevel then sym + else + assert(sym == other, i"conflicting symbols at same nesting level: $sym, $other") + sym + */ + + def minNested(other: Symbol)(using Context): Symbol = + if sym.ccNestingLevel > other.ccNestingLevel then other else sym + +extension (tp: TermRef | ThisType) + /** The nesting level of this reference as defined by capture checking */ + def ccNestingLevel(using Context): Int = tp match + case tp: TermRef => tp.symbol.ccNestingLevel + case tp: ThisType => tp.cls.ccNestingLevel + extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala new file mode 100644 index 000000000000..4e1241518963 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -0,0 +1,112 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* +import Hashable.Binders +import printing.Showable +import util.SimpleIdentitySet +import Decorators.i +import scala.annotation.constructorOnly + +type CaptureRoot = TermRef | CaptureRoot.Var + +object CaptureRoot: + + case class Var(owner: Symbol, source: Symbol = NoSymbol)(using @constructorOnly ictx: Context) extends CaptureRef, Showable: + + var upperBound: Symbol = owner + var lowerBound: Symbol = NoSymbol + var upperLevel: Int = owner.ccNestingLevel + var lowerLevel: Int = Int.MinValue + private[CaptureRoot] var lowerRoots: SimpleIdentitySet[Var] = SimpleIdentitySet.empty + private[CaptureRoot] var upperRoots: SimpleIdentitySet[Var] = SimpleIdentitySet.empty + private[CaptureRoot] var alias: CaptureRoot = this + + override def localRootOwner(using Context) = owner + override def isTrackableRef(using Context): Boolean = true + override def captureSetOfInfo(using Context) = CaptureSet.universal + + def setAlias(target: CaptureRoot) = + alias = target + + def followAlias: CaptureRoot = alias match + case alias: Var if alias ne this => alias.followAlias + case _ => this + + def locallyConsistent = + lowerLevel <= upperLevel + && lowerRoots.forall(_.upperLevel <= upperLevel) + && upperRoots.forall(_.lowerLevel >= lowerLevel) + + def computeHash(bs: Binders): Int = hash + def hash: Int = System.identityHashCode(this) + def underlying(using Context): Type = defn.Caps_Cap.typeRef + end Var + + def isEnclosingRoot(c1: CaptureRoot, c2: CaptureRoot)(using Context): Boolean = + if c1 eq c2 then return true + c1 match + case c1: Var if c1.alias ne c1 => return isEnclosingRoot(c1.alias, c2) + case _ => + c2 match + case c2: Var if c2.alias ne c2 => return isEnclosingRoot(c1, c2.alias) + case _ => + (c1, c2) match + case (c1: TermRef, c2: TermRef) => + c1.ccNestingLevel <= c2.ccNestingLevel + case (c1: TermRef, c2: Var) => + val level1 = c1.ccNestingLevel + if level1 <= c2.lowerLevel then + true // no change + else if level1 <= c2.upperLevel && c2.upperRoots.forall(isEnclosingRoot(c1, _)) then + if level1 == c2.upperLevel then + c2.alias = c1 + else + c2.lowerBound = c1.symbol + c2.lowerLevel = level1 + true + else false + case (c1: Var, c2: TermRef) => + val level2 = c2.ccNestingLevel + if c1.upperLevel <= level2 then + true // no change + else if c1.lowerLevel <= level2 && c1.lowerRoots.forall(isEnclosingRoot(_, c2)) then + if level2 == c1.lowerLevel then + c1.alias = c2 + else + c1.upperBound = c2.symbol + c1.upperLevel = level2 + true + else false + case (c1: Var, c2: Var) => + if c1.upperRoots.contains(c2) then + true // no change + else if c1.lowerLevel > c2.upperLevel then + false // local inconsistency + else + c1.upperRoots += c2 // set early to prevent infinite looping + if c1.lowerRoots.forall(isEnclosingRoot(_, c2)) + && c2.upperRoots.forall(isEnclosingRoot(c1, _)) + then + if c1.lowerRoots.contains(c2) then + val c2a = c2.followAlias + if c2a ne c1 then c1.alias = c2a + else + if c1.upperLevel > c2.upperLevel then + c1.upperBound = c2.upperBound + c1.upperLevel = c2.upperLevel + if c2.lowerLevel < c1.lowerLevel then + c2.lowerBound = c1.lowerBound + c2.lowerLevel = c1.lowerLevel + c2.lowerRoots += c1 + true + else + c1.upperRoots -= c2 + false + end isEnclosingRoot +end CaptureRoot + + + diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 84a04c13a91f..5fdbea2a1bc6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -12,7 +12,8 @@ import annotation.internal.sharable import reporting.trace import printing.{Showable, Printer} import printing.Texts.* -import util.{SimpleIdentitySet, Property} +import util.{SimpleIdentitySet, Property, optional}, optional.{break, ?} +import typer.ErrorReporting.Addenda import util.common.alwaysTrue import scala.collection.mutable import config.Config.ccAllowUnsoundMaps @@ -55,6 +56,11 @@ sealed abstract class CaptureSet extends Showable: */ def isAlwaysEmpty: Boolean + /** The level owner in which the set is defined. Sets can only take + * elements with nesting level up to the cc-nestinglevel of owner. + */ + def owner: Symbol + /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -112,21 +118,40 @@ sealed abstract class CaptureSet extends Showable: if accountsFor(elem) then CompareResult.OK else addNewElems(elem.singletonCaptureSet.elems, origin) - /* x subsumes y if x is the same as y, or x is a this reference and y refers to a field of x */ - extension (x: CaptureRef) private def subsumes(y: CaptureRef) = - (x eq y) - || y.match - case y: TermRef => y.prefix eq x - case _ => false + /* x subsumes y if one of the following is true: + * - x is the same as y, + * - x is a this reference and y refers to a field of x + * - x and y are local roots and y is an enclosing root of x + * - the LooseRootChecking property is asserted, and either `x` is `cap` + * or `x` is a local root and y is `cap`. + */ + extension (x: CaptureRef)(using Context) + private def subsumes(y: CaptureRef) = + (x eq y) + || y.match + case y: TermRef => (y.prefix eq x) || x.isRootIncluding(y) + case y: CaptureRoot.Var => x.isRootIncluding(y) + case _ => false + || (x.isGenericRootCapability || y.isGenericRootCapability && x.isRootCapability) + && ctx.property(LooseRootChecking).isDefined + + private def isRootIncluding(y: CaptureRoot) = + x.isLocalRootCapability && y.isLocalRootCapability + && CaptureRoot.isEnclosingRoot(y, x.asInstanceOf[CaptureRoot]) + end extension /** {x} <:< this where <:< is subcapturing, but treating all variables * as frozen. */ def accountsFor(x: CaptureRef)(using Context): Boolean = - reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true) { - elems.exists(_.subsumes(x)) - || !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK - } + if comparer.isInstanceOf[ExplainingTypeComparer] then // !!! DEBUG + reporting.trace.force(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true): + elems.exists(_.subsumes(x)) + || !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK + else + reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true): + elems.exists(_.subsumes(x)) + || !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK /** A more optimistic version of accountsFor, which does not take variable supersets * of the `x` reference into account. A set might account for `x` if it accounts @@ -191,7 +216,8 @@ 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.elems ++ that.elems).addAsDependentTo(this).addAsDependentTo(that) + else Var(this.owner.maxNested(that.owner), this.elems ++ that.elems) + .addAsDependentTo(this).addAsDependentTo(that) /** The smallest superset (via <:<) of this capture set that also contains `ref`. */ @@ -276,7 +302,9 @@ sealed abstract class CaptureSet extends Showable: if isUniversal then handler() this - /** Invoke handler on the elements to check wellformedness of the capture set */ + /** Invoke handler on the elements to ensure wellformedness of the capture set. + * The handler might add additional elements to the capture set. + */ def ensureWellformed(handler: List[CaptureRef] => Context ?=> Unit)(using Context): this.type = handler(elems.toList) this @@ -300,15 +328,18 @@ sealed abstract class CaptureSet extends Showable: /** This capture set with a description that tells where it comes from */ def withDescription(description: String): CaptureSet - /** The provided description (using `withDescription`) for this capture set or else "" */ + /** The provided description (set via `withDescription`) for this capture set or else "" */ def description: String + /** More info enabled by -Y flags */ + def optionalInfo(using Context): String = "" + /** A regular @retains or @retainsByName annotation with the elements of this set as arguments. */ def toRegularAnnotation(cls: Symbol)(using Context): Annotation = Annotation(CaptureAnnotation(this, boxed = false)(cls).tree) override def toText(printer: Printer): Text = - Str("{") ~ Text(elems.toList.map(printer.toTextCaptureRef), ", ") ~ Str("}") ~~ description + printer.toTextCaptureSet(this) ~~ description object CaptureSet: type Refs = SimpleIdentitySet[CaptureRef] @@ -339,6 +370,13 @@ object CaptureSet: def apply(elems: Refs)(using Context): CaptureSet.Const = if elems.isEmpty then empty else Const(elems) + /** If this context property is asserted, we conflate capture roots in subCapture + * tests. Specifically, `cap` then subsumes everything and all local roots subsume `cap`. + * This generally not sound. We currently use loose root checking only in self type + * conformance tests in CheckCaptures.checkSelfTypes. + */ + val LooseRootChecking: Property.Key[Unit] = Property.Key() + /** The subclass of constant capture sets with given elements `elems` */ class Const private[CaptureSet] (val elems: Refs, val description: String = "") extends CaptureSet: def isConst = true @@ -353,6 +391,8 @@ object CaptureSet: def withDescription(description: String): Const = Const(elems, description) + def owner = NoSymbol + override def toString = elems.toString end Const @@ -371,16 +411,23 @@ object CaptureSet: end Fluid /** The subclass of captureset variables with given initial elements */ - class Var(initialElems: Refs = emptySet) extends CaptureSet: + class Var(directOwner: Symbol, initialElems: Refs = emptySet)(using @constructorOnly ictx: Context) extends CaptureSet: /** A unique identification number for diagnostics */ val id = varId += 1 varId + override val owner = directOwner.levelOwner + /** A variable is solved if it is aproximated to a from-then-on constant set. */ private var isSolved: Boolean = false + private var ownLevelCache = -1 + private def ownLevel(using Context) = + if ownLevelCache == -1 then ownLevelCache = owner.ccNestingLevel + ownLevelCache + /** The elements currently known to be in the set */ var elems: Refs = initialElems @@ -400,6 +447,8 @@ object CaptureSet: var description: String = "" + private var triedElem: Option[CaptureRef] = None + /** Record current elements in given VarState provided it does not yet * contain an entry for this variable. */ @@ -425,7 +474,16 @@ object CaptureSet: deps = state.deps(this) def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - if !isConst && recordElemsState() then + if isConst || !recordElemsState() then + CompareResult.fail(this) // fail if variable is solved or given VarState is frozen + else if newElems.exists(!levelOK(_)) then + val res = widenCaptures(newElems) match + case Some(newElems1) => tryInclude(newElems1, origin) + case None => CompareResult.fail(this) + if !res.isOK then recordLevelError() + res + else + //assert(id != 2, newElems) elems ++= newElems if isUniversal then rootAddedHandler() newElemAddedHandler(newElems.toList) @@ -433,8 +491,32 @@ object CaptureSet: (CompareResult.OK /: deps) { (r, dep) => r.andAlso(dep.tryInclude(newElems, this)) } - else // fail if variable is solved or given VarState is frozen - CompareResult.fail(this) + + private def recordLevelError()(using Context): Unit = + for elem <- triedElem do + ccState.levelError = Some((elem, this)) + + private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match + case elem: (TermRef | ThisType) => elem.ccNestingLevel <= ownLevel + case elem: CaptureRoot.Var => CaptureRoot.isEnclosingRoot(elem, owner.localRoot.termRef) + case _ => true + + private def widenCaptures(elems: Refs)(using Context): Option[Refs] = + val res = optional: + (SimpleIdentitySet[CaptureRef]() /: elems): (acc, elem) => + if levelOK(elem) then acc + elem + else + val saved = triedElem + triedElem = triedElem.orElse(Some(elem)) + if elem.isRootCapability then break() + val res = acc ++ widenCaptures(elem.captureSetOfInfo.elems).? + triedElem = saved // reset only in case of success, leave as is on error + res + def resStr = res match + case Some(refs) => i"${refs.toList}" + case None => "FAIL" + capt.println(i"widen captures ${elems.toList} for $this at $owner = $resStr") + res def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = if (cs eq this) || cs.isUniversal || isConst then @@ -460,8 +542,10 @@ object CaptureSet: * of this set. The universal set {cap} is a sound fallback. */ final def upperApprox(origin: CaptureSet)(using Context): CaptureSet = - if computingApprox then universal - else if isConst then this + if isConst then this + else if elems.exists(_.isRootCapability) then + CaptureSet(elems.filter(_.isRootCapability).toList*) + else if computingApprox then universal else computingApprox = true try computeApprox(origin).ensuring(_.isConst) @@ -478,6 +562,7 @@ object CaptureSet: def solve()(using Context): Unit = if !isConst then val approx = upperApprox(empty) + .showing(i"solve $this = $result", capt) //println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}") val newElems = approx.elems -- elems if newElems.isEmpty || addNewElems(newElems, empty)(using ctx, VarState()).isOK then @@ -489,11 +574,24 @@ object CaptureSet: deps.foreach(_.propagateSolved()) def withDescription(description: String): this.type = - this.description = - if this.description.isEmpty then description - else s"${this.description} and $description" + this.description = this.description.join(" and ", description) this + /** Adds variables to the ShownVars context property if that exists, which + * establishes a record of all variables printed in an error message. + * Returns variable `ids` under -Ycc-debug, and owner/nesting level info + * under -Yprint-level. + */ + override def optionalInfo(using Context): String = + for vars <- ctx.property(ShownVars) do vars += this + val debugInfo = + if !isConst && ctx.settings.YccDebug.value then ids else "" + val nestingInfo = + if ctx.settings.YprintLevel.value + then s"" + else "" + debugInfo ++ nestingInfo + /** Used for diagnostics and debugging: A string that traces the creation * history of a variable by following source links. Each variable on the * path is characterized by the variable's id and the first letter of the @@ -506,21 +604,20 @@ object CaptureSet: case _ => "" s"$id${getClass.getSimpleName.nn.take(1)}$trail" - /** Adds variables to the ShownVars context property if that exists, which - * establishes a record of all variables printed in an error message. - * Prints variables wih ids under -Ycc-debug. - */ - override def toText(printer: Printer): Text = inContext(printer.printerContext) { - for vars <- ctx.property(ShownVars) do vars += this - super.toText(printer) ~ (Str(ids) provided !isConst && ctx.settings.YccDebug.value) - } - override def toString = s"Var$id$elems" end Var + /** A variable used in refinements of class parameters. See `addCaptureRefinements`. + */ + class RefiningVar(owner: Symbol, val getter: Symbol)(using @constructorOnly ictx: Context) extends Var(owner): + description = i"of parameter ${getter.name} of ${getter.owner}" + override def optionalInfo(using Context): String = + super.optionalInfo + ( + if ctx.settings.YprintDebug.value then "(refining)" else "") + /** A variable that is derived from some other variable via a map or filter. */ - abstract class DerivedVar(initialElems: Refs)(using @constructorOnly ctx: Context) - extends Var(initialElems): + abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context) + extends Var(owner, initialElems): // For debugging: A trace where a set was created. Note that logically it would make more // sense to place this variable in Mapped, but that runs afoul of the initializatuon checker. @@ -546,7 +643,7 @@ object CaptureSet: */ class Mapped private[CaptureSet] (val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context) - extends DerivedVar(initial.elems): + extends DerivedVar(source.owner, initial.elems): addAsDependentTo(initial) // initial mappings could change by propagation private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap] @@ -612,7 +709,7 @@ object CaptureSet: */ final class BiMapped private[CaptureSet] (val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) - extends DerivedVar(initialElems): + extends DerivedVar(source.owner, initialElems): override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = if origin eq source then @@ -633,7 +730,7 @@ object CaptureSet: */ override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = val supApprox = super.computeApprox(this) - if source eq origin then supApprox.map(bimap.inverseTypeMap) + if source eq origin then supApprox.map(bimap.inverse) else source.upperApprox(this).map(bimap) ** supApprox override def toString = s"BiMapped$id($source, elems = $elems)" @@ -642,7 +739,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.elems.filter(p)): + extends DerivedVar(source.owner, source.elems.filter(p)): override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = val filtered = newElems.filter(p) @@ -673,7 +770,7 @@ object CaptureSet: extends Filtered(source, !other.accountsFor(_)) class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context) - extends Var(elemIntersection(cs1, cs2)): + extends Var(cs1.owner.minNested(cs2.owner), elemIntersection(cs1, cs2)): addAsDependentTo(cs1) addAsDependentTo(cs2) deps += cs1 @@ -870,10 +967,12 @@ object CaptureSet: tp.captureSet case tp: TermParamRef => tp.captureSet - case _: TypeRef => - if tp.classSymbol.hasAnnotation(defn.CapabilityAnnot) then universal else empty + case tp: TypeRef => + if tp.typeSymbol == defn.Caps_Cap then universal else empty case _: TypeParamRef => empty + case CapturingType(parent, refs: RefiningVar) => + refs case CapturingType(parent, refs) => recur(parent) ++ refs case tpd @ defn.RefinedFunctionOf(rinfo: MethodType) if followResult => @@ -933,4 +1032,19 @@ object CaptureSet: println(i" ${cv.show.padTo(20, ' ')} :: ${cv.deps.toList}%, %") } else op + + def levelErrors: Addenda = new Addenda: + override def toAdd(using Context) = + for + state <- ctx.property(ccStateKey).toList + (ref, cs) <- state.levelError + yield + val levelStr = ref match + case ref: (TermRef | ThisType) => i", defined at level ${ref.ccNestingLevel}" + case _ => "" + i""" + | + |Note that reference ${ref}$levelStr + |cannot be included in outer capture set $cs, defined at level ${cs.owner.nestingLevel} in ${cs.owner}""" + end CaptureSet diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 66a7899a6bd0..d5bd8522ca92 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -12,12 +12,13 @@ import ast.{tpd, untpd, Trees} import Trees.* import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} +import typer.ErrorReporting.Addenda import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property} import transform.SymUtils.* import transform.{Recheck, PreRecheck} import Recheck.* import scala.collection.mutable -import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap} +import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, LooseRootChecking} import StdNames.nme import NameKinds.DefaultGetterName import reporting.trace @@ -45,11 +46,11 @@ object CheckCaptures: enum EnvKind: case Regular // normal case - case NestedInOwner // environment is a temporary one nested in the owner's environment, + case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). case ClosureResult // environment is for the result of a closure - case Boxed // envrionment is inside a box (in which case references are not counted) + case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. * @param owner the current owner @@ -73,12 +74,11 @@ object CheckCaptures: /** Similar normal substParams, but this is an approximating type map that * maps parameters in contravariant capture sets to the empty set. - * TODO: check what happens with non-variant. */ final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap: /** This SubstParamsMap is exact if `to` only contains `CaptureRef`s. */ - private val isExactSubstitution: Boolean = to.forall(_.isInstanceOf[CaptureRef]) + private val isExactSubstitution: Boolean = to.forall(_.isTrackableRef) /** As long as this substitution is exact, there is no need to create `Range`s when mapping invariant positions. */ override protected def needsRangeIfInvariant(refs: CaptureSet): Boolean = !isExactSubstitution @@ -96,6 +96,39 @@ object CheckCaptures: mapOver(tp) end SubstParamsMap + final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context) + extends BiTypeMap: + thisMap => + + def apply(tp: Type): Type = tp match + case tp: ParamRef => + if tp.binder == from then to(tp.paramNum) else tp + case tp: NamedType => + if tp.prefix `eq` NoPrefix then tp + else tp.derivedSelect(apply(tp.prefix)) + case _: ThisType => + tp + case _ => + mapOver(tp) + + lazy val inverse = new BiTypeMap: + def apply(tp: Type): Type = tp match + case tp: NamedType => + var idx = 0 + var to1 = to + while idx < to.length && (tp ne to(idx)) do + idx += 1 + to1 = to1.tail + if idx < to.length then from.paramRefs(idx) + else if tp.prefix `eq` NoPrefix then tp + else tp.derivedSelect(apply(tp.prefix)) + case _: ThisType => + tp + case _ => + mapOver(tp) + def inverse = thisMap + end SubstParamsBiMap + /** Check that a @retains annotation only mentions references that can be tracked. * This check is performed at Typer. */ @@ -105,12 +138,15 @@ object CheckCaptures: report.error(em"Singleton type $parent cannot have capture set", parent.srcPos) case _ => for elem <- retainedElems(ann) do - elem.tpe match - case ref: CaptureRef => - if !ref.canBeTracked 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 QualifiedRoot(outer) => + // Will be checked by Setup's checkOuterRoots + case _ => 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", elem.srcPos) + case tpe => + report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) /** If `tp` is a capturing type, check that all references it mentions have non-empty * capture sets. Also: warn about redundant capture annotations. @@ -122,7 +158,7 @@ object CheckCaptures: if ref.captureSetOfInfo.elems.isEmpty then report.error(em"$ref cannot be tracked since its capture set is empty", pos) else if parent.captureSet.accountsFor(ref) then - report.warning(em"redundant capture: $parent already accounts for $ref", pos) + report.warning(em"redundant capture: $parent already accounts for $ref in $tp", pos) case _ => /** Warn if `ann`, which is the tree of a @retains annotation, defines some elements that @@ -130,29 +166,19 @@ object CheckCaptures: * Note: We need to perform the check on the original annotation rather than its * capture set since the conversion to a capture set already eliminates redundant elements. */ - def warnIfRedundantCaptureSet(ann: Tree)(using Context): Unit = + def warnIfRedundantCaptureSet(ann: Tree, tpt: Tree)(using Context): Unit = var retained = retainedElems(ann).toArray for i <- 0 until retained.length do - val ref = retained(i).toCaptureRef + val refTree = retained(i) + val ref = refTree.toCaptureRef val others = for j <- 0 until retained.length if j != i yield retained(j).toCaptureRef val remaining = CaptureSet(others*) if remaining.accountsFor(ref) then - report.warning(em"redundant capture: $remaining already accounts for $ref", ann.srcPos) - - /** Report an error if some part of `tp` contains the root capability in its capture set */ - def disallowRootCapabilitiesIn(tp: Type, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = - val check = new TypeTraverser: - def traverse(t: Type) = - if variance >= 0 then - t.captureSet.disallowRootCapability: () => - def part = if t eq tp then "" else i"the part $t of " - report.error( - em"""$what cannot $have $tp since - |${part}that type captures the root capability `cap`. - |$addendum""", - pos) - traverseChildren(t) - check.traverse(tp) + val srcTree = + if refTree.span.exists then refTree + else if ann.span.exists then ann + else tpt + report.warning(em"redundant capture: $remaining already accounts for $ref", srcTree.srcPos) /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -168,6 +194,8 @@ class CheckCaptures extends Recheck, SymTransformer: def newRechecker()(using Context) = CaptureChecker(ctx) + private val state = new CCState + override def run(using Context): Unit = if Feature.ccEnabled then super.run @@ -176,6 +204,8 @@ class CheckCaptures extends Recheck, SymTransformer: if Synthetics.needsTransform(sym) then Synthetics.transform(sym, toCC = false) else super.transformSym(sym) + override def printingContext(ctx: Context) = ctx.withProperty(ccStateKey, Some(new CCState)) + class CaptureChecker(ictx: Context) extends Rechecker(ictx): import ast.tpd.* @@ -194,6 +224,16 @@ class CheckCaptures extends Recheck, SymTransformer: if variance < 0 then capt.println(i"solving $t") refs.solve() + if ctx.owner.isLevelOwner then + // instantiate root vars with upper bound ctx.owner to its local root + for ref <- refs.elems do ref match + case ref: CaptureRoot.Var => ref.followAlias match + case rv: CaptureRoot.Var if rv.upperBound == ctx.owner => + val inst = ctx.owner.localRoot.termRef + capt.println(i"instantiate $rv to $inst") + rv.setAlias(inst) + case _ => + case _ => traverse(parent) case t @ defn.RefinedFunctionOf(rinfo) => traverse(rinfo) @@ -215,23 +255,35 @@ class CheckCaptures extends Recheck, SymTransformer: def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2") - /** Check subcapturing `{elem} <: cs`, report error on failure */ - def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos)(using Context) = - val res = elem.singletonCaptureSet.subCaptures(cs, frozen = false) + def checkOK(res: CompareResult, prefix: => String, pos: SrcPos, provenance: => String = "")(using Context): Unit = if !res.isOK then - report.error(em"$elem cannot be referenced here; it is not included in the allowed capture set ${res.blocking}", pos) + def toAdd: String = CaptureSet.levelErrors.toAdd.mkString + def descr: String = + val d = res.blocking.description + if d.isEmpty then provenance else "" + report.error(em"$prefix included in the allowed capture set ${res.blocking}$descr$toAdd", pos) + + /** Check subcapturing `{elem} <: cs`, report error on failure */ + def checkElem(elem: CaptureRef, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = + checkOK( + elem.singletonCaptureSet.subCaptures(cs, frozen = false), + i"$elem cannot be referenced here; it is not", + pos, provenance) /** Check subcapturing `cs1 <: cs2`, report error on failure */ - def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos)(using Context) = - val res = cs1.subCaptures(cs2, frozen = false) - if !res.isOK then - def header = - if cs1.elems.size == 1 then i"reference ${cs1.elems.toList}%, % is not" - else i"references $cs1 are not all" - report.error(em"$header included in allowed capture set ${res.blocking}", pos) + def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) = + checkOK( + cs1.subCaptures(cs2, frozen = false), + if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head} is not" + else i"references $cs1 are not all", + pos, provenance) /** The current environment */ - private var curEnv: Env = Env(NoSymbol, EnvKind.Regular, CaptureSet.empty, null) + private var curEnv: Env = inContext(ictx): + Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) + + /** Currently checked closures and their expected types, used for error reporting */ + private var openClosures: List[(Symbol, Type)] = Nil private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() @@ -240,7 +292,8 @@ class CheckCaptures extends Recheck, SymTransformer: */ def capturedVars(sym: Symbol)(using Context) = myCapturedVars.getOrElseUpdate(sym, - if sym.ownersIterator.exists(_.isTerm) then CaptureSet.Var() + if sym.ownersIterator.exists(_.isTerm) then + CaptureSet.Var(if sym.isConstructor then sym.owner.owner else sym.owner) else CaptureSet.empty) /** For all nested environments up to `limit` or a closed environment perform `op`, @@ -258,6 +311,19 @@ class CheckCaptures extends Recheck, SymTransformer: recur(nextEnv, skip = env.kind == EnvKind.ClosureResult) recur(curEnv, skip = false) + /** A description where this environment comes from */ + private def provenance(env: Env)(using Context): String = + val owner = env.owner + if owner.isAnonymousFunction then + val expected = openClosures + .find(_._1 == owner) + .map(_._2) + .getOrElse(owner.info.toFunctionType()) + i"\nof an enclosing function literal with expected type $expected" + else + i"\nof the enclosing ${owner.showLocated}" + + /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. */ @@ -267,7 +333,7 @@ class CheckCaptures extends Recheck, SymTransformer: if ref.isTracked then forallOuterEnvsUpTo(sym.enclosure): env => capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") - checkElem(ref, env.captured, pos) + checkElem(ref, env.captured, pos, provenance(env)) /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside @@ -284,7 +350,7 @@ class CheckCaptures extends Recheck, SymTransformer: case ref: ThisType => isVisibleFromEnv(ref.cls) case _ => false capt.println(i"Include call capture $included in ${env.owner}") - checkSubset(included, env.captured, pos) + checkSubset(included, env.captured, pos, provenance(env)) /** Include references captured by the called method in the current environment stack */ def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = @@ -307,7 +373,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => mapOver(t) if variance > 0 then t1 - else Setup.decorate(t1, Function.const(CaptureSet.Fluid)) + else setup.decorate(t1, mapRoots = false, addedSet = Function.const(CaptureSet.Fluid)) def isPreCC(sym: Symbol): Boolean = sym.isTerm && sym.maybeOwner.isClass @@ -373,6 +439,16 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") + override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = + val srcRoot = + if meth.isConstructor && meth.owner.source == ctx.compilationUnit.source + then meth.owner.localRoot + else defn.captureRoot + val mapr = mapRoots(srcRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner, meth)) + funtpe.derivedLambdaType( + paramInfos = funtpe.paramInfos.mapConserve(mapr), + resType = mapr(funtpe.resType)).asInstanceOf[MethodType] + /** A specialized implementation of the apply rule. * * E |- f: Ra ->Cf Rr^Cr @@ -397,7 +473,7 @@ class CheckCaptures extends Recheck, SymTransformer: if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) + val argType0 = recheck(arg, pt.capturing(CaptureSet(CaptureRoot.Var(ctx.owner)))) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -408,10 +484,16 @@ class CheckCaptures extends Recheck, SymTransformer: else if meth == defn.Caps_unsafeUnbox then mapArgUsing(_.forceBoxStatus(false)) else if meth == defn.Caps_unsafeBoxFunArg then - mapArgUsing: + def forceBox(tp: Type): Type = tp match case defn.FunctionOf(paramtpe :: Nil, restpe, isContextual) => defn.FunctionOf(paramtpe.forceBoxStatus(true) :: Nil, restpe, isContextual) - + case tp @ RefinedType(parent, rname, rinfo: MethodType) => + tp.derivedRefinedType(parent, rname, + rinfo.derivedLambdaType( + paramInfos = rinfo.paramInfos.map(_.forceBoxStatus(true)))) + case tp @ CapturingType(parent, refs) => + tp.derivedCapturingType(forceBox(parent), refs) + mapArgUsing(forceBox) else super.recheckApply(tree, pt) match case appType @ CapturingType(appType1, refs) => @@ -431,6 +513,10 @@ class CheckCaptures extends Recheck, SymTransformer: case appType => appType end recheckApply + private def isDistinct(xs: List[Type]): Boolean = xs match + case x :: xs1 => xs1.isEmpty || !xs1.contains(x) && isDistinct(xs1) + case Nil => true + /** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`. * This means: * - Instantiate result type with actual arguments @@ -438,11 +524,19 @@ class CheckCaptures extends Recheck, SymTransformer: * - remember types of arguments corresponding to tracked * parameters in refinements. * - add capture set of instantiated class to capture set of result type. + * If all argument types are mutually disfferent trackable capture references, use a BiTypeMap, + * since that is more precise. Otherwise use a normal idempotent map, which might lose information + * in the case where the result type contains captureset variables that are further + * constrained afterwards. */ override def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = val ownType = - if mt.isResultDependent then SubstParamsMap(mt, argTypes)(mt.resType) - else mt.resType + if !mt.isResultDependent then + mt.resType + else if argTypes.forall(_.isTrackableRef) && isDistinct(argTypes) then + SubstParamsBiMap(mt, argTypes)(mt.resType) + else + SubstParamsMap(mt, argTypes)(mt.resType) if sym.isConstructor then val cls = sym.owner.asClass @@ -485,63 +579,42 @@ class CheckCaptures extends Recheck, SymTransformer: else ownType end instantiate - override def recheckClosure(tree: Closure, pt: Type)(using Context): Type = + override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type = val cs = capturedVars(tree.meth.symbol) capt.println(i"typing closure $tree with cvs $cs") - super.recheckClosure(tree, pt).capturing(cs) - .showing(i"rechecked $tree / $pt = $result", capt) - - /** Additionally to normal processing, update types of closures if the expected type - * is a function with only pure parameters. In that case, make the anonymous function - * also have the same parameters as the prototype. - * TODO: Develop a clearer rationale for this. - * TODO: Can we generalize this to arbitrary parameters? - * Currently some tests fail if we do this. (e.g. neg.../stackAlloc.scala, others) - */ - override def recheckBlock(block: Block, pt: Type)(using Context): Type = - block match - case closureDef(mdef) => - pt.dealias match - case defn.FunctionOf(ptformals, _, _) - if ptformals.nonEmpty && ptformals.forall(_.captureSet.isAlwaysEmpty) => - // Redo setup of the anonymous function so that formal parameters don't - // get capture sets. This is important to avoid false widenings to `cap` - // when taking the base type of the actual closures's dependent function - // type so that it conforms to the expected non-dependent function type. - // See withLogFile.scala for a test case. - val meth = mdef.symbol - // First, undo the previous setup which installed a completer for `meth`. - atPhase(preRecheckPhase.prev)(meth.denot.copySymDenotation()) - .installAfter(preRecheckPhase) - - // Next, update all parameter symbols to match expected formals - meth.paramSymss.head.lazyZip(ptformals).foreach: (psym, pformal) => - psym.updateInfoBetween(preRecheckPhase, thisPhase, pformal.mapExprType) - - // Next, update types of parameter ValDefs - mdef.paramss.head.lazyZip(ptformals).foreach: (param, pformal) => - val ValDef(_, tpt, _) = param: @unchecked - tpt.rememberTypeAlways(pformal) - - // Next, install a new completer reflecting the new parameters for the anonymous method - val mt = meth.info.asInstanceOf[MethodType] - val completer = new LazyType: - def complete(denot: SymDenotation)(using Context) = - denot.info = mt.companion(ptformals, mdef.tpt.knownType) - .showing(i"simplify info of $meth to $result", capt) - recheckDef(mdef, meth) - meth.updateInfoBetween(preRecheckPhase, thisPhase, completer) - case _ => - mdef.rhs match - case rhs @ closure(_, _, _) => - // In a curried closure `x => y => e` don't leak capabilities retained by - // the second closure `y => e` into the first one. This is an approximation - // of the CC rule which says that a closure contributes captures to its - // environment only if a let-bound reference to the closure is used. - mdef.rhs.putAttachment(ClosureBodyValue, ()) - case _ => + super.recheckClosure(tree, pt, forceDependent).capturing(cs) + .showing(i"rechecked closure $tree / $pt = $result", capt) + + override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = + mdef.rhs match + case rhs @ closure(_, _, _) => + // In a curried closure `x => y => e` don't leak capabilities retained by + // the second closure `y => e` into the first one. This is an approximation + // of the CC rule which says that a closure contributes captures to its + // environment only if a let-bound reference to the closure is used. + mdef.rhs.putAttachment(ClosureBodyValue, ()) case _ => - super.recheckBlock(block, pt) + + // Constrain closure's parameters and result from the expected type before + // rechecking the body. + openClosures = (mdef.symbol, pt) :: openClosures + try + 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 + // its internals early since that would give error messages in generated code + // which are less intelligible. + // 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) + //else report.warning(i"skip test $mdef", mdef.srcPos) + recheckDef(mdef, mdef.symbol) + //println(i"RECHECK CLOSURE ${mdef.symbol.info}") + res + finally + openClosures = openClosures.tail + end recheckClosureBlock override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit = try @@ -559,7 +632,8 @@ class CheckCaptures extends Recheck, SymTransformer: if !Synthetics.isExcluded(sym) then val saved = curEnv val localSet = capturedVars(sym) - if !localSet.isAlwaysEmpty then curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) + if !localSet.isAlwaysEmpty then + curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) try super.recheckDefDef(tree, sym) finally interpolateVarsIn(tree.tpt) @@ -576,7 +650,8 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv val localSet = capturedVars(cls) for parent <- impl.parents do // (1) - checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos) + checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos, + i"\nof the references allowed to be captured by $cls") if !localSet.isAlwaysEmpty then curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls") @@ -607,13 +682,14 @@ class CheckCaptures extends Recheck, SymTransformer: super.recheckTyped(tree) override def recheckTry(tree: Try, pt: Type)(using Context): Type = - val tp = super.recheckTry(tree, pt) - if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then - disallowRootCapabilitiesIn(tp, - "Result of `try`", "have type", - "This is often caused by a locally generated exception capability leaking as part of its result.", - tree.srcPos) - tp + val tryOwner = ccState.tryBlockOwner.remove(tree).getOrElse(ctx.owner) + val saved = curEnv + curEnv = Env(tryOwner, EnvKind.Regular, CaptureSet.Var(curEnv.owner), curEnv) + try + inContext(ctx.withOwner(tryOwner)): + super.recheckTry(tree, pt) + finally + curEnv = saved /* Currently not needed, since capture checking takes place after ElimByName. * Keep around in case we need to get back to it @@ -640,9 +716,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) + curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner), curEnv) case _ if tree.hasAttachment(ClosureBodyValue) => - curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(), curEnv) + curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv) case _ => val res = try super.recheck(tree, pt) @@ -659,13 +735,10 @@ class CheckCaptures extends Recheck, SymTransformer: * of simulated boxing and unboxing. */ override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = - val typeToCheck = tree match - case _: Ident | _: Select | _: Apply | _: TypeApply if tree.symbol.unboxesResult => - tpe - case _: Try => - tpe - case _ => - NoType + def needsUniversalCheck = tree match + case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult + case _: Try => true + case _ => false def checkNotUniversal(tp: Type): Unit = tp.widenDealias match case wtp @ CapturingType(parent, refs) => refs.disallowRootCapability { () => @@ -676,25 +749,44 @@ class CheckCaptures extends Recheck, SymTransformer: } checkNotUniversal(parent) case _ => - if !allowUniversalInBoxed then checkNotUniversal(typeToCheck) + if !allowUniversalInBoxed && needsUniversalCheck then + checkNotUniversal(tpe) super.recheckFinish(tpe, tree, pt) - - // ------------------ Adaptation ------------------------------------- - // - // Adaptations before checking conformance of actual vs expected: - // - // - Convert function to dependent function if expected type is a dependent function type - // (c.f. alignDependentFunction). - // - Relax expected capture set containing `this.type`s by adding references only - // accessible through those types (c.f. addOuterRefs, also #14930 for a discussion). - // - Adapt box status and environment capture sets by simulating box/unbox operations. + end recheckFinish + + // ------------------ Adaptation ------------------------------------- + // + // Adaptations before checking conformance of actual vs expected: + // + // - Convert function to dependent function if expected type is a dependent function type + // (c.f. alignDependentFunction). + // - Relax expected capture set containing `this.type`s by adding references only + // accessible through those types (c.f. addOuterRefs, also #14930 for a discussion). + // - Adapt box status and environment capture sets by simulating box/unbox operations. + // - Instantiate `cap` in actual as needed to a local root. + + override def isCompatible(actual: Type, expected: Type)(using Context): Boolean = + super.isCompatible(actual, expected) + || { + // When testing whether `A <: B`, it could be that `B` uses a local capture root, + // but a uses `cap`, i.e. is capture polymorphic. In this case, adaptation is allowed + // to instantiate `A` to match the root in `B`. + val actual1 = mapRoots(defn.captureRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner))(actual) + (actual1 ne actual) && { + val res = super.isCompatible(actual1, expected) + if !res && ctx.settings.YccDebug.value then + println(i"Failure under mapped roots:") + println(i"${TypeComparer.explained(_.isSubType(actual, expected))}") + res + } + } /** Massage `actual` and `expected` types using the methods below before checking conformance */ - override def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit = + override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Unit = val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) val actual1 = adaptBoxed(actual, expected1, tree.srcPos) //println(i"check conforms $actual1 <<< $expected1") - super.checkConformsExpr(actual1, expected1, tree) + super.checkConformsExpr(actual1, expected1, tree, addenda ++ CaptureSet.levelErrors) private def toDepFun(args: List[Type], resultType: Type, isContextual: Boolean)(using Context): Type = MethodType.companion(isContextual = isContextual)(args, resultType) @@ -767,6 +859,12 @@ class CheckCaptures extends Recheck, SymTransformer: */ def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, alwaysConst: Boolean = false)(using Context): Type = + inline def inNestedEnv[T](boxed: Boolean)(op: => T): T = + val saved = curEnv + curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(curEnv.owner), if boxed then null else curEnv) + try op + finally curEnv = saved + /** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies) * to `expected` type. * It returns the adapted type along with a capture set consisting of the references @@ -776,10 +874,7 @@ class CheckCaptures extends Recheck, SymTransformer: def adaptFun(actual: Type, aargs: List[Type], ares: Type, expected: Type, covariant: Boolean, boxed: Boolean, reconstruct: (List[Type], Type) => Type): (Type, CaptureSet) = - val saved = curEnv - curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(), if boxed then null else curEnv) - - try + inNestedEnv(boxed): val (eargs, eres) = expected.dealias.stripCapturing match case defn.FunctionOf(eargs, eres, _) => (eargs, eres) case expected: MethodType => (expected.paramInfos, expected.resType) @@ -793,8 +888,7 @@ class CheckCaptures extends Recheck, SymTransformer: else reconstruct(aargs1, ares1) (resTp, curEnv.captured) - finally - curEnv = saved + end adaptFun /** Adapt type function type `actual` to the expected type. * @see [[adaptFun]] @@ -803,10 +897,7 @@ class CheckCaptures extends Recheck, SymTransformer: actual: Type, ares: Type, expected: Type, covariant: Boolean, boxed: Boolean, reconstruct: Type => Type): (Type, CaptureSet) = - val saved = curEnv - curEnv = Env(curEnv.owner, EnvKind.NestedInOwner, CaptureSet.Var(), if boxed then null else curEnv) - - try + inNestedEnv(boxed): val eres = expected.dealias.stripCapturing match case defn.PolyFunctionOf(rinfo: PolyType) => rinfo.resType case expected: PolyType => expected.resType @@ -819,8 +910,6 @@ class CheckCaptures extends Recheck, SymTransformer: else reconstruct(ares1) (resTp, curEnv.captured) - finally - curEnv = saved end adaptTypeFun def adaptInfo(actual: Type, expected: Type, covariant: Boolean): String = @@ -961,17 +1050,20 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) + private var setup: Setup = compiletime.uninitialized + override def checkUnit(unit: CompilationUnit)(using Context): Unit = - Setup(preRecheckPhase, thisPhase, recheckDef)(ctx.compilationUnit.tpdTree) - //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") - withCaptureSetsExplained { - super.checkUnit(unit) - checkOverrides.traverse(unit.tpdTree) - checkSelfTypes(unit.tpdTree) - postCheck(unit.tpdTree) - if ctx.settings.YccDebug.value then - show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing - } + setup = Setup(preRecheckPhase, thisPhase, recheckDef) + inContext(ctx.withProperty(ccStateKey, Some(state))): + setup(ctx.compilationUnit.tpdTree) + //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") + withCaptureSetsExplained: + super.checkUnit(unit) + checkOverrides.traverse(unit.tpdTree) + checkSelfTypes(unit.tpdTree) + postCheck(unit.tpdTree) + if ctx.settings.YccDebug.value then + show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing /** Check that self types of subclasses conform to self types of super classes. * (See comment below how this is achieved). The check assumes that classes @@ -1002,28 +1094,34 @@ class CheckCaptures extends Recheck, SymTransformer: } assert(roots.nonEmpty) for case root: ClassSymbol <- roots do - checkSelfAgainstParents(root, root.baseClasses) - val selfType = root.asClass.classInfo.selfType - interpolator(startingVariance = -1).traverse(selfType) - if !root.isEffectivelySealed then - def matchesExplicitRefsInBaseClass(refs: CaptureSet, cls: ClassSymbol): Boolean = - cls.baseClasses.tail.exists { psym => - val selfType = psym.asClass.givenSelfType - selfType.exists && selfType.captureSet.elems == refs.elems - } - selfType match - case CapturingType(_, refs: CaptureSet.Var) - if !refs.isUniversal && !matchesExplicitRefsInBaseClass(refs, root) => - // Forbid inferred self types unless they are already implied by an explicit - // self type in a parent. - report.error( - em"""$root needs an explicitly declared self type since its - |inferred self type $selfType - |is not visible in other compilation units that define subclasses.""", - root.srcPos) - case _ => - parentTrees -= root - capt.println(i"checked $root with $selfType") + inContext(ctx.fresh.setOwner(root).withProperty(LooseRootChecking, Some(()))): + // Without LooseRootChecking, we get problems with F-bounded parent types. + // These can make `cap` "pop out" in ways that are hard to prevent. I believe + // to prevent it we'd have to map `cap` in a whole class graph with all parent + // classes, which would be very expensive. So for now we approximate by assuming + // different roots are compatible for self type conformance checking. + checkSelfAgainstParents(root, root.baseClasses) + val selfType = root.asClass.classInfo.selfType + interpolator(startingVariance = -1).traverse(selfType) + if !root.isEffectivelySealed then + def matchesExplicitRefsInBaseClass(refs: CaptureSet, cls: ClassSymbol): Boolean = + cls.baseClasses.tail.exists { psym => + val selfType = psym.asClass.givenSelfType + selfType.exists && selfType.captureSet.elems == refs.elems + } + selfType match + case CapturingType(_, refs: CaptureSet.Var) + if !refs.elems.exists(_.isRootCapability) && !matchesExplicitRefsInBaseClass(refs, root) => + // Forbid inferred self types unless they are already implied by an explicit + // self type in a parent. + report.error( + em"""$root needs an explicitly declared self type since its + |inferred self type $selfType + |is not visible in other compilation units that define subclasses.""", + root.srcPos) + case _ => + parentTrees -= root + capt.println(i"checked $root with $selfType") end checkSelfTypes /** Heal ill-formed capture sets in the type parameter. @@ -1032,9 +1130,9 @@ class CheckCaptures extends Recheck, SymTransformer: * that this type parameter can't see. * For example, when capture checking the following expression: * - * def usingLogFile[T](op: (f: {cap} File) => T): T = ... + * def usingLogFile[T](op: File^ => T): T = ... * - * usingLogFile[box ?1 () -> Unit] { (f: {cap} File) => () => { f.write(0) } } + * usingLogFile[box ?1 () -> Unit] { (f: File^) => () => { f.write(0) } } * * We may propagate `f` into ?1, making ?1 ill-formed. * This also causes soundness issues, since `f` in ?1 should be widened to `cap`, @@ -1046,34 +1144,29 @@ class CheckCaptures extends Recheck, SymTransformer: */ private def healTypeParam(tree: Tree)(using Context): Unit = val checker = new TypeTraverser: + private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty + private def isAllowed(ref: CaptureRef): Boolean = ref match case ref: TermParamRef => allowed.contains(ref) case _ => true - // Widen the given term parameter refs x₁ : C₁ S₁ , ⋯ , xₙ : Cₙ Sₙ to their capture sets C₁ , ⋯ , Cₙ. - // - // If in these capture sets there are any capture references that are term parameter references we should avoid, - // we will widen them recursively. - private def widenParamRefs(refs: List[TermParamRef]): List[CaptureSet] = - @scala.annotation.tailrec - def recur(todos: List[TermParamRef], acc: List[CaptureSet]): List[CaptureSet] = - todos match - case Nil => acc - case ref :: rem => - val cs = ref.captureSetOfInfo - val nextAcc = cs.filter(isAllowed(_)) :: acc - val nextRem: List[TermParamRef] = (cs.elems.toList.filter(!isAllowed(_)) ++ rem).asInstanceOf - recur(nextRem, nextAcc) - recur(refs, Nil) - private def healCaptureSet(cs: CaptureSet): Unit = - def avoidance(elems: List[CaptureRef])(using Context): Unit = - val toInclude = widenParamRefs(elems.filter(!isAllowed(_)).asInstanceOf) - //println(i"HEAL $cs by widening to $toInclude") - toInclude.foreach(checkSubset(_, cs, tree.srcPos)) - cs.ensureWellformed(avoidance) - - private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty + cs.ensureWellformed: elems => + ctx ?=> + var seen = new util.HashSet[CaptureRef] + def recur(elems: List[CaptureRef]): Unit = + for case ref: TermParamRef <- elems do + if !allowed.contains(ref) && !seen.contains(ref) then + seen += ref + if ref.underlying.isRef(defn.Caps_Cap) then + report.error(i"escaping local reference $ref", tree.srcPos) + else + val widened = ref.captureSetOfInfo + val added = widened.filter(isAllowed(_)) + capt.println(i"heal $ref in $cs by widening to $added") + checkSubset(added, cs, tree.srcPos) + recur(widened.elems.toList) + recur(elems) def traverse(tp: Type) = tp match @@ -1106,16 +1199,19 @@ class CheckCaptures extends Recheck, SymTransformer: def postCheck(unit: tpd.Tree)(using Context): Unit = val checker = new TreeTraverser: def traverse(tree: Tree)(using Context): Unit = - traverseChildren(tree) + val lctx = tree match + case _: DefTree | _: TypeDef if tree.symbol.exists => ctx.withOwner(tree.symbol) + case _ => ctx + traverseChildren(tree)(using lctx) check(tree) - def check(tree: Tree) = tree match + def check(tree: Tree)(using Context) = tree match case _: InferredTypeTree => case tree: TypeTree if !tree.span.isZeroExtent => tree.knownType.foreachPart { tp => checkWellformedPost(tp, tree.srcPos) tp match case AnnotatedType(_, annot) if annot.symbol == defn.RetainsAnnot => - warnIfRedundantCaptureSet(annot.tree) + warnIfRedundantCaptureSet(annot.tree, tree) case _ => } case t: ValOrDefDef @@ -1125,13 +1221,16 @@ class CheckCaptures extends Recheck, SymTransformer: sym.owner.ownersIterator.exists(_.isTerm) || sym.accessBoundary(defn.RootClass).isContainedIn(sym.topLevelClass) def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly - sym.is(Private) // private symbols can always have inferred types - || sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be + sym.is(Private) // Private symbols can always have inferred types + || sym.name.is(DefaultGetterName) // Default getters are exempted since otherwise it would be // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. || // non-local symbols cannot have inferred types since external capture types are not inferred isLocal // local symbols still need explicit types if && !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference + || // If there are overridden symbols, their types form an upper bound + sym.allOverriddenSymbols.nonEmpty // for the inferred type. In this case, separate compilation would + // not be a soundness issue. def isNotPureThis(ref: CaptureRef) = ref match { case ref: ThisType => !ref.cls.isPureClass case _ => true @@ -1164,17 +1263,18 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => end check end checker - checker.traverse(unit) + checker.traverse(unit)(using ctx.withOwner(defn.RootClass)) if !ctx.reporter.errorsReported then - // We dont report errors here if previous errors were reported, because other - // errors often result in bad applied types, but flagging these bad types gives - // often worse error messages than the original errors. - val checkApplied = new TreeTraverser: - def traverse(t: Tree)(using Context) = t match - case tree: InferredTypeTree => - case tree: New => - case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) - case _ => traverseChildren(t) - checkApplied.traverse(unit) + //inContext(ctx.withProperty(LooseRootChecking, Some(()))): + // We dont report errors here if previous errors were reported, because other + // errors often result in bad applied types, but flagging these bad types gives + // often worse error messages than the original errors. + val checkApplied = new TreeTraverser: + def traverse(t: Tree)(using Context) = t match + case tree: InferredTypeTree => + case tree: New => + case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) + case _ => traverseChildren(t) + checkApplied.traverse(unit) end CaptureChecker end CheckCaptures diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b66b9f2b2277..adaa7219d68b 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -6,13 +6,14 @@ import core._ import Phases.*, DenotTransformers.*, SymDenotations.* import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* import Types.*, StdNames.* +import Annotations.Annotation +import config.Feature import config.Printers.capt import ast.tpd import transform.Recheck.* import CaptureSet.IdentityCaptRefMap import Synthetics.isExcluded import util.Property -import dotty.tools.dotc.core.Annotations.Annotation /** A tree traverser that prepares a compilation unit to be capture checked. * It does the following: @@ -82,7 +83,8 @@ extends tpd.TreeTraverser: * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - private def mapInferred(using Context) = new TypeMap: + private def mapInferred(mapRoots: Boolean)(using Context) = new TypeMap: + override def toString = "map inferred" /** Drop @retains annotations everywhere */ object cleanup extends TypeMap: @@ -106,7 +108,8 @@ extends tpd.TreeTraverser: cls.paramGetters.foldLeft(tp) { (core, getter) => if getter.termRef.isTracked then val getterType = tp.memberInfo(getter).strippedDealias - RefinedType(core, getter.name, CapturingType(getterType, CaptureSet.Var())) + RefinedType(core, getter.name, + CapturingType(getterType, CaptureSet.RefiningVar(ctx.owner, getter))) .showing(i"add capture refinement $tp --> $result", capt) else core @@ -121,8 +124,7 @@ extends tpd.TreeTraverser: isTopLevel = false try ts.mapConserve(this) finally isTopLevel = saved - def apply(t: Type) = - val tp = expandThrowsAlias(t) + def apply(tp: Type) = val tp1 = tp match case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => // Drop explicit retains annotations @@ -164,12 +166,12 @@ extends tpd.TreeTraverser: resType = this(tp.resType)) case _ => mapOver(tp) - Setup.addVar(addCaptureRefinements(tp1)) + addVar(addCaptureRefinements(tp1), ctx.owner, mapRoots) end apply end mapInferred - private def transformInferredType(tp: Type, boxed: Boolean)(using Context): Type = - val tp1 = mapInferred(tp) + private def transformInferredType(tp: Type, boxed: Boolean, mapRoots: Boolean)(using Context): Type = + val tp1 = mapInferred(mapRoots)(tp) if boxed then box(tp1) else tp1 /** Recognizer for `res $throws exc`, returning `(res, exc)` in case of success */ @@ -204,32 +206,58 @@ extends tpd.TreeTraverser: else fntpe case _ => tp - private def expandThrowsAliases(using Context) = new TypeMap: - def apply(t: Type) = t match - case _: AppliedType => - val t1 = expandThrowsAlias(t) - if t1 ne t then apply(t1) else mapOver(t) - case _: LazyRef => - t - case t @ AnnotatedType(t1, ann) => - // Don't map capture sets, since that would implicitly normalize sets that - // are not well-formed. - t.derivedAnnotatedType(apply(t1), ann) - case _ => - mapOver(t) + extension (tp: Type) def isCapabilityClassRef(using Context) = tp match + case _: TypeRef | _: AppliedType => tp.typeSymbol.hasAnnotation(defn.CapabilityAnnot) + case _ => false - private def transformExplicitType(tp: Type, boxed: Boolean)(using Context): Type = - val tp1 = expandThrowsAliases(if boxed then box(tp) else tp) - if tp1 ne tp then capt.println(i"expanded: $tp --> $tp1") - tp1 + /** Map references to capability classes C to C^ */ + private def expandCapabilityClass(tp: Type)(using Context): Type = + if tp.isCapabilityClassRef + then CapturingType(tp, CaptureSet.universal, boxed = false) + else tp + + private def checkQualifiedRoots(tree: Tree)(using Context): Unit = + for case elem @ QualifiedRoot(outer) <- retainedElems(tree) do + if !ctx.owner.levelOwnerNamed(outer).exists then + report.error(em"`$outer` does not name an outer definition that represents a capture level", elem.srcPos) + + private def expandAliases(using Context) = new TypeMap with FollowAliases: + override def toString = "expand aliases" + def apply(t: Type) = + val t1 = expandThrowsAlias(t) + if t1 ne t then return this(t1) + val t2 = expandCapabilityClass(t) + if t2 ne t then return t2 + t match + case t @ AnnotatedType(t1, ann) => + checkQualifiedRoots(ann.tree) + val t3 = + if ann.symbol == defn.RetainsAnnot && t1.isCapabilityClassRef then t1 + else this(t1) + // Don't map capture sets, since that would implicitly normalize sets that + // are not well-formed. + t.derivedAnnotatedType(t3, ann) + case t => + normalizeCaptures(mapOverFollowingAliases(t)) + + private def transformExplicitType(tp: Type, boxed: Boolean, mapRoots: Boolean)(using Context): Type = + val tp1 = expandAliases(tp) + val tp2 = + if mapRoots + then cc.mapRoots(defn.captureRoot.termRef, ctx.owner.localRoot.termRef)(tp1) + .showing(i"map roots $tp1, ${tp1.getClass} == $result", capt) + else tp1 + val tp3 = if boxed then box(tp2) else tp2 + if tp3 ne tp then capt.println(i"expanded: $tp --> $tp3") + tp3 /** Transform type of type tree, and remember the transformed type as the type the tree */ - private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit = + private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean, mapRoots: Boolean)(using Context): Unit = if !tree.hasRememberedType then tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tree.tpe, boxed) - else transformExplicitType(tree.tpe, boxed)) + then transformInferredType(tree.tpe, boxed, mapRoots) + else transformExplicitType(tree.tpe, boxed, mapRoots)) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -238,6 +266,7 @@ extends tpd.TreeTraverser: */ private class SubstParams(from: List[List[Symbol]], to: List[LambdaType])(using Context) extends DeepTypeMap, BiTypeMap: + thisMap => def apply(t: Type): Type = t match case t: NamedType => @@ -253,145 +282,284 @@ extends tpd.TreeTraverser: case _ => mapOver(t) - def inverse(t: Type): Type = t match - case t: ParamRef => - def recur(from: List[LambdaType], to: List[List[Symbol]]): Type = - if from.isEmpty then t - else if t.binder eq from.head then to.head(t.paramNum).namedType - else recur(from.tail, to.tail) - recur(to, from) - case _ => - mapOver(t) + lazy val inverse = new BiTypeMap: + override def toString = "SubstParams.inverse" + def apply(t: Type): Type = t match + case t: ParamRef => + def recur(from: List[LambdaType], to: List[List[Symbol]]): Type = + if from.isEmpty then t + else if t.binder eq from.head then to.head(t.paramNum).namedType + else recur(from.tail, to.tail) + recur(to, from) + case _ => + mapOver(t) + def inverse = thisMap end SubstParams + /** If the outer context directly enclosing the definition of `sym` + * has a owner, that owner, otherwise null. + */ + def newOwnerFor(sym: Symbol)(using Context): Symbol | Null = + var octx = ctx + while octx.owner == sym do octx = octx.outer + if octx.owner.name == nme.TRY_BLOCK then octx.owner else null + /** Update info of `sym` for CheckCaptures phase only */ private def updateInfo(sym: Symbol, info: Type)(using Context) = - sym.updateInfoBetween(preRecheckPhase, thisPhase, info) + sym.updateInfoBetween(preRecheckPhase, thisPhase, info, newOwnerFor(sym)) + sym.namedType match + case ref: CaptureRef => ref.invalidateCaches() + case _ => + + /** Update only the owner part fo info if necessary. A symbol should be updated + * only once by either updateInfo or updateOwner. + */ + private def updateOwner(sym: Symbol)(using Context) = + if newOwnerFor(sym) != null then updateInfo(sym, sym.info) + + extension (sym: Symbol) def takesCappedParam(using Context): Boolean = + def search = new TypeAccumulator[Boolean]: + def apply(x: Boolean, t: Type): Boolean = //reporting.trace.force(s"hasCapAt $v, $t"): + if x then true + else t match + case t @ AnnotatedType(t1, annot) + if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot => + val elems = annot match + case CaptureAnnotation(refs, _) => refs.elems.toList + case _ => retainedElems(annot.tree).map(_.tpe) + if elems.exists(_.widen.isRef(defn.Caps_Cap)) then true + else !t1.isCapabilityClassRef && this(x, t1) + case t: PolyType => + apply(x, t.resType) + case t: MethodType => + t.paramInfos.exists(apply(false, _)) + case _ => + if t.isRef(defn.Caps_Cap) || t.isCapabilityClassRef then true + else + val t1 = t.dealiasKeepAnnots + if t1 ne t then this(x, t1) + else foldOver(x, t) + true || sym.info.stripPoly.match + case mt: MethodType => + mt.paramInfos.exists(search(false, _)) + case _ => + false + end extension def traverse(tree: Tree)(using Context): Unit = tree match - case tree: DefDef => - if isExcluded(tree.symbol) then + case tree @ DefDef(_, paramss, tpt: TypeTree, _) => + val meth = tree.symbol + if isExcluded(meth) then return - tree.tpt match - case tpt: TypeTree if tree.symbol.allOverriddenSymbols.hasNext => - tree.paramss.foreach(traverse) - transformTT(tpt, boxed = false, exact = true) - traverse(tree.rhs) - //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") - case _ => - traverseChildren(tree) + + def isCaseClassSynthetic = // TODO drop + meth.owner.isClass && meth.owner.is(Case) && meth.is(Synthetic) && meth.info.firstParamNames.isEmpty + + inContext(ctx.withOwner(meth)): + val canHaveLocalRoot = + if meth.isAnonymousFunction then + ccState.rhsClosure.remove(meth) + || meth.definedLocalRoot.exists // TODO drop + else !meth.isConstructor && !isCaseClassSynthetic + if canHaveLocalRoot && meth.takesCappedParam then + //println(i"level owner: $meth") + ccState.levelOwners += meth + paramss.foreach(traverse) + transformTT(tpt, boxed = false, + exact = tree.symbol.allOverriddenSymbols.hasNext, + mapRoots = true) + traverse(tree.rhs) + //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") + case tree @ ValDef(_, tpt: TypeTree, _) => - transformTT(tpt, - boxed = tree.symbol.is(Mutable), // types of mutable variables are boxed - exact = tree.symbol.allOverriddenSymbols.hasNext // types of symbols that override a parent don't get a capture set - ) - if allowUniversalInBoxed && tree.symbol.is(Mutable) - && !tree.symbol.hasAnnotation(defn.UncheckedCapturesAnnot) - then - CheckCaptures.disallowRootCapabilitiesIn(tpt.knownType, - i"Mutable variable ${tree.symbol.name}", "have type", - "This restriction serves to prevent local capabilities from escaping the scope where they are defined.", - tree.srcPos) - traverse(tree.rhs) + def containsCap(tp: Type) = tp.existsPart: + case CapturingType(_, refs) => refs.isUniversal + case _ => false + def mentionsCap(tree: Tree): Boolean = tree match + case Apply(fn, _) => mentionsCap(fn) + case TypeApply(fn, args) => args.exists(mentionsCap) + case _: InferredTypeTree => false + case _: TypeTree => containsCap(expandAliases(tree.tpe)) + case _ => false + + val sym = tree.symbol + val mapRoots = tree.rhs match + case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => + //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) + ccState.rhsClosure += ddef.symbol + // Toplevel closures bound to vals count as level owners + // unless the closure is an implicit eta expansion over a type application + // that mentions `cap`. In that case we prefer not to silently rebind + // the `cap` to a local root of an invisible closure. See + // pos-custom-args/captures/eta-expansions.scala for examples of both cases. + !tpt.isInstanceOf[InferredTypeTree] + // in this case roots in inferred val type count as polymorphic + case _ => + true + transformTT(tpt, + boxed = sym.is(Mutable), // types of mutable variables are boxed + exact = sym.allOverriddenSymbols.hasNext, // types of symbols that override a parent don't get a capture set + mapRoots + ) + capt.println(i"mapped $tree = ${tpt.knownType}") + traverse(tree.rhs) + case tree @ TypeApply(fn, args) => traverse(fn) for case arg: TypeTree <- args do - transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed - - if allowUniversalInBoxed then - val polyType = fn.tpe.widen.asInstanceOf[TypeLambda] - for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do - if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then - def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else "" - CheckCaptures.disallowRootCapabilitiesIn(arg.knownType, - 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) + transformTT(arg, boxed = true, exact = false, mapRoots = true) // type arguments in type applications are boxed + + case tree: Template => + val cls = tree.symbol.owner + inContext(ctx.withOwner(cls)): + if cls.primaryConstructor.takesCappedParam then + //println(i"level owner $cls") + ccState.levelOwners += cls + traverseChildren(tree) + case tree: Try if Feature.enabled(Feature.saferExceptions) => + val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType)) + ccState.levelOwners += tryOwner + ccState.tryBlockOwner(tree) = tryOwner + inContext(ctx.withOwner(tryOwner)): + traverseChildren(tree) case _ => traverseChildren(tree) - tree match - case tree: TypeTree => - transformTT(tree, boxed = false, exact = false) // other types are not boxed - case tree: ValOrDefDef => - val sym = tree.symbol + postProcess(tree) + end traverse - // replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the - // capture sets of the types of the method's parameter symbols and result type. - def integrateRT( - info: Type, // symbol info to replace - psymss: List[List[Symbol]], // the local (type and term) parameter symbols corresponding to `info` - prevPsymss: List[List[Symbol]], // the local parameter symbols seen previously in reverse order - prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order - ): Type = - info match - case mt: MethodOrPoly => - val psyms = psymss.head - mt.companion(mt.paramNames)( - mt1 => - if !psyms.exists(_.isUpdatedAfter(preRecheckPhase)) && !mt.isParamDependent && prevLambdas.isEmpty then - mt.paramInfos - else - val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => subst(psym.info).asInstanceOf[mt.PInfo]), - mt1 => - integrateRT(mt.resType, psymss.tail, psyms :: prevPsymss, mt1 :: prevLambdas) - ) - case info: ExprType => - info.derivedExprType(resType = - integrateRT(info.resType, psymss, prevPsymss, prevLambdas)) - case _ => - val restp = tree.tpt.knownType - if prevLambdas.isEmpty then restp - else SubstParams(prevPsymss, prevLambdas)(restp) - - if sym.exists && tree.tpt.hasRememberedType && !sym.isConstructor then - val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) - .showing(i"update info $sym: ${sym.info} --> $result", capt) - if newInfo ne sym.info then - val completer = new LazyType: + override def apply(x: Unit, trees: List[Tree])(using Context): Unit = trees match + case (imp: Import) :: rest => + traverse(rest)(using ctx.importContext(imp, imp.symbol)) + case tree :: rest => + traverse(tree) + traverse(rest) + case Nil => + + def postProcess(tree: Tree)(using Context): Unit = tree match + case tree: TypeTree => + transformTT(tree, boxed = false, exact = false, + mapRoots = !ctx.owner.levelOwner.isStaticOwner // other types in static locations are not boxed + ) + case tree: ValOrDefDef => + val sym = tree.symbol + + /** The return type of a constructor instantiated with local type and value + * parameters. Constructors have `unit` result type, that's why we can't + * get this type by reading the result type tree, and have to construct it + * explicitly. + */ + def constrReturnType(info: Type, psymss: List[List[Symbol]]): Type = info match + case info: MethodOrPoly => + constrReturnType(info.instantiate(psymss.head.map(_.namedType)), psymss.tail) + case _ => + info + + /** The local result type, which is the known type of the result type tree, + * with special treatment for constructors. + */ + def localReturnType = + if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) + else tree.tpt.knownType + + // Replace an existing symbol info with inferred types where capture sets of + // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the + // capture sets of the types of the method's parameter symbols and result type. + def integrateRT( + info: Type, // symbol info to replace + psymss: List[List[Symbol]], // the local (type and term) parameter symbols corresponding to `info` + prevPsymss: List[List[Symbol]], // the local parameter symbols seen previously in reverse order + prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order + ): Type = + info match + case mt: MethodOrPoly => + val psyms = psymss.head + val mapr = + if sym.isLevelOwner then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) + else identity[Type] + mt.companion(mt.paramNames)( + mt1 => + if !psyms.exists(_.isUpdatedAfter(preRecheckPhase)) && !mt.isParamDependent && prevLambdas.isEmpty then + mt.paramInfos + else + val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) + psyms.map(psym => mapr(subst(psym.info)).asInstanceOf[mt.PInfo]), + mt1 => + mapr(integrateRT(mt.resType, psymss.tail, psyms :: prevPsymss, mt1 :: prevLambdas)) + ) + case info: ExprType => + info.derivedExprType(resType = + integrateRT(info.resType, psymss, prevPsymss, prevLambdas)) + case info => + if prevLambdas.isEmpty then localReturnType + else SubstParams(prevPsymss, prevLambdas)(localReturnType) + + def signatureChanges = + tree.tpt.hasRememberedType && !sym.isConstructor + || tree.match + case tree: DefDef => tree.termParamss.nestedExists(_.tpt.hasRememberedType) + case _ => false + + if sym.exists && signatureChanges then + val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) + .showing(i"update info $sym: ${sym.info} = $result", capt) + if newInfo ne sym.info then + updateInfo(sym, + if sym.isAnonymousFunction then + // closures are handled specially; the newInfo is constrained from + // the expected type and only afterwards we recheck the definition + newInfo + else new LazyType: def complete(denot: SymDenotation)(using Context) = + // infos of other methods are determined from their definitions which + // are checked on demand denot.info = newInfo - recheckDef(tree, sym) - updateInfo(sym, completer) - case tree: Bind => - val sym = tree.symbol - updateInfo(sym, transformInferredType(sym.info, boxed = false)) - case tree: TypeDef => - tree.symbol match - case cls: ClassSymbol => - val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo + recheckDef(tree, sym)) + else updateOwner(sym) + else if !sym.is(Module) then updateOwner(sym) // Modules are updated with their module classes + + case tree: Bind => + val sym = tree.symbol + updateInfo(sym, transformInferredType(sym.info, boxed = false, mapRoots = true)) + case tree: TypeDef => + tree.symbol match + case cls: ClassSymbol => + val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo + val newSelfType = if (selfInfo eq NoType) || cls.is(ModuleClass) && !cls.isStatic then - // add capture set to self type of nested classes if no self type is given explicitly - val localRefs = CaptureSet.Var() - val newInfo = ClassInfo(prefix, cls, ps, decls, - CapturingType(cinfo.selfType, localRefs) - .showing(i"inferred self type for $cls: $result", capt)) - updateInfo(cls, 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, localRefs)) - modul.termRef.invalidateCaches() - case _ => - val info = atPhase(preRecheckPhase)(tree.symbol.info) - val newInfo = transformExplicitType(info, boxed = false) - if newInfo ne info then - updateInfo(tree.symbol, newInfo) - capt.println(i"update info of ${tree.symbol} from $info to $newInfo") - case _ => - end traverse - - def apply(tree: Tree)(using Context): Unit = - traverse(tree)(using ctx.withProperty(Setup.IsDuringSetupKey, Some(()))) - -object Setup: - val IsDuringSetupKey = new Property.Key[Unit] - - def isDuringSetup(using Context): Boolean = - ctx.property(IsDuringSetupKey).isDefined + // add capture set to self type of nested classes if no self type is given explicitly. + // It's unclear what the right level owner should be. A self type should + // be able to mention class parameters, which are owned by the class; that's + // why the class was picked as level owner. But self types should not be able + // to mention other fields. + CapturingType(cinfo.selfType, CaptureSet.Var(cls)) + else selfInfo match + case selfInfo: Type => + inContext(ctx.withOwner(cls)): + transformExplicitType(selfInfo, boxed = false, mapRoots = true) + case _ => + NoType + if newSelfType.exists then + capt.println(i"mapped self type for $cls: $newSelfType, was $selfInfo") + val newInfo = ClassInfo(prefix, cls, ps, decls, newSelfType) + updateInfo(cls, 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, newSelfType.captureSet)) + modul.termRef.invalidateCaches() + else + updateOwner(cls) + if cls.is(ModuleClass) then updateOwner(cls.sourceModule) + case _ => + val info = atPhase(preRecheckPhase)(tree.symbol.info) + val newInfo = transformExplicitType(info, boxed = false, mapRoots = !ctx.owner.isStaticOwner) + updateInfo(tree.symbol, newInfo) + if newInfo ne info then + capt.println(i"update info of ${tree.symbol} from $info to $newInfo") + case _ => + end postProcess private def superTypeIsImpure(tp: Type)(using Context): Boolean = { tp.dealias match @@ -403,7 +571,8 @@ object Setup: sym == defn.AnyClass // we assume Any is a shorthand of {cap} Any, so if Any is an upper // bound, the type is taken to be impure. - else superTypeIsImpure(tp.superType) + else + sym != defn.Caps_Cap && superTypeIsImpure(tp.superType) case tp: (RefinedOrRecType | MatchType) => superTypeIsImpure(tp.underlying) case tp: AndType => @@ -422,13 +591,9 @@ object Setup: if sym.isClass then !sym.isPureClass && sym != defn.AnyClass else - sym != defn.FromJavaObjectSymbol - // For capture checking, we assume Object from Java is the same as Any - && { - val tp1 = tp.dealias - if tp1 ne tp then needsVariable(tp1) - else superTypeIsImpure(tp1) - } + val tp1 = tp.dealias + if tp1 ne tp then needsVariable(tp1) + else superTypeIsImpure(tp1) case tp: (RefinedOrRecType | MatchType) => needsVariable(tp.underlying) case tp: AndType => @@ -439,14 +604,14 @@ object Setup: needsVariable(parent) && refs.isConst // if refs is a variable, no need to add another && !refs.isUniversal // if refs is {cap}, an added variable would not change anything + case AnnotatedType(parent, _) => + needsVariable(parent) case _ => false }.showing(i"can have inferred capture $tp = $result", capt) - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. - */ - def decorate(tp: Type, addedSet: Type => CaptureSet)(using Context): Type = tp match + /** Pull out an embedded capture set from a part of `tp` */ + def normalizeCaptures(tp: Type)(using Context): Type = tp match case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, parent.isBoxed) case tp: RecType => @@ -458,31 +623,53 @@ object Setup: // by `mapInferred`. Hence if the underlying type admits capture variables // a variable was already added, and the first case above would apply. case AndType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => - assert(refs1.elems.isEmpty) - assert(refs2.elems.isEmpty) assert(tp1.isBoxed == tp2.isBoxed) CapturingType(AndType(parent1, parent2), refs1 ** refs2, tp1.isBoxed) case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => - assert(refs1.elems.isEmpty) - assert(refs2.elems.isEmpty) assert(tp1.isBoxed == tp2.isBoxed) CapturingType(OrType(parent1, parent2, tp.isSoft), refs1 ++ refs2, tp1.isBoxed) case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2) => CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed) case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) => CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) - case _ if needsVariable(tp) => - CapturingType(tp, addedSet(tp)) + case tp: LazyRef => + normalizeCaptures(tp.ref) case _ => tp /** 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)(using Context): Type = - decorate(tp, + def decorate(tp: Type, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type = + if tp.typeSymbol == defn.FromJavaObjectSymbol then + // For capture checking, we assume Object from Java is the same as Any + tp + else + def maybeAdd(target: Type, fallback: Type) = + if needsVariable(target) then CapturingType(target, addedSet(target)) + else fallback + val tp0 = normalizeCaptures(tp) + val tp1 = tp0.dealiasKeepAnnots + if tp1 ne tp0 then + val tp2 = transformExplicitType(tp1, boxed = false, mapRoots) + maybeAdd(tp2, if tp2 ne tp1 then tp2 else tp0) + else maybeAdd(tp0, tp0) + + /** 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, mapRoots: Boolean)(using Context): Type = + decorate(tp, mapRoots, addedSet = _.dealias.match - case CapturingType(_, refs) => CaptureSet.Var(refs.elems) - case _ => CaptureSet.Var()) + case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems) + case _ => CaptureSet.Var(owner)) + def apply(tree: Tree)(using Context): Unit = + traverse(tree)(using ctx.withProperty(Setup.IsDuringSetupKey, Some(()))) + +object Setup: + val IsDuringSetupKey = new Property.Key[Unit] + + def isDuringSetup(using Context): Boolean = + ctx.property(IsDuringSetupKey).isDefined end Setup \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 8a7f2ff4e051..f0a1453672e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -815,7 +815,7 @@ object Contexts { * Note: plain TypeComparers always take on the kind of the outer comparer if they are in the same context. * In other words: tracking or explaining is a sticky property in the same context. */ - private def comparer(using Context): TypeComparer = + def comparer(using Context): TypeComparer = util.Stats.record("comparing") val base = ctx.base if base.comparersInUse > 0 diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 8ae4cb4ceee2..fc2b6a852216 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -56,6 +56,11 @@ object Decorators { def indented(width: Int): String = val padding = " " * width padding + s.replace("\n", "\n" + padding) + + def join(sep: String, other: String) = + if s.isEmpty then other + else if other.isEmpty then s + else s + sep + other end extension /** Convert lazy string to message. To be with caution, since no message-defined @@ -234,6 +239,9 @@ object Decorators { def nestedExists(p: T => Boolean): Boolean = xss match case xs :: xss1 => xs.exists(p) || xss1.nestedExists(p) case nil => false + def nestedFind(p: T => Boolean): Option[T] = xss match + case xs :: xss1 => xs.find(p).orElse(xss1.nestedFind(p)) + case nil => None end extension extension (text: Text) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b4df6bcd4ca5..4a9d4162107b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -971,12 +971,13 @@ class Definitions { @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap") + @tu lazy val Caps_Cap: TypeSymbol = CapsModule.requiredType("Cap") + @tu lazy val Caps_capIn: TermSymbol = CapsModule.requiredMethod("capIn") @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 Caps_SealedAnnot: ClassSymbol = requiredClass("scala.caps.Sealed") @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") @@ -1027,7 +1028,6 @@ class Definitions { @tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked") @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 VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns") @tu lazy val CaptureCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.CaptureChecked") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index a478d60ce348..640ba8015be7 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -884,7 +884,6 @@ object Denotations { /** Install this denotation to be the result of the given denotation transformer. * This is the implementation of the same-named method in SymDenotations. * It's placed here because it needs access to private fields of SingleDenotation. - * @pre Can only be called in `phase.next`. */ protected def installAfter(phase: DenotTransformer)(using Context): Unit = { val targetId = phase.next.id @@ -892,16 +891,21 @@ object Denotations { else { val current = symbol.current // println(s"installing $this after $phase/${phase.id}, valid = ${current.validFor}") - // printPeriods(current) + // println(current.definedPeriodsString) this.validFor = Period(ctx.runId, targetId, current.validFor.lastPhaseId) if (current.validFor.firstPhaseId >= targetId) current.replaceWith(this) + symbol.denot + // Let symbol point to updated denotation + // Without this we can get problems when we immediately recompute the denotation + // at another phase since the invariant that symbol used to point to a valid + // denotation is lost. else { current.validFor = Period(ctx.runId, current.validFor.firstPhaseId, targetId - 1) insertAfter(current) } + // println(current.definedPeriodsString) } - // printPeriods(this) } /** Apply a transformation `f` to all denotations in this group that start at or after diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 2a3828004525..3fc7238cdd82 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -366,6 +366,11 @@ object Phases { def initContext(ctx: FreshContext): Unit = () + /** A hook that allows to transform the usual context passed to the function + * that prints a compilation unit after a phase + */ + def printingContext(ctx: Context): Context = ctx + private var myPeriod: Period = Periods.InvalidPeriod private var myBase: ContextBase = _ private var myErasedTypes = false diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index cd51d4bf79c2..4fc7ea4185d8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -287,6 +287,7 @@ object StdNames { // Compiler-internal val CAPTURE_ROOT: N = "cap" + val LOCAL_CAPTURE_ROOT: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val EVT2U: N = "evt2u$" @@ -298,6 +299,7 @@ object StdNames { val SELF: N = "$this" val SKOLEM: N = "" val TRAIT_CONSTRUCTOR: N = "$init$" + val TRY_BLOCK: N = "" val THROWS: N = "$throws" val U2EVT: N = "u2evt$" val ALLARGS: N = "$allArgs" @@ -432,6 +434,7 @@ object StdNames { val bytes: N = "bytes" val canEqual_ : N = "canEqual" val canEqualAny : N = "canEqualAny" + val capIn: N = "capIn" val caps: N = "caps" val captureChecking: N = "captureChecking" val checkInitialized: N = "checkInitialized" diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index 3e32340b21bd..5a641416b3e1 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -165,7 +165,7 @@ object Substituters: final class SubstBindingMap(from: BindingType, to: BindingType)(using Context) extends DeepTypeMap, BiTypeMap { def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) - def inverse(tp: Type): Type = tp.subst(to, from) + def inverse = SubstBindingMap(to, from) } final class Subst1Map(from: Symbol, to: Type)(using Context) extends DeepTypeMap { @@ -182,7 +182,7 @@ object Substituters: final class SubstSymMap(from: List[Symbol], to: List[Symbol])(using Context) extends DeepTypeMap, BiTypeMap { def apply(tp: Type): Type = substSym(tp, from, to, this)(using mapCtx) - def inverse(tp: Type) = tp.substSym(to, from) // implicitly requires that `to` contains no duplicates. + def inverse = SubstSymMap(to, from) // implicitly requires that `to` contains no duplicates. } final class SubstThisMap(from: ClassSymbol, to: Type)(using Context) extends DeepTypeMap { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index de40ac7232b7..b2f135ebedbb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -23,7 +23,7 @@ import typer.ProtoTypes.constrained import typer.Applications.productSelectorTypes import reporting.trace import annotation.constructorOnly -import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure} +import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure, mapRoots, localRoot} import NameKinds.WildcardParamName /** Provides methods to compare types. @@ -667,15 +667,25 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if defn.isFunctionType(tp2) then if tp2.derivesFrom(defn.PolyFunctionClass) then - tp1.member(nme.apply).info match - case info1: PolyType => - return isSubInfo(info1, tp2.refinedInfo) - case _ => + return isSubInfo(tp1.member(nme.apply).info, tp2.refinedInfo) else tp1w.widenDealias match case tp1: RefinedType => return isSubInfo(tp1.refinedInfo, tp2.refinedInfo) case _ => + else tp2.refinedInfo match + case rinfo2 @ CapturingType(_, refs: CaptureSet.RefiningVar) => + tp1.widen match + case RefinedType(parent1, tp2.refinedName, rinfo1) => + // When comparing against a Var in class instance refinement, + // take the Var as the precise truth, don't also look in the parent. + // The parent might have a capture root at the wrong level. + // TODO: Generalize this to other refinement situations where the + // lower type's refinement appears elsewhere? + return isSubType(rinfo1, rinfo2) && recur(parent1, tp2.parent) + case _ => + case _ => + end if val skipped2 = skipMatching(tp1w, tp2) if (skipped2 eq tp2) || !Config.fastPathForRefinedSubtype then @@ -2075,7 +2085,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def qualifies(m: SingleDenotation): Boolean = val info2 = tp2.refinedInfo val isExpr2 = info2.isInstanceOf[ExprType] - val info1 = m.info match + var info1 = m.info match case info1: ValueType if isExpr2 || m.symbol.is(Mutable) => // OK: { val x: T } <: { def x: T } // OK: { var x: T } <: { def x: T } @@ -2085,9 +2095,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // OK{ { def x(): T } <: { def x: T} // if x is Java defined ExprType(info1.resType) case info1 => info1 + + if ctx.phase == Phases.checkCapturesPhase then + // When comparing against a RefiningVar refinement, map the + // localRoot of the corresponding class in `tp1` to the owner of the + // refining capture set. + tp2.refinedInfo match + case rinfo2 @ CapturingType(_, refs: CaptureSet.RefiningVar) => + info1 = mapRoots(refs.getter.owner.localRoot.termRef, refs.owner.localRoot.termRef)(info1) + case _ => + isSubInfo(info1, info2, m.symbol.info.orElse(info1)) || matchAbstractTypeMember(m.info) || (tp1.isStable && m.symbol.isStableMember && isSubType(TermRef(tp1, m.symbol), tp2.refinedInfo)) + end qualifies tp1.member(name).hasAltWithInline(qualifies) } @@ -3132,6 +3153,9 @@ object TypeComparer { def tracked[T](op: TrackingTypeComparer => T)(using Context): T = comparing(_.tracked(op)) + + def subCaptures(refs1: CaptureSet, refs2: CaptureSet, frozen: Boolean)(using Context): CaptureSet.CompareResult = + comparing(_.subCaptures(refs1, refs2, frozen)) } object TrackingTypeComparer: diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 24a207da6836..1dcd2301b1a7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -177,7 +177,7 @@ object CyclicReference: def apply(denot: SymDenotation)(using Context): CyclicReference = val ex = new CyclicReference(denot) if ex.computeStackTrace then - cyclicErrors.println(s"Cyclic reference involving! $denot") + cyclicErrors.println(s"Cyclic reference involving $denot") val sts = ex.getStackTrace.asInstanceOf[Array[StackTraceElement]] for (elem <- sts take 200) cyclicErrors.println(elem.toString) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5265a882a6e9..f2c4fdecb834 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -36,7 +36,7 @@ import config.Printers.{core, typr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized -import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, EventuallyCapturingType, boxedUnlessFun} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, EventuallyCapturingType, boxedUnlessFun, ccNestingLevel} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -483,6 +483,11 @@ object Types { */ 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) @@ -823,19 +828,26 @@ object Types { pinfo recoverable_& rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } - else - val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] - val joint = pdenot.meet( - new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), - pre, - safeIntersection = ctx.base.pendingMemberSearches.contains(name)) - joint match - case joint: SingleDenotation - if isRefinedMethod && rinfo <:< joint.info => - // use `rinfo` to keep the right parameter names for named args. See i8516.scala. - joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) - case _ => - joint + else rinfo match + case CapturingType(_, cs: CaptureSet.RefiningVar) => + // If `rinfo` is a capturing type added by `addCaptureRefinements` it + // already contains everything there is to know about the member type. + // On the other hand, the member in parent might belong to an outer nesting level, + // which should be ignored at the point where instances of the class are constructed. + pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, rinfo) + case _ => + val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] + val joint = pdenot.meet( + new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), + pre, + safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + joint match + case joint: SingleDenotation + if isRefinedMethod && rinfo <:< joint.info => + // use `rinfo` to keep the right parameter names for named args. See i8516.scala. + joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) + case _ => + joint } def goApplied(tp: AppliedType, tycon: HKTypeLambda) = @@ -2157,18 +2169,27 @@ object Types { private var myCaptureSetRunId: Int = NoRunId private var mySingletonCaptureSet: CaptureSet.Const | Null = null - /** Can the reference be tracked? This is true for all ThisTypes or ParamRefs - * but only for some NamedTypes. - */ - def canBeTracked(using Context): Boolean - /** 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 = canBeTracked && !captureSetOfInfo.isAlwaysEmpty + final def isTracked(using Context): Boolean = isTrackableRef && !captureSetOfInfo.isAlwaysEmpty - /** Is this reference the root capability `cap` ? */ - def isRootCapability(using Context): Boolean = false + /** Is this reference the generic root capability `cap` ? */ + def isGenericRootCapability(using Context): Boolean = false + + /** Is this reference a local root capability `{}` + * for some level owner? + */ + def isLocalRootCapability(using Context): Boolean = + localRootOwner.exists + + /** If this is a local root capability, its owner, otherwise NoSymbol. + */ + def localRootOwner(using Context): Symbol = NoSymbol + + /** Is this reference the a (local or generic) root capability? */ + def isRootCapability(using Context): Boolean = + isGenericRootCapability || isLocalRootCapability /** Normalize reference so that it can be compared with `eq` for equality */ def normalizedRef(using Context): CaptureRef = this @@ -2198,7 +2219,8 @@ object Types { override def captureSet(using Context): CaptureSet = val cs = captureSetOfInfo - if canBeTracked && !cs.isAlwaysEmpty then singletonCaptureSet else cs + if isTrackableRef && !cs.isAlwaysEmpty then singletonCaptureSet else cs + end CaptureRef /** A trait for types that bind other types that refer to them. @@ -2895,17 +2917,26 @@ object Types { * They are subsumed in the capture sets of the enclosing class. * TODO: ^^^ What about call-by-name? */ - def canBeTracked(using Context) = + override def isTrackableRef(using Context) = ((prefix eq NoPrefix) || symbol.is(ParamAccessor) && (prefix eq symbol.owner.thisType) || isRootCapability ) && !symbol.isOneOf(UnstableValueFlags) - override def isRootCapability(using Context): Boolean = + override def isGenericRootCapability(using Context): Boolean = name == nme.CAPTURE_ROOT && symbol == defn.captureRoot + override def localRootOwner(using Context): Symbol = + if name == nme.LOCAL_CAPTURE_ROOT then + if symbol.owner.isLocalDummy then symbol.owner.owner + else symbol.owner + else if info.isRef(defn.Caps_Cap) then + val owner = symbol.maybeOwner + if owner.isTerm then owner else NoSymbol + else NoSymbol + override def normalizedRef(using Context): CaptureRef = - if canBeTracked then symbol.termRef else this + if isTrackableRef then symbol.termRef else this } abstract case class TypeRef(override val prefix: Type, @@ -3058,7 +3089,7 @@ object Types { // can happen in IDE if `cls` is stale } - def canBeTracked(using Context) = true + override def isTrackableRef(using Context) = true override def computeHash(bs: Binders): Int = doHash(bs, tref) @@ -4051,15 +4082,10 @@ object Types { protected def toPInfo(tp: Type)(using Context): PInfo - /** If `tparam` is a sealed type parameter symbol of a polymorphic method, add - * a @caps.Sealed annotation to the upperbound in `tp`. - */ - protected def addSealed(tparam: ParamInfo, tp: Type)(using Context): Type = tp - def fromParams[PI <: ParamInfo.Of[N]](params: List[PI], resultType: Type)(using Context): Type = if (params.isEmpty) resultType else apply(params.map(_.paramName))( - tl => params.map(param => toPInfo(addSealed(param, tl.integrate(params, param.paramInfo)))), + tl => params.map(param => toPInfo(tl.integrate(params, param.paramInfo))), tl => tl.integrate(params, resultType)) } @@ -4381,16 +4407,6 @@ object Types { resultTypeExp: PolyType => Type)(using Context): PolyType = unique(new PolyType(paramNames)(paramInfosExp, resultTypeExp)) - override protected def addSealed(tparam: ParamInfo, tp: Type)(using Context): Type = - tparam match - case tparam: Symbol if tparam.is(Sealed) => - tp match - case tp @ TypeBounds(lo, hi) => - tp.derivedTypeBounds(lo, - AnnotatedType(hi, Annotation(defn.Caps_SealedAnnot, tparam.span))) - case _ => tp - case _ => tp - def unapply(tl: PolyType): Some[(List[LambdaParam], Type)] = Some((tl.typeParams, tl.resType)) } @@ -4672,9 +4688,9 @@ object Types { */ abstract case class TermParamRef(binder: TermLambda, paramNum: Int) extends ParamRef, CaptureRef { type BT = TermLambda - def canBeTracked(using Context) = true def kindString: String = "Term" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) + override def isTrackableRef(using Context) = true } private final class TermParamRefImpl(binder: TermLambda, paramNum: Int) extends TermParamRef(binder, paramNum) @@ -5727,23 +5743,16 @@ object Types { trait BiTypeMap extends TypeMap: thisMap => - /** The inverse of the type map as a function */ - def inverse(tp: Type): Type - - /** The inverse of the type map as a BiTypeMap map, which - * has the original type map as its own inverse. - */ - def inverseTypeMap(using Context) = new BiTypeMap: - def apply(tp: Type) = thisMap.inverse(tp) - def inverse(tp: Type) = thisMap.apply(tp) + /** The inverse of the type map */ + def inverse: BiTypeMap /** A restriction of this map to a function on tracked CaptureRefs */ def forward(ref: CaptureRef): CaptureRef = this(ref) match - case result: CaptureRef if result.canBeTracked => result + case result: CaptureRef if result.isTrackableRef => result /** A restriction of the inverse to a function on tracked CaptureRefs */ def backward(ref: CaptureRef): CaptureRef = inverse(ref) match - case result: CaptureRef if result.canBeTracked => result + case result: CaptureRef if result.isTrackableRef => result end BiTypeMap abstract class TypeMap(implicit protected var mapCtx: Context) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 40c90bc25e3c..bd5159a60931 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1423,13 +1423,23 @@ object Parsers { case _ => None } - /** CaptureRef ::= ident | `this` + /** CaptureRef ::= ident | `this` | `cap` [`[` ident `]`] */ def captureRef(): Tree = if in.token == THIS then simpleRef() else termIdent() match - case Ident(nme.CAPTURE_ROOT) => captureRoot - case id => id + case id @ Ident(nme.CAPTURE_ROOT) => + if in.token == LBRACKET then + val ref = atSpan(id.span.start)(captureRootIn) + val qual = + inBrackets: + atSpan(in.offset): + Literal(Constant(ident().toString)) + atSpan(id.span.start)(Apply(ref, qual :: Nil)) + else + atSpan(id.span.start)(captureRoot) + case id => + id /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ @@ -3179,9 +3189,7 @@ object Parsers { * id [HkTypeParamClause] TypeParamBounds * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ - * DefTypeParam ::= {Annotation} - * [`sealed`] -- under captureChecking - * id [HkTypeParamClause] TypeParamBounds + * DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ * TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds @@ -3191,25 +3199,24 @@ object Parsers { */ def typeParamClause(ownerKind: ParamOwner): List[TypeDef] = inBrackets { - def checkVarianceOK(): Boolean = - val ok = ownerKind != ParamOwner.Def && ownerKind != ParamOwner.TypeParam - if !ok then syntaxError(em"no `+/-` variance annotation allowed here") - in.nextToken() - ok + def variance(vflag: FlagSet): FlagSet = + if ownerKind == ParamOwner.Def || ownerKind == ParamOwner.TypeParam then + syntaxError(em"no `+/-` variance annotation allowed here") + in.nextToken() + EmptyFlags + else + in.nextToken() + vflag def typeParam(): TypeDef = { val isAbstractOwner = ownerKind == ParamOwner.Type || ownerKind == ParamOwner.TypeParam val start = in.offset - var mods = annotsAsMods() | Param - if ownerKind == ParamOwner.Class then mods |= PrivateLocal - if Feature.ccEnabled && in.token == SEALED then - if ownerKind == ParamOwner.Def then mods |= Sealed - else syntaxError(em"`sealed` modifier only allowed for method type parameters") - in.nextToken() - if isIdent(nme.raw.PLUS) && checkVarianceOK() then - mods |= Covariant - else if isIdent(nme.raw.MINUS) && checkVarianceOK() then - mods |= Contravariant + val mods = + annotsAsMods() + | (if (ownerKind == ParamOwner.Class) Param | PrivateLocal else Param) + | (if isIdent(nme.raw.PLUS) then variance(Covariant) + else if isIdent(nme.raw.MINUS) then variance(Contravariant) + else EmptyFlags) atSpan(start, nameStart) { val name = if (isAbstractOwner && in.token == USCORE) { diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index b739bcf1b74d..83ff03e05592 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, EventuallyCapturingType, CaptureSet, isBoxed} +import cc.{CapturingType, EventuallyCapturingType, CaptureSet, CaptureRoot, isBoxed, ccNestingLevel, levelOwner} class PlainPrinter(_ctx: Context) extends Printer { @@ -48,6 +48,11 @@ class PlainPrinter(_ctx: Context) extends Printer { protected def homogenizedView: Boolean = ctx.settings.YtestPickler.value protected def debugPos: Boolean = ctx.settings.YdebugPos.value + /** If true, shorten local roots of current owner tp `cap`, + * TODO: we should drop this switch once we implemented disambiguation of capture roots. + */ + private val shortenCap = true + def homogenize(tp: Type): Type = if (homogenizedView) tp match { @@ -150,11 +155,13 @@ class PlainPrinter(_ctx: Context) extends Printer { + defn.FromJavaObjectSymbol def toTextCaptureSet(cs: CaptureSet): Text = - if printDebug && !cs.isConst then cs.toString - else if ctx.settings.YccDebug.value then cs.show + if printDebug && ctx.settings.YccDebug.value && !cs.isConst then cs.toString else if cs == CaptureSet.Fluid then "" - else if !cs.isConst && cs.elems.isEmpty then "?" - else "{" ~ Text(cs.elems.toList.map(toTextCaptureRef), ", ") ~ "}" + else + val core: Text = + if !cs.isConst && cs.elems.isEmpty then "?" + else "{" ~ Text(cs.elems.toList.map(toTextCaptureRef), ", ") ~ "}" + core ~ cs.optionalInfo /** Print capturing type, overridden in RefinedPrinter to account for * capturing function types. @@ -164,15 +171,18 @@ class PlainPrinter(_ctx: Context) extends Printer { boxText ~ toTextLocal(parent) ~ "^" ~ (refsText provided refsText != rootSetText) - final protected def rootSetText = Str("{cap}") + final protected def rootSetText = Str("{cap}") // TODO Use disambiguation def toText(tp: Type): Text = controlled { homogenize(tp) match { case tp: TypeType => toTextRHS(tp) case tp: TermRef - if !tp.denotationIsCurrent && !homogenizedView || // always print underlying when testing picklers - tp.symbol.is(Module) || tp.symbol.name == nme.IMPORT => + if !tp.denotationIsCurrent + && !homogenizedView // always print underlying when testing picklers + && !tp.isRootCapability + || tp.symbol.is(Module) + || tp.symbol.name == nme.IMPORT => toTextRef(tp) ~ ".type" case tp: TermRef if tp.denot.isOverloaded => "" @@ -222,7 +232,22 @@ class PlainPrinter(_ctx: Context) extends Printer { }.close case tp @ EventuallyCapturingType(parent, refs) => val boxText: Text = Str("box ") provided tp.isBoxed //&& ctx.settings.YccDebug.value - val refsText = if refs.isUniversal then rootSetText else toTextCaptureSet(refs) + val rootsInRefs = refs.elems.filter(_.isRootCapability).toList + val showAsCap = rootsInRefs match + case (tp: TermRef) :: Nil => + if tp.symbol == defn.captureRoot then + refs.elems.size == 1 || !printDebug + // {caps.cap} gets printed as `{cap}` even under printDebug as long as there + // are no other elements in the set + else + tp.symbol.name == nme.LOCAL_CAPTURE_ROOT + && ctx.owner.levelOwner == tp.localRootOwner + && !printDebug + && shortenCap // !!! + // local roots get printed as themselves under printDebug + case _ => + false + val refsText = if showAsCap then rootSetText else toTextCaptureSet(refs) toTextCapturing(parent, refsText, boxText) case tp: PreviousErrorType if ctx.settings.XprintTypes.value => "" // do not print previously reported error message because they may try to print this error type again recuresevely @@ -324,7 +349,10 @@ class PlainPrinter(_ctx: Context) extends Printer { */ protected def idString(sym: Symbol): String = (if (showUniqueIds || Printer.debugPrintUnique) "#" + sym.id else "") + - (if (showNestingLevel) "%" + sym.nestingLevel else "") + (if showNestingLevel then + if ctx.phase == Phases.checkCapturesPhase then "%" + sym.ccNestingLevel + else "%" + sym.nestingLevel + else "") def nameString(sym: Symbol): String = simpleNameString(sym) + idString(sym) // + "<" + (if (sym.exists) sym.owner else "") + ">" @@ -354,7 +382,13 @@ class PlainPrinter(_ctx: Context) extends Printer { def toTextRef(tp: SingletonType): Text = controlled { tp match { case tp: TermRef => - toTextPrefixOf(tp) ~ selectionString(tp) + if tp.symbol.name == nme.LOCAL_CAPTURE_ROOT then // TODO: Move to toTextCaptureRef + if ctx.owner.levelOwner == tp.localRootOwner && !printDebug && shortenCap then + Str("cap") + else + Str(s"cap[${tp.localRootOwner.name}]") ~ + Str(s"%${tp.symbol.ccNestingLevel}").provided(showNestingLevel) + else toTextPrefixOf(tp) ~ selectionString(tp) case tp: ThisType => nameString(tp.cls) + ".this" case SuperType(thistpe: SingletonType, _) => @@ -373,6 +407,15 @@ class PlainPrinter(_ctx: Context) extends Printer { if (homogenizedView) toText(tp.info) else if (ctx.settings.XprintTypes.value) "<" ~ toText(tp.repr) ~ ":" ~ toText(tp.info) ~ ">" else toText(tp.repr) + case tp: CaptureRoot.Var => + if tp.followAlias ne tp then toTextRef(tp.followAlias) + else + def boundText(sym: Symbol): Text = + (toTextRef(sym.termRef) + ~ Str(s"/${sym.ccNestingLevel}").provided(showNestingLevel) + ).provided(sym.exists) + "'cap[" ~ boundText(tp.lowerBound) ~ ".." ~ boundText(tp.upperBound) ~ "]" + ~ ("(from instantiating " ~ nameString(tp.source) ~ ")").provided(tp.source.exists) } } diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 04cea9fb9702..eafa399313da 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -10,6 +10,7 @@ import Types.{Type, SingletonType, LambdaParam, NamedType}, import typer.Implicits.* import util.SourcePosition import typer.ImportInfo +import cc.CaptureSet import scala.annotation.internal.sharable @@ -106,6 +107,9 @@ abstract class Printer { /** Textual representation of a reference in a capture set */ def toTextCaptureRef(tp: Type): Text + /** Textual representation of a reference in a capture set */ + def toTextCaptureSet(cs: CaptureSet): Text + /** Textual representation of symbol's declaration */ def dclText(sym: Symbol): Text diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ff09a6084136..114037fd0bd0 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -29,7 +29,7 @@ import config.{Config, Feature} import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} -import cc.{CaptureSet, toCaptureSet, IllegalCaptureRef} +import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, ccNestingLevelOpt} class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -268,7 +268,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(tycon) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => toTextMethodAsFunction(tp.refinedInfo, - isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction) + isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction, + refs = tp.parent match + case CapturingType(_, cs) => toTextCaptureSet(cs) + case _ => "") case tp: TypeRef => if (tp.symbol.isAnonymousClass && !showUniqueIds) toText(tp.info) @@ -865,10 +868,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def optAscription[T <: Untyped](tpt: Tree[T]): Text = optText(tpt)(": " ~ _) + private def nestingLevel(sym: Symbol): Int = + sym.ccNestingLevelOpt.getOrElse(sym.nestingLevel) + private def idText(tree: untpd.Tree): Text = (if showUniqueIds && tree.hasType && tree.symbol.exists then s"#${tree.symbol.id}" else "") ~ (if showNestingLevel then tree.typeOpt match - case tp: NamedType if !tp.symbol.isStatic => s"%${tp.symbol.nestingLevel}" + case tp: NamedType if !tp.symbol.isStatic => s"%${nestingLevel(tp.symbol)}" case tp: TypeVar => s"%${tp.nestingLevel}" case tp: TypeParamRef => ctx.typerState.constraint.typeVarOfParam(tp) match case tvar: TypeVar => s"%${tvar.nestingLevel}" diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 593018ceb965..2456e4011367 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -15,6 +15,7 @@ import typer.ErrorReporting.err import typer.ProtoTypes.* import typer.TypeAssigner.seqLitType import typer.ConstFold +import typer.ErrorReporting.{Addenda, NothingToAdd} import NamerOps.methodType import config.Printers.recheckr import util.Property @@ -48,21 +49,25 @@ object Recheck: extension (sym: Symbol) /** Update symbol's info to newInfo from prevPhase.next to lastPhase. - * Reset to previous info for phases after lastPhase. + * Also update owner to newOwnerOrNull if it is not null. + * Reset to previous info and owner for phases after lastPhase. */ - def updateInfoBetween(prevPhase: DenotTransformer, lastPhase: DenotTransformer, newInfo: Type)(using Context): Unit = - if sym.info ne newInfo then + def updateInfoBetween(prevPhase: DenotTransformer, lastPhase: DenotTransformer, newInfo: Type, newOwnerOrNull: Symbol | Null = null)(using Context): Unit = + val newOwner = if newOwnerOrNull == null then sym.owner else newOwnerOrNull + if (sym.info ne newInfo) || (sym.owner ne newOwner) then + val flags = sym.flags sym.copySymDenotation( initFlags = - if sym.flags.isAllOf(ResetPrivateParamAccessor) - then sym.flags &~ ResetPrivate | Private - else sym.flags + if flags.isAllOf(ResetPrivateParamAccessor) + then flags &~ ResetPrivate | Private + else flags ).installAfter(lastPhase) // reset sym.copySymDenotation( + owner = newOwner, info = newInfo, initFlags = - if newInfo.isInstanceOf[LazyType] then sym.flags &~ Touched - else sym.flags + if newInfo.isInstanceOf[LazyType] then flags &~ Touched + else flags ).installAfter(prevPhase) /** Does symbol have a new denotation valid from phase.next that is different @@ -96,17 +101,43 @@ object Recheck: case Some(tpe) => tree.withType(tpe).asInstanceOf[T] case None => tree - extension (tpe: Type) - - /** Map ExprType => T to () ?=> T (and analogously for pure versions). - * Even though this phase runs after ElimByName, ExprTypes can still occur - * as by-name arguments of applied types. See note in doc comment for - * ElimByName phase. Test case is bynamefun.scala. - */ - def mapExprType(using Context): Type = tpe match - case ExprType(rt) => defn.ByNameFunction(rt) - case _ => tpe - + /** Map ExprType => T to () ?=> T (and analogously for pure versions). + * Even though this phase runs after ElimByName, ExprTypes can still occur + * as by-name arguments of applied types. See note in doc comment for + * ElimByName phase. Test case is bynamefun.scala. + */ + private def mapExprType(tp: Type)(using Context): Type = tp match + case ExprType(rt) => defn.ByNameFunction(rt) + case _ => tp + + /** Normalize `=> A` types to `() ?=> A` types + * - at the top level + * - in function and method parameter types + * - under annotations + */ + def normalizeByName(tp: Type)(using Context): Type = tp match + case tp: ExprType => + mapExprType(tp) + case tp: PolyType => + tp.derivedLambdaType(resType = normalizeByName(tp.resType)) + case tp: MethodType => + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(mapExprType), + resType = normalizeByName(tp.resType)) + case tp @ RefinedType(parent, nme.apply, rinfo) if defn.isFunctionType(tp) => + tp.derivedRefinedType(parent, nme.apply, normalizeByName(rinfo)) + case tp @ defn.FunctionOf(pformals, restpe, isContextual) => + val pformals1 = pformals.mapConserve(mapExprType) + val restpe1 = normalizeByName(restpe) + if (pformals1 ne pformals) || (restpe1 ne restpe) then + defn.FunctionOf(pformals1, restpe1, isContextual) + else + tp + case tp @ AnnotatedType(parent, ann) => + tp.derivedAnnotatedType(normalizeByName(parent), ann) + case _ => + tp +end Recheck /** A base class that runs a simplified typer pass over an already re-typed program. The pass * does not transform trees but returns instead the re-typed type of each tree as it is @@ -183,27 +214,16 @@ abstract class Recheck extends Phase, SymTransformer: else AnySelectionProto recheckSelection(tree, recheck(qual, proto).widenIfUnstable, name, pt) - /** When we select the `apply` of a function with type such as `(=> A) => B`, - * we need to convert the parameter type `=> A` to `() ?=> A`. See doc comment - * of `mapExprType`. - */ - def normalizeByName(mbr: SingleDenotation)(using Context): SingleDenotation = mbr.info match - case mt: MethodType if mt.paramInfos.exists(_.isInstanceOf[ExprType]) => - mbr.derivedSingleDenotation(mbr.symbol, - mt.derivedLambdaType(paramInfos = mt.paramInfos.map(_.mapExprType))) - case _ => - mbr - def recheckSelection(tree: Select, qualType: Type, name: Name, sharpen: Denotation => Denotation)(using Context): Type = if name.is(OuterSelectName) then tree.tpe else //val pre = ta.maybeSkolemizePrefix(qualType, name) - val mbr = normalizeByName( + val mbr = sharpen( qualType.findMember(name, qualType, excluded = if tree.symbol.is(Private) then EmptyFlags else Private - )).suchThat(tree.symbol == _)) + )).suchThat(tree.symbol == _) val newType = tree.tpe match case prevType: NamedType => val prevDenot = prevType.denot @@ -268,12 +288,15 @@ abstract class Recheck extends Phase, SymTransformer: protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = mt.instantiate(argTypes) + protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe + def recheckApply(tree: Apply, pt: Type)(using Context): Type = - val funTp = recheck(tree.fun) + val funtpe0 = recheck(tree.fun) // reuse the tree's type on signature polymorphic methods, instead of using the (wrong) rechecked one - val funtpe = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else funTp - funtpe.widen match - case fntpe: MethodType => + val funtpe1 = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else funtpe0 + funtpe1.widen match + case fntpe1: MethodType => + val fntpe = prepareFunction(fntpe1, tree.fun.symbol) assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) val formals = if false && tree.symbol.is(JavaDefined) // see NOTE in mapJavaArgs @@ -281,7 +304,7 @@ abstract class Recheck extends Phase, SymTransformer: else fntpe.paramInfos def recheckArgs(args: List[Tree], formals: List[Type], prefs: List[ParamRef]): List[Type] = args match case arg :: args1 => - val argType = recheck(arg, formals.head.mapExprType) + val argType = recheck(arg, normalizeByName(formals.head)) val formals1 = if fntpe.isParamDependent then formals.tail.map(_.substParam(prefs.head, argType)) @@ -294,7 +317,7 @@ abstract class Recheck extends Phase, SymTransformer: constFold(tree, instantiate(fntpe, argTypes, tree.fun.symbol)) //.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result") case tp => - assert(false, i"unexpected type of ${tree.fun}: $funtpe") + assert(false, i"unexpected type of ${tree.fun}: $tp") def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = recheck(tree.fun).widen match @@ -313,27 +336,33 @@ abstract class Recheck extends Phase, SymTransformer: recheck(tree.rhs, lhsType.widen) defn.UnitType - def recheckBlock(stats: List[Tree], expr: Tree, pt: Type)(using Context): Type = + private def recheckBlock(stats: List[Tree], expr: Tree)(using Context): Type = recheckStats(stats) val exprType = recheck(expr) + TypeOps.avoid(exprType, localSyms(stats).filterConserve(_.isTerm)) + + def recheckBlock(tree: Block, pt: Type)(using Context): Type = tree match + case Block(Nil, expr: Block) => recheckBlock(expr, pt) + case Block((mdef : DefDef) :: Nil, closure: Closure) => + recheckClosureBlock(mdef, closure.withSpan(tree.span), pt) + case Block(stats, expr) => recheckBlock(stats, expr) // The expected type `pt` is not propagated. Doing so would allow variables in the // expected type to contain references to local symbols of the block, so the // local symbols could escape that way. - TypeOps.avoid(exprType, localSyms(stats).filterConserve(_.isTerm)) - def recheckBlock(tree: Block, pt: Type)(using Context): Type = - recheckBlock(tree.stats, tree.expr, pt) + def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = + recheckBlock(mdef :: Nil, expr) def recheckInlined(tree: Inlined, pt: Type)(using Context): Type = - recheckBlock(tree.bindings, tree.expansion, pt)(using inlineContext(tree)) + recheckBlock(tree.bindings, tree.expansion)(using inlineContext(tree)) def recheckIf(tree: If, pt: Type)(using Context): Type = recheck(tree.cond, defn.BooleanType) recheck(tree.thenp, pt) | recheck(tree.elsep, pt) - def recheckClosure(tree: Closure, pt: Type)(using Context): Type = + def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type = if tree.tpt.isEmpty then - tree.meth.tpe.widen.toFunctionType(isJava = tree.meth.symbol.is(JavaDefined)) + tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent) else recheck(tree.tpt) @@ -534,26 +563,25 @@ abstract class Recheck extends Phase, SymTransformer: /** Check that widened types of `tpe` and `pt` are compatible. */ def checkConforms(tpe: Type, pt: Type, tree: Tree)(using Context): Unit = tree match - case _: DefTree | EmptyTree | _: TypeTree | _: Closure => - // Don't report closure nodes, since their span is a point; wait instead - // for enclosing block to preduce an error + case _: DefTree | EmptyTree | _: TypeTree => case _ => checkConformsExpr(tpe.widenExpr, pt.widenExpr, tree) - def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit = - //println(i"check conforms $actual <:< $expected") + def isCompatible(actual: Type, expected: Type)(using Context): Boolean = + actual <:< expected + || expected.isRepeatedParam + && isCompatible(actual, + expected.translateFromRepeated(toArray = actual.isRef(defn.ArrayClass))) + || { + val widened = widenSkolems(expected) + (widened ne expected) && isCompatible(actual, widened) + } - def isCompatible(expected: Type): Boolean = - actual <:< expected - || expected.isRepeatedParam - && isCompatible(expected.translateFromRepeated(toArray = tree.tpe.isRef(defn.ArrayClass))) - || { - val widened = widenSkolems(expected) - (widened ne expected) && isCompatible(widened) - } - if !isCompatible(expected) then + def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Unit = + //println(i"check conforms $actual <:< $expected") + if !isCompatible(actual, expected) then recheckr.println(i"conforms failed for ${tree}: $actual vs $expected") - err.typeMismatch(tree.withType(actual), expected) + err.typeMismatch(tree.withType(actual), expected, addenda) else if debugSuccesses then tree match case _: Ident => diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c17c5f25ab5f..1ea24187a185 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -517,12 +517,7 @@ object Checking { // note: this is not covered by the next test since terms can be abstract (which is a dual-mode flag) // but they can never be one of ClassOnlyFlags if !sym.isClass && sym.isOneOf(ClassOnlyFlags) then - val illegal = sym.flags & ClassOnlyFlags - if sym.is(TypeParam) && illegal == Sealed && Feature.ccEnabled && cc.allowUniversalInBoxed then - if !sym.owner.is(Method) then - fail(em"only method type parameters can be sealed") - else - fail(em"only classes can be ${illegal.flagsString}") + fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}") if (sym.is(AbsOverride) && !sym.owner.is(Trait)) fail(AbstractOverrideOnlyInTraits(sym)) if sym.is(Trait) then diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 25cbfdfec600..68ea402eff3f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -70,6 +70,15 @@ object ErrorReporting { case _ => foldOver(s, tp) tps.foldLeft("")(collectMatchTrace) + /** A mixin trait that can produce added elements for an error message */ + trait Addenda: + self => + def toAdd(using Context): List[String] = Nil + def ++ (follow: Addenda) = new Addenda: + override def toAdd(using Context) = self.toAdd ++ follow.toAdd + + object NothingToAdd extends Addenda + class Errors(using Context) { /** An explanatory note to be added to error messages @@ -162,7 +171,7 @@ object ErrorReporting { def patternConstrStr(tree: Tree): String = ??? - def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = { + def typeMismatch(tree: Tree, pt: Type, addenda: Addenda = NothingToAdd): Tree = { val normTp = normalize(tree.tpe, pt) val normPt = normalize(pt, pt) @@ -184,7 +193,7 @@ object ErrorReporting { "\nMaybe you are missing an else part for the conditional?" case _ => "" - errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), implicitFailure.whyNoConversion, missingElse)) + errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), (addenda.toAdd :+ missingElse)*)) } /** A subtype log explaining why `found` does not conform to `expected` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index b0f476444754..3e2faee56d1b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -446,7 +446,7 @@ object Implicits: } } - abstract class SearchFailureType extends ErrorType { + abstract class SearchFailureType extends ErrorType, Addenda { def expectedType: Type def argument: Tree @@ -463,11 +463,6 @@ object Implicits: if (argument.isEmpty) i"match type ${clarify(expectedType)}" else i"convert from ${argument.tpe} to ${clarify(expectedType)}" } - - /** If search was for an implicit conversion, a note describing the failure - * in more detail - this is either empty or starts with a '\n' - */ - def whyNoConversion(using Context): String = "" } class NoMatchingImplicits(val expectedType: Type, val argument: Tree, constraint: Constraint = OrderingConstraint.empty) @@ -521,17 +516,21 @@ object Implicits: /** A failure value indicating that an implicit search for a conversion was not tried */ case class TooUnspecific(target: Type) extends NoMatchingImplicits(NoType, EmptyTree, OrderingConstraint.empty): - override def whyNoConversion(using Context): String = + + override def toAdd(using Context) = i""" |Note that implicit conversions were not tried because the result of an implicit conversion - |must be more specific than $target""" + |must be more specific than $target""" :: Nil override def msg(using Context) = super.msg.append("\nThe expected type $target is not specific enough, so no search was attempted") + override def toString = s"TooUnspecific" + end TooUnspecific /** An ambiguous implicits failure */ - class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType { + class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType: + def msg(using Context): Message = var str1 = err.refStr(alt1.ref) var str2 = err.refStr(alt2.ref) @@ -539,15 +538,16 @@ object Implicits: str1 = ctx.printer.toTextRef(alt1.ref).show str2 = ctx.printer.toTextRef(alt2.ref).show em"both $str1 and $str2 $qualify".withoutDisambiguation() - override def whyNoConversion(using Context): String = + + override def toAdd(using Context) = if !argument.isEmpty && argument.tpe.widen.isRef(defn.NothingClass) then - "" + Nil else val what = if (expectedType.isInstanceOf[SelectionProto]) "extension methods" else "conversions" i""" |Note that implicit $what cannot be applied because they are ambiguous; - |$explanation""" - } + |$explanation""" :: Nil + end AmbiguousImplicits class MismatchedImplicit(ref: TermRef, val expectedType: Type, diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1b816dfc9307..061d759e9ca4 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -11,7 +11,7 @@ import util.Spans._ import scala.collection.mutable import ast._ import MegaPhase._ -import config.Printers.{checks, noPrinter} +import config.Printers.{checks, noPrinter, capt} import Decorators._ import OverridingPairs.isOverridingPair import typer.ErrorReporting._ @@ -20,6 +20,7 @@ import config.SourceVersion.{`3.0`, `future`} import config.Printers.refcheck import reporting._ import Constants.Constant +import cc.{mapRoots, localRoot} object RefChecks { import tpd._ @@ -103,7 +104,10 @@ object RefChecks { val cinfo = cls.classInfo def checkSelfConforms(other: ClassSymbol) = - val otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType) + var otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType) + if ctx.phase == Phases.checkCapturesPhase then + otherSelf = mapRoots(other.localRoot.termRef, cls.localRoot.termRef)(otherSelf) + .showing(i"map self $otherSelf = $result", capt) if otherSelf.exists then if !(cinfo.selfType <:< otherSelf) then report.error(DoesNotConformToSelfType("illegal inheritance", cinfo.selfType, cls, otherSelf, "parent", other), diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c896631dfab3..6ae7e96c40aa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3204,6 +3204,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer protected def makeContextualFunction(tree: untpd.Tree, pt: Type)(using Context): Tree = { val defn.FunctionOf(formals, _, true) = pt.dropDependentRefinement: @unchecked + val paramNamesOrNil = pt match + case RefinedType(_, _, rinfo: MethodType) => rinfo.paramNames + case _ => Nil // The getter of default parameters may reach here. // Given the code below @@ -3236,7 +3239,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => paramTypes.map(_ => false) } - val ifun = desugar.makeContextualFunction(paramTypes, tree, erasedParams) + val ifun = desugar.makeContextualFunction(paramTypes, paramNamesOrNil, tree, erasedParams) typr.println(i"make contextual function $tree / $pt ---> $ifun") typedFunctionValue(ifun, pt) } diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala index dd766dc99c7e..b243145c9e5f 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -14,7 +14,10 @@ abstract class SimpleIdentitySet[+Elem <: AnyRef] { def contains[E >: Elem <: AnyRef](x: E): Boolean def foreach(f: Elem => Unit): Unit def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean - def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] + def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = + var acc: SimpleIdentitySet[B] = SimpleIdentitySet.empty + foreach(x => acc += f(x)) + acc def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A def toList: List[Elem] def iterator: Iterator[Elem] @@ -63,7 +66,7 @@ object SimpleIdentitySet { def contains[E <: AnyRef](x: E): Boolean = false def foreach(f: Nothing => Unit): Unit = () def exists[E <: AnyRef](p: E => Boolean): Boolean = false - def map[B <: AnyRef](f: Nothing => B): SimpleIdentitySet[B] = empty + override def map[B <: AnyRef](f: Nothing => B): SimpleIdentitySet[B] = empty def /: [A, E <: AnyRef](z: A)(f: (A, E) => A): A = z def toList = Nil def iterator = Iterator.empty @@ -79,7 +82,7 @@ object SimpleIdentitySet { def foreach(f: Elem => Unit): Unit = f(x0.asInstanceOf[Elem]) def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean = p(x0.asInstanceOf[E]) - def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = + override def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = Set1(f(x0.asInstanceOf[Elem])) def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A = f(z, x0.asInstanceOf[E]) @@ -99,8 +102,10 @@ object SimpleIdentitySet { def foreach(f: Elem => Unit): Unit = { f(x0.asInstanceOf[Elem]); f(x1.asInstanceOf[Elem]) } def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean = p(x0.asInstanceOf[E]) || p(x1.asInstanceOf[E]) - def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = - Set2(f(x0.asInstanceOf[Elem]), f(x1.asInstanceOf[Elem])) + override def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = + val y0 = f(x0.asInstanceOf[Elem]) + val y1 = f(x1.asInstanceOf[Elem]) + if y0 eq y1 then Set1(y0) else Set2(y0, y1) def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A = f(f(z, x0.asInstanceOf[E]), x1.asInstanceOf[E]) def toList = x0.asInstanceOf[Elem] :: x1.asInstanceOf[Elem] :: Nil @@ -133,8 +138,14 @@ object SimpleIdentitySet { } def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean = p(x0.asInstanceOf[E]) || p(x1.asInstanceOf[E]) || p(x2.asInstanceOf[E]) - def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = - Set3(f(x0.asInstanceOf[Elem]), f(x1.asInstanceOf[Elem]), f(x2.asInstanceOf[Elem])) + override def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = + val y0 = f(x0.asInstanceOf[Elem]) + val y1 = f(x1.asInstanceOf[Elem]) + val y2 = f(x2.asInstanceOf[Elem]) + if y1 eq y0 then + if y2 eq y0 then Set1(y0) else Set2(y0, y2) + else if (y2 eq y0) || (y2 eq y1) then Set2(y0, y1) + else Set3(y0, y1, y2) def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A = f(f(f(z, x0.asInstanceOf[E]), x1.asInstanceOf[E]), x2.asInstanceOf[E]) def toList = x0.asInstanceOf[Elem] :: x1.asInstanceOf[Elem] :: x2.asInstanceOf[Elem] :: Nil @@ -182,8 +193,6 @@ object SimpleIdentitySet { } def exists[E >: Elem <: AnyRef](p: E => Boolean): Boolean = xs.asInstanceOf[Array[E]].exists(p) - def map[B <: AnyRef](f: Elem => B): SimpleIdentitySet[B] = - SetN(xs.map(x => f(x.asInstanceOf[Elem]).asInstanceOf[AnyRef])) def /: [A, E >: Elem <: AnyRef](z: A)(f: (A, E) => A): A = xs.asInstanceOf[Array[E]].foldLeft(z)(f) def toList: List[Elem] = { diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 6a5e92a51a37..9529f94a3890 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -32,7 +32,8 @@ class BootstrappedOnlyCompilationTests { ).checkCompile() } - @Test def posWithCompilerCC: Unit = + // @Test + def posWithCompilerCC: Unit = implicit val testGroup: TestGroup = TestGroup("compilePosWithCompilerCC") aggregateTests( compileDir("tests/pos-with-compiler-cc/dotc", withCompilerOptions.and("-language:experimental.captureChecking")) diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index 1b50da9b7ed0..b1b2fa8e80cc 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -705,6 +705,8 @@ Generally, the string following the capture set consists of alternating numbers - `F` : a variable resulting from _filtering_ the elements of the variable indicated by the string to the right, - `I` : a variable resulting from an _intersection_ of two capture sets, - `D` : a variable resulting from the set _difference_ of two capture sets. + - `R` : a regular variable that _refines_ a class parameter, so that the capture + set of a constructor argument is known in the class instance type. At the end of a compilation run, `-Ycc-debug` will print all variable dependencies of variables referred to in previous output. Here is an example: ``` @@ -723,3 +725,5 @@ This section lists all variables that appeared in previous diagnostics and their - variable `31` has a constant fixed superset `{xs, f}` - variable `32` has no dependencies. + + diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 50f65a29c912..a32d27a5c28b 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -4,12 +4,18 @@ import annotation.experimental @experimental object caps: + class Cap // should be @erased + /** The universal capture reference (deprecated) */ @deprecated("Use `cap` instead") - val `*`: Any = () + val `*`: Cap = cap /** The universal capture reference */ - val cap: Any = () + val cap: Cap = Cap() + + given Cap = cap + + def capIn(scope: String): Cap = cap object unsafe: @@ -40,9 +46,3 @@ import annotation.experimental def unsafeBoxFunArg: T => U = f end unsafe - - /** An annotation that expresses the sealed modifier on a type parameter - * Should not be directly referred to in source - */ - @deprecated("The Sealed annotation should not be directly used in source code.\nUse the `sealed` modifier on type parameters instead.") - class Sealed extends annotation.Annotation diff --git a/tests/neg-custom-args/captures/boundschecks2.scala b/tests/neg-custom-args/captures/boundschecks2.scala index 923758d722f9..159fc0691f42 100644 --- a/tests/neg-custom-args/captures/boundschecks2.scala +++ b/tests/neg-custom-args/captures/boundschecks2.scala @@ -8,6 +8,6 @@ object test { val foo: C[Tree^] = ??? // error type T = C[Tree^] // error - val bar: T -> T = ??? + val bar: T -> T = ??? // error val baz: C[Tree^] -> Unit = ??? // error } diff --git a/tests/neg-custom-args/captures/box-adapt-boxing.scala b/tests/neg-custom-args/captures/box-adapt-boxing.scala index ea133051a21a..4b631472bad4 100644 --- a/tests/neg-custom-args/captures/box-adapt-boxing.scala +++ b/tests/neg-custom-args/captures/box-adapt-boxing.scala @@ -1,11 +1,11 @@ trait Cap def main(io: Cap^, fs: Cap^): Unit = { - val test1: Unit -> Unit = _ => { // error + val test1: Unit -> Unit = _ => { type Op = [T] -> (T ->{io} Unit) -> Unit val f: (Cap^{io}) -> Unit = ??? val op: Op = ??? - op[Cap^{io}](f) + op[Cap^{io}](f) // error // expected type of f: {io} (box {io} Cap) -> Unit // actual type: ({io} Cap) -> Unit // adapting f to the expected type will also diff --git a/tests/neg-custom-args/captures/box-unsoundness.scala b/tests/neg-custom-args/captures/box-unsoundness.scala new file mode 100644 index 000000000000..e9436b7236cc --- /dev/null +++ b/tests/neg-custom-args/captures/box-unsoundness.scala @@ -0,0 +1,6 @@ +@annotation.capability class CanIO { def use(): Unit = () } +def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x) +def test(io: CanIO): Unit = + val f = use[CanIO](io) + val g: () -> Unit = () => f(x => x.use()) // error + // was UNSOUND: g uses the capability io but has an empty capture set \ No newline at end of file diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index b1d8fb3b5404..61b83fc24688 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -5,10 +5,8 @@ | Required: (x$0: Int) ->{cap2} Int | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:19:5 ---------------------------------------- +-- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ - | Found: () ?->{cap2} I - | Required: () ?->{cap1} I - | - | longer explanation available when compiling with `-explain` + | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} + | of an enclosing function literal with expected type () ?->{cap1} I diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index f14951f410c4..470e3caef967 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -14,14 +14,14 @@ def raise[E <: Exception](e: E): Nothing throws E = throw e def foo(x: Boolean): Int throws Fail = if x then 1 else raise(Fail()) -def handle[E <: Exception, sealed R <: Top](op: (CanThrow[E]) => R)(handler: E => R): R = - val x: CanThrow[E] = ??? +def handle[E <: Exception, R <: Top](op: (lcap: caps.Cap) ?-> (CT[E] @retains(lcap)) => R)(handler: E => R): R = + val x: CT[E] = ??? try op(x) catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { // error - (x: CanThrow[Exception]) => () => raise(new Exception)(using x) + 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 85d3b2a7ddcb..124bd61a0109 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,21 +1,17 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:4:2 ------------------------------------------ +-- Error: tests/neg-custom-args/captures/capt1.scala:4:11 -------------------------------------------------------------- 4 | () => if x == null then y else y // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: () ->{x} C^? - | Required: () -> C - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:7:2 ------------------------------------------ + | ^ + | (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 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: () ->{x} C^? - | Required: Matchable - | - | longer explanation available when compiling with `-explain` + | ^ + | (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 | ^ - | Found: Int ->{x} Int + | Found: (y: Int) ->{x} Int | Required: Matchable 15 | f | @@ -37,10 +33,8 @@ 27 | def m() = if x == null then y else y | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:32:24 ---------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:32:30 ------------------------------------------------------------- 32 | val z2 = h[() -> Cap](() => x) // error - | ^^^^^^^ - | Found: () ->{x} box C^ - | Required: () -> box C^ - | - | longer explanation available when compiling with `-explain` + | ^ + | (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^ diff --git a/tests/neg-custom-args/captures/cc-glb.check b/tests/neg-custom-args/captures/cc-glb.check index 7e0d2ff85691..669cf81a082b 100644 --- a/tests/neg-custom-args/captures/cc-glb.check +++ b/tests/neg-custom-args/captures/cc-glb.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-glb.scala:7:19 ---------------------------------------- 7 | val x2: Foo[T] = x1 // error | ^^ - | Found: (x1 : (Foo[T]^) & (Foo[Any]^{io})) + | Found: (x1 : (Foo[T] & Foo[Any])^{io}) | Required: Foo[T] | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-this.check b/tests/neg-custom-args/captures/cc-this.check index 47207f913f1d..335302c5c259 100644 --- a/tests/neg-custom-args/captures/cc-this.check +++ b/tests/neg-custom-args/captures/cc-this.check @@ -8,8 +8,8 @@ -- Error: tests/neg-custom-args/captures/cc-this.scala:10:15 ----------------------------------------------------------- 10 | class C2(val x: () => Int): // error | ^ - | reference (C2.this.x : () => Int) is not included in allowed capture set {} of the self type of class C2 + | reference (C2.this.x : () => Int) is not included in the allowed capture set {} of the self type of class C2 -- Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ------------------------------------------------------------ 17 | class C4(val f: () => Int) extends C3 // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | reference (C4.this.f : () => Int) is not included in allowed capture set {} of pure base class class C3 + | reference (C4.this.f : () => Int) is not included in the allowed capture set {} of pure base class class C3 diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index d2f87a131791..fbf7d5f4d831 100644 --- a/tests/neg-custom-args/captures/cc-this2.check +++ b/tests/neg-custom-args/captures/cc-this2.check @@ -2,5 +2,5 @@ -- Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 -------------------------------------------------------- 2 |class D extends C: // error |^ - |reference (caps.cap : Any) is not included in allowed capture set {} of pure base class class C + |reference (cap : caps.Cap) is not included in the allowed capture set {} of pure base class class C 3 | this: D^ => diff --git a/tests/neg-custom-args/captures/cc-this5.check b/tests/neg-custom-args/captures/cc-this5.check index 84ac97474b80..522279339bef 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,7 +1,8 @@ -- 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^{cap[test]}) 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/ctest.scala b/tests/neg-custom-args/captures/ctest.scala deleted file mode 100644 index ad10b43a7773..000000000000 --- a/tests/neg-custom-args/captures/ctest.scala +++ /dev/null @@ -1,6 +0,0 @@ -class CC -type Cap = CC^ - -def test(cap1: Cap, cap2: Cap) = - var b: List[String => String] = Nil // error - val bc = b.head // was error, now OK diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index a77d66382095..382ee177aa91 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -5,10 +5,8 @@ | Required: () -> Proc^{f} | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/eta.scala:6:14 ------------------------------------------- +-- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- 6 | bar( () => f ) // error - | ^^^^^^^ - | Found: () ->{f} box () ->{f} Unit - | Required: () -> box () ->? Unit - | - | longer explanation available when compiling with `-explain` + | ^ + | (f : () => Unit) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box () ->? Unit diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 189a6f091c0b..4533cf2c083d 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,13 +1,13 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:2:6 ----------------------------------------------- 2 |class Err extends Exception: // error |^ - |reference (caps.cap : Any) is not included in allowed capture set {} of pure base class class Throwable + |reference (cap : caps.Cap) is not included in the allowed capture set {} of pure base class class Throwable 3 | self: Err^ => -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ - |(c : Any^) cannot be referenced here; it is not included in the allowed capture set {} of pure base class class Throwable + |(c : Any^{cap[test]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class class Throwable -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:8 ----------------------------------------------- 8 | class Err3(c: Any^) extends Exception // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | reference (Err3.this.c : Any^) is not included in allowed capture set {} of pure base class class Throwable + | reference (Err3.this.c : Any^) is not included in the allowed capture set {} of pure base class class Throwable diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index 830563f51de3..a7ef9d987b1d 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,14 +5,14 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // error + var file: File^ = uninitialized def log = file.write("log") -def withFile[T](op: (f: File^) => T): T = +def withFile[T](op: (l: caps.Cap) ?-> (f: File^{l}) => T): T = op(new File) def test = withFile: f => val o = Service() - o.file = f + o.file = f // error o.log diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index 58d12f8b6ce5..f72325a0be8a 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -2,32 +2,33 @@ import language.experimental.captureChecking trait Cap { def use(): Unit } -def localCap[sealed T](op: (cap: Cap^{cap}) => T): T = ??? +def localCap[T](op: (lcap: caps.Cap) ?-> (c: Cap^{lcap}) => T): T = ??? def main(io: Cap^{cap}, net: Cap^{cap}): Unit = { - val test1 = localCap { cap => // error - () => { cap.use() } + + val test1 = localCap { c => // error + () => { c.use() } } - val test2: (cap: Cap^{cap}) -> () ->{cap} Unit = - localCap { cap => // should work - (cap1: Cap^{cap}) => () => { cap1.use() } + val test2: (c: Cap^{cap}) -> () ->{cap} Unit = + localCap { c => // error, was: should work + (c1: Cap^{cap}) => () => { c1.use() } } - val test3: (cap: Cap^{io}) -> () ->{io} Unit = - localCap { cap => // should work - (cap1: Cap^{io}) => () => { cap1.use() } + val test3: (c: Cap^{io}) -> () ->{io} Unit = + localCap { c => // should work + (c1: Cap^{io}) => () => { c1.use() } } - val test4: (cap: Cap^{io}) -> () ->{net} Unit = - localCap { cap => // error - (cap1: Cap^{io}) => () => { cap1.use() } + val test4: (c: Cap^{io}) -> () ->{net} Unit = + localCap { c => // error + (c1: Cap^{io}) => () => { c1.use() } } - def localCap2[sealed T](op: (cap: Cap^{io}) => T): T = ??? + def localCap2[T](op: (c: Cap^{io}) => T): T = ??? val test5: () ->{io} Unit = - localCap2 { cap => // ok - () => { cap.use() } + localCap2 { c => // ok + () => { c.use() } } } diff --git a/tests/neg-custom-args/captures/i15049.scala b/tests/neg-custom-args/captures/i15049.scala index d978e0e1ad0f..e60367946377 100644 --- a/tests/neg-custom-args/captures/i15049.scala +++ b/tests/neg-custom-args/captures/i15049.scala @@ -2,7 +2,7 @@ class Session: def request = "Response" class Foo: private val session: Session^{cap} = new Session - def withSession[sealed T](f: (Session^{cap}) => T): T = f(session) + def withSession[T](f: (local: caps.Cap) ?-> (Session^{local}) => T): T = f(session) def Test: Unit = val f = new Foo diff --git a/tests/neg-custom-args/captures/i15116.check b/tests/neg-custom-args/captures/i15116.check index 4b637a7c2e40..765477df7466 100644 --- a/tests/neg-custom-args/captures/i15116.check +++ b/tests/neg-custom-args/captures/i15116.check @@ -9,7 +9,7 @@ 5 | val x = Foo(m) // error | ^^^^^^^^^^^^^^ | Non-local value x cannot have an inferred type - | Foo{val m: String^}^{Baz.this} + | Foo{val m: String^{Baz.this}}^{Baz.this} | with non-empty capture set {Baz.this}. | The type needs to be declared explicitly. -- Error: tests/neg-custom-args/captures/i15116.scala:7:6 -------------------------------------------------------------- @@ -23,6 +23,6 @@ 9 | val x = Foo(m) // error | ^^^^^^^^^^^^^^ | Non-local value x cannot have an inferred type - | Foo{val m: String^}^{Baz2.this} + | Foo{val m: String^{Baz2.this}}^{Baz2.this} | with non-empty capture set {Baz2.this}. | The type needs to be declared explicitly. diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 04cbe14f40a3..cb6b40361add 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,22 +1,45 @@ +-- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ +19 | val c : C^{x} = new C(x) // error + | ^ + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error | ^^^^^^^ - | Found: (C{val arg: C^}^{c} => Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[..main1](from instantiating box1), c} Unit) ->{c} Unit + | Required: (C^ => Unit) -> Unit | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ +26 | val c : C^{x} = new C(x) // error + | ^ + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ - | Found: (C{val arg: C^}^{c} => Unit) ->{c} Unit - | Required: Observe[C^] + | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[..main2](from instantiating box2), c} Unit) ->{c} Unit + | Required: (C^ => Unit) -> Unit | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error - | ^^^^^^^ - | Found: (C{val arg: C^}^ => Unit) ->? Unit - | Required: Observe[C]^ + | ^ + | Found: box C^{cap[c]} + | Required: box C{val arg: C^?}^? + | + | Note that reference (cap[c] : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method main3 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:34:29 --------------------------------------- +34 | boxed2((cap: C^) => unsafe(c)) // error + | ^ + | Found: C^{cap[c]} + | Required: C^{'cap[..main3](from instantiating unsafe)} + | + | Note that reference (cap[c] : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method main3 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- @@ -25,4 +48,7 @@ | Found: () ->{x} Unit | Required: () -> Unit | + | Note that reference (cap[c] : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method main3 + | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15772.scala b/tests/neg-custom-args/captures/i15772.scala index e4efb6b9ccab..7e62fdeffb24 100644 --- a/tests/neg-custom-args/captures/i15772.scala +++ b/tests/neg-custom-args/captures/i15772.scala @@ -16,14 +16,14 @@ class C(val arg: C^) { def main1(x: C^) : () -> Int = () => - val c : C^{x} = new C(x) + val c : C^{x} = new C(x) // error val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error boxed1((cap: C^) => unsafe(c)) 0 def main2(x: C^) : () -> Int = () => - val c : C^{x} = new C(x) + val c : C^{x} = new C(x) // error val boxed2 : Observe[C^] = box2(c) // error boxed2((cap: C^) => unsafe(c)) 0 @@ -31,7 +31,7 @@ def main2(x: C^) : () -> Int = def main3(x: C^) = def c : C^ = new C(x) val boxed2 : Observe[C]^ = box2(c) // error - boxed2((cap: C^) => unsafe(c)) + boxed2((cap: C^) => unsafe(c)) // error 0 trait File: diff --git a/tests/neg-custom-args/captures/i15923.scala b/tests/neg-custom-args/captures/i15923.scala index 3994b34f5928..754fd0687037 100644 --- a/tests/neg-custom-args/captures/i15923.scala +++ b/tests/neg-custom-args/captures/i15923.scala @@ -3,12 +3,12 @@ type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) def bar() = { - def withCap[sealed X](op: (Cap^) => X): X = { - val cap: Cap^ = new Cap { def use() = { println("cap is used"); 0 } } + def withCap[X](op: (lcap: caps.Cap) ?-> Cap^{lcap} => X): X = { + val cap: Cap = new Cap { def use() = { println("cap is used"); 0 } } val result = op(cap) result } - val leak = withCap(cap => mkId(cap)) // error + val leak = withCap(cap => mkId(cap)) // error // error leak { cap => cap.use() } } \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index d22c7f02d5fb..3bb276d1fc5c 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -12,16 +12,16 @@ def withCap[T](op: Cap^ => T): T = { def main(fs: Cap^): Unit = { def badOp(io: Cap^{cap}): Unit ->{} Unit = { - val op1: Unit ->{io} Unit = (x: Unit) => // error // limitation + val op1: Unit ->{io} Unit = (x: Unit) => expect[Cap^] { io.use() - fs + fs // error (limitation) } - val op2: Unit ->{fs} Unit = (x: Unit) => // error // limitation + val op2: Unit ->{fs} Unit = (x: Unit) => expect[Cap^] { fs.use() - io + io // error (limitation) } val op3: Unit ->{io} Unit = (x: Unit) => // ok @@ -30,13 +30,13 @@ def main(fs: Cap^): Unit = { io } - val op4: Unit ->{} Unit = (x: Unit) => // ok + val op4: Unit ->{} Unit = (x: Unit) => // o k expect[Cap^](io) - val op: Unit -> Unit = (x: Unit) => // error + val op: Unit -> Unit = (x: Unit) => expect[Cap^] { - io.use() - io + io.use() // error + io // error } op } diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 4b7611fc3fb7..aeb410f07d65 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: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^{cap[LazyCons]}}^{cap1}) + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- @@ -37,6 +37,6 @@ 22 | def tail: LazyList[Nothing]^ = ??? // error overriding | ^ | error overriding method tail in class LazyList of type -> lazylists.LazyList[Nothing]; - | method tail of type -> lazylists.LazyList[Nothing]^ has incompatible type + | method tail of type -> lazylists.LazyList[Nothing]^{cap[tail]} has incompatible type | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index f58ed265d3be..74318b6bb254 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,11 +1,13 @@ --- 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. -37 | tabulate(10) { i => +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:37:4 -------------------------- +37 | tabulate(10) { i => // error + | ^ + | Found: LazyList[Int]^ + | Required: LazyList[Int]^? + | + | Note that reference (cap : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method problem 38 | if i > 9 then throw Ex1() 39 | i * i 40 | } -41 | catch case ex: Ex1 => LazyNil + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.scala b/tests/neg-custom-args/captures/lazylists-exceptions.scala index 6a72facf7285..f70f66cd6950 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.scala +++ b/tests/neg-custom-args/captures/lazylists-exceptions.scala @@ -33,8 +33,8 @@ def tabulate[A](n: Int)(gen: Int => A): LazyList[A]^{gen} = class Ex1 extends Exception def problem = - try // error - tabulate(10) { i => + try + tabulate(10) { i => // error if i > 9 then throw Ex1() i * i } diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index 72efbc08f8e2..5038ab1bea93 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -25,11 +25,11 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + |(f : A ->{cap[map3]} B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped -- Error: tests/neg-custom-args/captures/lazylists2.scala:41:48 -------------------------------------------------------- 41 | def tail: LazyList[B]^{this}= xs.tail.map(f) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + |(f : A ->{cap[map3]} B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ 45 | final class Mapped extends LazyList[B]: // error | ^ @@ -45,5 +45,5 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:60:10 -------------------------------------------------------- 60 | class Mapped2 extends Mapped: // error | ^ - | references {f, xs} are not all included in allowed capture set {} of the self type of class Mapped2 + | references {f, xs} are not all included in the allowed capture set {} of the self type of class Mapped2 61 | this: Mapped => diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 590f871c57d5..509069797def 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,4 +1,11 @@ --- Error: tests/neg-custom-args/captures/leaked-curried.scala:12:52 ---------------------------------------------------- -12 | val get: () ->{} () ->{io} Cap^ = () => () => io // error - | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure +-- Error: tests/neg-custom-args/captures/leaked-curried.scala:13:20 ---------------------------------------------------- +13 | () => () => io // error + | ^^ + |(io : Cap^{cap[main]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure +-- [E164] Declaration Error: tests/neg-custom-args/captures/leaked-curried.scala:12:10 --------------------------------- +12 | val get: () ->{} () ->{io} Cap^ = // error + | ^ + | error overriding value get in trait Box of type () -> () ->{cap[Box]} Cap^{cap[Box]}; + | value get of type () -> () ->{io} Cap^ has incompatible type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.scala b/tests/neg-custom-args/captures/leaked-curried.scala index 000f2ef72cb0..a566999d7c39 100644 --- a/tests/neg-custom-args/captures/leaked-curried.scala +++ b/tests/neg-custom-args/captures/leaked-curried.scala @@ -1,7 +1,7 @@ trait Cap: def use(): Unit -def withCap[sealed T](op: (x: Cap^) => T): T = ??? +def withCap[T](op: (x: Cap^) => T): T = ??? trait Box: val get: () ->{} () ->{cap} Cap^ @@ -9,6 +9,7 @@ trait Box: def main(): Unit = val leaked = withCap: (io: Cap^) => class Foo extends Box, Pure: - val get: () ->{} () ->{io} Cap^ = () => () => io // error + val get: () ->{} () ->{io} Cap^ = // error + () => () => io // error new Foo val bad = leaked.get()().use() // using a leaked capability diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check new file mode 100644 index 000000000000..8d11c196b10f --- /dev/null +++ b/tests/neg-custom-args/captures/levels.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:15:11 --------------------------------------- +15 | r.setV(g) // error + | ^ + | Found: box (x: String) ->{cap3} String + | Required: box (x$0: String) ->? String + | + | Note that reference (cap3 : CC^), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | 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 new file mode 100644 index 000000000000..35fb2d490398 --- /dev/null +++ b/tests/neg-custom-args/captures/levels.scala @@ -0,0 +1,33 @@ +class CC + +def test(cap1: CC^) = + + class Ref[T](init: T): + private var v: T = init + def setV(x: T): Unit = v = x + def getV: T = v + + val r = Ref((x: String) => x) + + def scope = + val cap3: CC^ = ??? + def g(x: String): String = if cap3 == cap3 then "" else "a" + r.setV(g) // error + () + +/* + Explicit: + cap is local root of enclosing method or class, can be overridden by qualifying it. + i.e. cap[name] + + On method instantiation: All uses of cap --> cap of caller + On class instantiation: All uses of cap, or local cap of clsss --> cap of caller + + Alternative solution: root variables + - track minimal & maximal level + - updated via subsumption tests, root added handler for prefix/member + + roots: Implicitly: outer <: inner + + def withFile[T]((local: Root) ?=> op: File^{local}) => T]): T +*/ diff --git a/tests/neg-custom-args/captures/localcaps.scala b/tests/neg-custom-args/captures/localcaps.scala new file mode 100644 index 000000000000..50cbe8e0f8f9 --- /dev/null +++ b/tests/neg-custom-args/captures/localcaps.scala @@ -0,0 +1,7 @@ +class C: + def x: C^{cap[d]} = ??? // error + + def y: C^{cap[C]} = ??? // ok + private val z = (x: Int) => (c: C^{cap[z]}) => x // ok + + private val z2 = identity((x: Int) => (c: C^{cap[z2]}) => x) // error diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index c8df3777bcfa..f57aef60745b 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,36 +1,36 @@ --- [E129] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:30:4 ---------------------------------- -30 | b.x +-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:36:4 ---------------------------------- +36 | b.x | ^^^ | A pure expression does nothing in statement position; you may be omitting necessary parentheses | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/real-try.scala:12:2 ----------------------------------------------------------- -12 | 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:2 ----------------------------------------------------------- -18 | 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. -19 | () => Cell(foo(1)) -20 | catch -21 | case _: Ex1 => ??? -22 | case _: Ex2 => ??? --- Error: tests/neg-custom-args/captures/real-try.scala:24:10 ---------------------------------------------------------- -24 | 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. -25 | Cell(() => foo(1))//: Cell[box {ev} () => Unit] <: Cell[box {cap} () => Unit] -26 | catch -27 | case _: Ex1 => ??? -28 | case _: Ex2 => ??? +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:19:4 -------------------------------------- +19 | () => foo(1) // error + | ^^^^^^^^^^^^ + | Found: () => Unit + | Required: () ->? Unit + | + | Note that reference (cap : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:25:4 -------------------------------------- +25 | () => Cell(foo(1)) // error + | ^^^^^^^^^^^^^^^^^^ + | Found: () => Cell[Unit]^? + | Required: () ->? Cell[Unit]^? + | + | Note that reference (cap : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:31:4 -------------------------------------- +31 | Cell(() => foo(1))// // error + | ^^^^^^^^^^^^^^^^^^ + | Found: Cell[box () => Unit]^? + | Required: Cell[() ->? Unit]^? + | + | Note that reference (cap : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index a826fdaa4af7..8020f98f0f10 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -9,20 +9,26 @@ def foo(i: Int): (CanThrow[Ex1], CanThrow[Ex2]) ?-> Unit = class Cell[+T](val x: T) def test(): Unit = - try // error - () => foo(1) + try + () => foo(1) // no error, since result type is Unit catch case _: Ex1 => ??? case _: Ex2 => ??? - try // error - () => Cell(foo(1)) + val x = try + () => foo(1) // error catch case _: Ex1 => ??? case _: Ex2 => ??? - val b = try // error - Cell(() => foo(1))//: Cell[box {ev} () => Unit] <: Cell[box {cap} () => Unit] + val y = try + () => Cell(foo(1)) // error + catch + case _: Ex1 => ??? + case _: Ex2 => ??? + + val b = try + Cell(() => foo(1))// // error catch case _: Ex1 => ??? case _: Ex2 => ??? diff --git a/tests/neg-custom-args/captures/refs.scala b/tests/neg-custom-args/captures/refs.scala new file mode 100644 index 000000000000..b64b27ef4af0 --- /dev/null +++ b/tests/neg-custom-args/captures/refs.scala @@ -0,0 +1,54 @@ +import java.io.* + +type Proc = () => Unit + +class Ref[T](init: T): + var x: T = init + def setX(x: T): Unit = this.x = x + +class MonoRef(init: Proc): + type MonoProc = Proc + var x: MonoProc = init + def setX(x: MonoProc): Unit = this.x = x + +def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + +def test1 = + usingLogFile[Proc]: (local: caps.Cap) ?=> // error (but with a hard to parse error message) + (f: FileOutputStream^{local}) => + () => f.write(1) // this line has type () ->{local} Unit, but usingLogFile + // requires Proc, which expands to () -> 'cap[..test1](from instantiating usingLogFile) + +def test2 = + val r = new Ref[Proc](() => ()) + usingLogFile: f => + r.setX(() => f.write(10)) // error + r.x() // crash: f is closed at that point + val mr = new MonoRef(() => ()) + usingLogFile[Unit]: f => + mr.setX(() => f.write(10)) // error + +def test3 = + val r = new Ref[Proc](() => ()) + usingLogFile[Unit]: f => + r.x = () => f.write(10) // error + r.x() // crash: f is closed at that point + val mr = MonoRef(() => ()) + usingLogFile: f => + mr.x = () => f.write(10) // error + +def test4 = + var r: Proc = () => () + usingLogFile[Unit]: f => + r = () => f.write(10) // error + r() // crash: f is closed at that point + + + + + + diff --git a/tests/neg-custom-args/captures/sealed-leaks.scala b/tests/neg-custom-args/captures/sealed-leaks.scala index bf46b52194c1..df438e6973bc 100644 --- a/tests/neg-custom-args/captures/sealed-leaks.scala +++ b/tests/neg-custom-args/captures/sealed-leaks.scala @@ -2,7 +2,7 @@ import java.io.* def Test2 = - def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + def usingLogFile[T](op: (l: caps.Cap) ?-> FileOutputStream^{l} => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -11,10 +11,10 @@ def Test2 = val later = usingLogFile { f => () => f.write(0) } // error val later2 = usingLogFile[(() => Unit) | Null] { f => () => f.write(0) } // error - var x: (FileOutputStream^) | Null = null // error + var x: (FileOutputStream^) | Null = null def foo(f: FileOutputStream^, g: FileOutputStream^) = var y = if ??? then f else g // error - usingLogFile { f => x = f } + usingLogFile { f => x = f } // error later() \ No newline at end of file diff --git a/tests/neg-custom-args/captures/simple-escapes.check b/tests/neg-custom-args/captures/simple-escapes.check new file mode 100644 index 000000000000..611d4f0f0dc3 --- /dev/null +++ b/tests/neg-custom-args/captures/simple-escapes.check @@ -0,0 +1,11 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/simple-escapes.scala:16:10 ------------------------------- +16 | foo = f // error + | ^ + | Found: box FileOutputStream^{f} + | Required: box FileOutputStream^{cap[Test1]} + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/simple-escapes.scala:19:15 ---------------------------------------------------- +19 | val later2 = usingLogFile { local => f => // error + | ^^^^^^^^^^^^ + | escaping local reference local.type diff --git a/tests/neg-custom-args/captures/simple-escapes.scala b/tests/neg-custom-args/captures/simple-escapes.scala new file mode 100644 index 000000000000..0d4666179292 --- /dev/null +++ b/tests/neg-custom-args/captures/simple-escapes.scala @@ -0,0 +1,24 @@ +class FileOutputStream(str: String): + def close(): Unit = () + def write(x: Int): Unit = () + +def Test1 = + + def usingLogFile[T](op: (local: caps.Cap) -> FileOutputStream^{local} => T): T = + val logFile = FileOutputStream("log") + val result = op(caps.cap)(logFile) + logFile.close() + result + + var foo: FileOutputStream^ = FileOutputStream("") + + val later1 = usingLogFile { local => f => + foo = f // error + () => () + } + val later2 = usingLogFile { local => f => // error + () => f.write(0) + } + later1() + later2() + diff --git a/tests/neg-custom-args/captures/stack-alloc.scala b/tests/neg-custom-args/captures/stack-alloc.scala index 71b544dbe88d..befafbf13003 100644 --- a/tests/neg-custom-args/captures/stack-alloc.scala +++ b/tests/neg-custom-args/captures/stack-alloc.scala @@ -5,7 +5,7 @@ class Pooled val stack = mutable.ArrayBuffer[Pooled]() var nextFree = 0 -def withFreshPooled[sealed T](op: Pooled^ => T): T = +def withFreshPooled[T](op: (lcap: caps.Cap) ?-> Pooled^{lcap} => T): T = if nextFree >= stack.size then stack.append(new Pooled) val pooled = stack(nextFree) nextFree = nextFree + 1 diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 9afbe61d2280..5994e3901179 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,19 +1,17 @@ --- Error: tests/neg-custom-args/captures/try.scala:23:16 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------ 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. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:43 ------------------------------------------ -29 | val b = handle[Exception, () -> Nothing] { // error - | ^ - | Found: (x: CT[Exception]^) ->? () ->{x} Nothing - | Required: (x$0: CanThrow[Exception]) => () -> Nothing -30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) -31 | } { + | ^ + |Found: (lcap: caps.Cap) ?->? (x$0: CT[Exception]^{lcap}) ->? box CT[Exception]^{lcap} + |Required: (lcap: caps.Cap) ?-> CT[Exception]^{lcap} ->{'cap[..test](from instantiating handle)} box CT[Exception]^ +24 | (x: CanThrow[Exception]) => x +25 | }{ | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- +30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + | ^ + | (x : CT[Exception]^{lcap}) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () ->? Nothing -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- 47 |val global: () -> Int = handle { 48 | (x: CanThrow[Exception]) => @@ -22,7 +20,7 @@ 51 | 22 52 |} { // error | ^ - | Found: () ->{x$0} Int + | Found: () ->{x$0, lcap} Int | Required: () -> Int 53 | (ex: Exception) => () => 22 54 |} @@ -31,7 +29,4 @@ -- Error: tests/neg-custom-args/captures/try.scala:35:11 --------------------------------------------------------------- 35 | val xx = handle { // error | ^^^^^^ - | Sealed type variable R cannot be instantiated to box () => Int 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. + | escaping local reference lcap.type diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 3c6f0605d8b9..fe58145bca54 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -14,8 +14,8 @@ def raise[E <: Exception](e: E): Nothing throws E = throw e def foo(x: Boolean): Int throws Fail = if x then 1 else raise(Fail()) -def handle[E <: Exception, sealed R <: Top](op: CanThrow[E] => R)(handler: E => R): R = - val x: CanThrow[E] = ??? +def handle[E <: Exception, R <: Top](op: (lcap: caps.Cap) ?-> CT[E]^{lcap} => R)(handler: E => R): R = + val x: CT[E] = ??? try op(x) catch case ex: E => handler(ex) @@ -26,8 +26,8 @@ def test = (ex: Exception) => ??? } - val b = handle[Exception, () -> Nothing] { // error - (x: CanThrow[Exception]) => () => raise(new Exception)(using x) + val b = handle[Exception, () -> Nothing] { + (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error } { (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/try3.scala b/tests/neg-custom-args/captures/try3.scala index 4c6835353c3f..004cda6a399c 100644 --- a/tests/neg-custom-args/captures/try3.scala +++ b/tests/neg-custom-args/captures/try3.scala @@ -4,9 +4,9 @@ class CT[E] type CanThrow[E] = CT[E]^ type Top = Any^ -def handle[E <: Exception, sealed T <: Top](op: CanThrow[E] ?=> T)(handler: E => T): T = - val x: CanThrow[E] = ??? - try op(using x) +def handle[E <: Exception, T <: Top](op: (lcap: caps.Cap) ?-> CT[E]^{lcap} ?=> T)(handler: E => T): T = + val x: CT[E] = ??? + try op(using caps.cap)(using x) catch case ex: E => handler(ex) def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = @@ -14,7 +14,8 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = @main def Test: Int = def f(a: Boolean) = - handle { // error + handle { // error: implementation restriction: curried dependent CFT not supported + // should work but give capture error if !a then raise(IOException()) (b: Boolean) => if !b then raise(IOException()) diff --git a/tests/neg-custom-args/captures/usingLogFile-alt.check b/tests/neg-custom-args/captures/usingLogFile-alt.check index 9444bc9dc46a..93fc3ca5edb5 100644 --- a/tests/neg-custom-args/captures/usingLogFile-alt.check +++ b/tests/neg-custom-args/captures/usingLogFile-alt.check @@ -1,7 +1,10 @@ -- Error: tests/neg-custom-args/captures/usingLogFile-alt.scala:18:2 --------------------------------------------------- -18 | usingFile( // error +18 | usingFile( // error | ^^^^^^^^^ - | Sealed type variable T 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 usingFile - | leaking as part of its result. + | reference (file : java.io.OutputStream^{lcap}) is not included in the allowed capture set {x$0, x$0²} + | + | Note that reference (file : java.io.OutputStream^{lcap}), defined at level 1 + | cannot be included in outer capture set {x$0, x$0}, defined at level 0 in package + | + | where: x$0 is a reference to a value parameter + | x$0² is a reference to a value parameter diff --git a/tests/neg-custom-args/captures/usingLogFile-alt.scala b/tests/neg-custom-args/captures/usingLogFile-alt.scala index 6b529ee6f892..36f6ecf1426e 100644 --- a/tests/neg-custom-args/captures/usingLogFile-alt.scala +++ b/tests/neg-custom-args/captures/usingLogFile-alt.scala @@ -7,15 +7,15 @@ object Test: class Logger(f: OutputStream^): def log(msg: String): Unit = ??? - def usingFile[sealed T](name: String, op: OutputStream^ => T): T = + def usingFile[T](name: String, op: (lcap: caps.Cap) ?-> OutputStream^{lcap} => T): T = val f = new FileOutputStream(name) val result = op(f) f.close() result - def usingLogger[sealed T](f: OutputStream^)(op: Logger^{f} => T): T = ??? + def usingLogger[T](f: OutputStream^)(op: Logger^{f} => T): T = ??? - usingFile( // error + usingFile( // error "foo", file => { usingLogger(file)(l => () => l.log("test")) diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index ff4c9fd3105f..9550b5864586 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,47 +1,28 @@ --- Error: tests/neg-custom-args/captures/usingLogFile.scala:31:6 ------------------------------------------------------- -31 | var later3: () => Unit = () => () // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Mutable variable later3 cannot have type box () => Unit since - | that type captures the root capability `cap`. - | This restriction serves to prevent local capabilities from escaping the scope where they are defined. --- Error: tests/neg-custom-args/captures/usingLogFile.scala:35:6 ------------------------------------------------------- -35 | var later4: Cell[() => Unit] = Cell(() => ()) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Mutable variable later4 cannot have type Test2.Cell[() => Unit] since - | the part () => Unit of that type captures the root capability `cap`. - | This restriction serves to prevent local capabilities from escaping the scope where they are defined. +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:32:37 ------------------------------------------------------ +32 | usingLogFile { f => later3 = () => f.write(0) } // error + | ^ + |(f : java.io.FileOutputStream^{local}) cannot be referenced here; it is not included in the allowed capture set {cap[]} + |of an enclosing function literal with expected type box () ->{cap[]} Unit +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:36:35 --------------------------------- +36 | usingLogFile { f => later4 = Cell(() => f.write(0)) } // error + | ^^^^^^^^^^^^^^^^^^^^^^ + | Found: Test2.Cell[box () ->{f} Unit]^? + | Required: Test2.Cell[() ->{cap[]} Unit] + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/usingLogFile.scala:23:14 ------------------------------------------------------ 23 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^ - | Sealed type variable T 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 usingLogFile - | leaking as part of its result. + | escaping local reference local.type -- Error: tests/neg-custom-args/captures/usingLogFile.scala:28:23 ------------------------------------------------------ 28 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Test2.Cell[() => Unit]^? since - | the part () => Unit of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method usingLogFile - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:6 ------------------------------------------------------- + | escaping local reference local.type +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:14 ------------------------------------------------------ 47 | val later = usingLogFile { f => () => f.write(0) } // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Non-local value later cannot have an inferred type - | () => Unit - | with non-empty capture set {x$0, cap}. - | The type needs to be declared explicitly. + | ^^^^^^^^^^^^ + | escaping local reference local.type -- Error: tests/neg-custom-args/captures/usingLogFile.scala:62:16 ------------------------------------------------------ 62 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error | ^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box (x$0: Int) => Unit since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method usingFile - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/usingLogFile.scala:71:16 ------------------------------------------------------ -71 | val later = usingFile("logfile", // error - | ^^^^^^^^^ - | Sealed type variable T 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 usingFile - | leaking as part of its result. + | escaping local reference local.type diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index e7c23573ca6e..b87b81d0eda8 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -3,18 +3,18 @@ import annotation.capability object Test1: - def usingLogFile[sealed T](op: FileOutputStream => T): T = + def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() result - val later = usingLogFile { f => () => f.write(0) } + private val later = usingLogFile { f => () => f.write(0) } // OK, `f` has global lifetime later() object Test2: - def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -28,17 +28,17 @@ object Test2: private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error later2.x() - var later3: () => Unit = () => () // error - usingLogFile { f => later3 = () => f.write(0) } + var later3: () => Unit = () => () + usingLogFile { f => later3 = () => f.write(0) } // error later3() - var later4: Cell[() => Unit] = Cell(() => ()) // error - usingLogFile { f => later4 = Cell(() => f.write(0)) } + var later4: Cell[() => Unit] = Cell(() => ()) + usingLogFile { f => later4 = Cell(() => f.write(0)) } // error later4.x() object Test3: - def usingLogFile[sealed T](op: FileOutputStream^ => T) = + def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T) = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -50,7 +50,7 @@ object Test4: class Logger(f: OutputStream^): def log(msg: String): Unit = ??? - def usingFile[sealed T](name: String, op: OutputStream^ => T): T = + def usingFile[T](name: String, op: (local: caps.Cap) ?-> OutputStream^{local} => T): T = val f = new FileOutputStream(name) val result = op(f) f.close() @@ -62,12 +62,11 @@ object Test4: val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error later(1) - - def usingLogger[sealed T](f: OutputStream^, op: Logger^{f} => T): T = + def usingLogger[T](f: OutputStream^, op: (local: caps.Cap) ?-> Logger^{f} => T): T = val logger = Logger(f) op(logger) def test = - val later = usingFile("logfile", // error + val later = usingFile("logfile", usingLogger(_, l => () => l.log("test"))) // ok, since we can widen `l` to `file` instead of to `cap` later() diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index a7e38dbfdb8a..f8abe22e9d53 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,15 +1,3 @@ --- Error: tests/neg-custom-args/captures/vars.scala:13:6 --------------------------------------------------------------- -13 | var a: String => String = f // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Mutable variable a cannot have type box String => String since - | that type captures the root capability `cap`. - | This restriction serves to prevent local capabilities from escaping the scope where they are defined. --- Error: tests/neg-custom-args/captures/vars.scala:14:6 --------------------------------------------------------------- -14 | var b: List[String => String] = Nil // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Mutable variable b cannot have type List[String => String] since - | the part String => String of that type captures the root capability `cap`. - | This restriction serves to prevent local capabilities from escaping the scope where they are defined. -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:11:24 ----------------------------------------- 11 | val z2c: () -> Unit = z2 // error | ^^ @@ -17,10 +5,46 @@ | Required: () -> Unit | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:32:2 --------------------------------------------------------------- -32 | local { cap3 => // error +-- Error: tests/neg-custom-args/captures/vars.scala:23:14 -------------------------------------------------------------- +23 | a = x => g(x) // error + | ^^^^ + | reference (cap3 : CC^) is not included in the allowed capture set {cap[test]} + | of an enclosing function literal with expected type box String ->{cap[test]} String +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:24:8 ------------------------------------------ +24 | a = g // error + | ^ + | Found: box (x: String) ->{cap3} String + | Required: box (x$0: String) ->{cap[test]} String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:26:12 ----------------------------------------- +26 | b = List(g) // error + | ^^^^^^^ + | Found: List[box (x$0: String) ->{cap3} String] + | Required: List[String ->{cap[test]} String] + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:30:10 ----------------------------------------- +30 | val s = scope // error (but should be OK, we need to allow poly-captures) + | ^^^^^ + | Found: (x$0: String) ->{cap[scope]} String + | Required: (x$0: String) ->? String + | + | Note that reference (cap[scope] : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:31:29 ----------------------------------------- +31 | val sc: String => String = scope // error (but should also be OK) + | ^^^^^ + | Found: (x$0: String) ->{cap[scope]} String + | Required: String => String + | + | Note that reference (cap[scope] : caps.Cap), defined at level 2 + | cannot be included in outer capture set ?, defined at level 1 in method test + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/vars.scala:35:2 --------------------------------------------------------------- +35 | local { root => cap3 => // error | ^^^^^ - | Sealed type variable T cannot be instantiated to box (x$0: String) => String since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method local - | leaking as part of its result. + | escaping local reference root.type diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index b7761952167e..9b280a42a2f2 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -9,9 +9,9 @@ def test(cap1: Cap, cap2: Cap) = val zc: () ->{cap1} String = z val z2 = () => { x = identity } val z2c: () -> Unit = z2 // error + var a: String => String = f - var a: String => String = f // error - var b: List[String => String] = Nil // error + var b: List[String => String] = Nil val u = a // was error, now ok a("") // was error, now ok b.head // was error, now ok @@ -19,17 +19,20 @@ def test(cap1: Cap, cap2: Cap) = def scope = val cap3: Cap = CC() def g(x: String): String = if cap3 == cap3 then "" else "a" - a = g - b = List(g) + def h(): String = "" + a = x => g(x) // error + a = g // error + + b = List(g) // error val gc = g g - val s = scope - val sc: String => String = scope + val s = scope // error (but should be OK, we need to allow poly-captures) + val sc: String => String = scope // error (but should also be OK) - def local[sealed T](op: Cap -> T): T = op(CC()) + def local[T](op: (local: caps.Cap) -> CC^{local} -> T): T = op(caps.cap)(CC()) - local { cap3 => // error + local { root => cap3 => // error def g(x: String): String = if cap3 == cap3 then "" else "a" g } @@ -39,4 +42,4 @@ def test(cap1: Cap, cap2: Cap) = val r = Ref() r.elem = f - val fc = r.elem + val fc = r.elem \ No newline at end of file diff --git a/tests/neg/capt-wf.scala b/tests/neg/capt-wf.scala index b1b375d12f55..0e1e4be2ca67 100644 --- a/tests/neg/capt-wf.scala +++ b/tests/neg/capt-wf.scala @@ -13,7 +13,7 @@ def test(c: Cap, other: String): Unit = val x3a: () -> String = s1 val s2 = () => if x1 == null then "" else "abc" val x4: C^{s2} = ??? // OK - val x5: C^{c, c} = ??? // error: redundant + val x5: C^{c, c} = ??? // error: redundant // error: redundant // val x6: C^{c}^{c} = ??? // would be syntax error val x7: Cap^{c} = ??? // error: redundant // val x8: C^{c}^{cap} = ??? // would be syntax error diff --git a/tests/new/test.scala b/tests/new/test.scala new file mode 100644 index 000000000000..e6bfc29fd808 --- /dev/null +++ b/tests/new/test.scala @@ -0,0 +1,2 @@ +object Test: + def f: Any = 1 diff --git a/tests/pos-custom-args/captures/bynamefun.scala b/tests/pos-custom-args/captures/bynamefun.scala index 86bad201ffc3..414f0c46c42f 100644 --- a/tests/pos-custom-args/captures/bynamefun.scala +++ b/tests/pos-custom-args/captures/bynamefun.scala @@ -1,11 +1,14 @@ object test: class Plan(elem: Plan) object SomePlan extends Plan(???) + type PP = (-> Plan) -> Plan def f1(expr: (-> Plan) -> Plan): Plan = expr(SomePlan) f1 { onf => Plan(onf) } def f2(expr: (=> Plan) -> Plan): Plan = ??? f2 { onf => Plan(onf) } def f3(expr: (-> Plan) => Plan): Plan = ??? - f1 { onf => Plan(onf) } + f3 { onf => Plan(onf) } def f4(expr: (=> Plan) => Plan): Plan = ??? - f2 { onf => Plan(onf) } + f4 { onf => Plan(onf) } + def f5(expr: PP): Plan = expr(SomePlan) + f5 { onf => Plan(onf) } \ No newline at end of file diff --git a/tests/pos-custom-args/captures/cc-this.scala b/tests/pos-custom-args/captures/cc-this.scala index 2124ee494041..12c62e99d186 100644 --- a/tests/pos-custom-args/captures/cc-this.scala +++ b/tests/pos-custom-args/captures/cc-this.scala @@ -14,4 +14,5 @@ def test(using Cap) = def c1 = new C(f) def c2 = c1 def c3 = c2.y + val c4: C^ = c3 val _ = c3: C^ diff --git a/tests/pos-custom-args/captures/colltest.scala b/tests/pos-custom-args/captures/colltest.scala new file mode 100644 index 000000000000..af4ae6f03c56 --- /dev/null +++ b/tests/pos-custom-args/captures/colltest.scala @@ -0,0 +1,35 @@ +// Showing a problem with recursive references +object CollectionStrawMan5 { + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableLike[A] { + this: Iterable[A]^ => + def iterator: Iterator[A]^{this} + def coll: Iterable[A]^{this} = this + } + + trait IterableLike[+A]: + this: IterableLike[A]^ => + def coll: Iterable[A]^{this} + def partition(p: A => Boolean): Unit = + val pn = Partition(coll, p) + () + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A] { + this: View[A]^ => + } + + case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { + self: Partition[A]^{underlying, p} => + + class Partitioned(expected: Boolean) extends View[A]: + this: Partitioned^{self} => + def iterator: Iterator[A]^{this} = + underlying.iterator.filter((x: A) => p(x) == expected) + + val left: Partitioned^{self} = Partitioned(true) + val right: Partitioned^{self} = Partitioned(false) + } + +} \ No newline at end of file diff --git a/tests/pos-custom-args/captures/ctest.scala b/tests/pos-custom-args/captures/ctest.scala new file mode 100644 index 000000000000..62aa77fec0a5 --- /dev/null +++ b/tests/pos-custom-args/captures/ctest.scala @@ -0,0 +1,7 @@ +class C +type Cap = C^ + +class S + +def f(y: Cap) = + val a: ((x: Cap) -> S^) = (x: Cap) => S() \ No newline at end of file diff --git a/tests/pos-custom-args/captures/eta-expansions.scala b/tests/pos-custom-args/captures/eta-expansions.scala new file mode 100644 index 000000000000..1aac7ded1b50 --- /dev/null +++ b/tests/pos-custom-args/captures/eta-expansions.scala @@ -0,0 +1,9 @@ +@annotation.capability class Cap + +def test(d: Cap) = + def map2(xs: List[Int])(f: Int => Int): List[Int] = xs.map(f) + val f1 = map2 // capture polymorphic implicit eta expansion + def f2c: List[Int] => (Int => Int) => List[Int] = f1 + val a0 = identity[Cap ->{d} Unit] // capture monomorphic implicit eta expansion + val a0c: (Cap ->{d} Unit) ->{d} Cap ->{d} Unit = a0 + val b0 = (x: Cap ->{d} Unit) => identity[Cap ->{d} Unit](x) // not an implicit eta expansion, hence capture polymorphic diff --git a/tests/pos-custom-args/captures/i15749a.scala b/tests/pos-custom-args/captures/i15749a.scala index fe5f4d75dae1..0ac65f7f4150 100644 --- a/tests/pos-custom-args/captures/i15749a.scala +++ b/tests/pos-custom-args/captures/i15749a.scala @@ -1,3 +1,4 @@ +// TODO: Adapt to levels class Unit object u extends Unit @@ -10,12 +11,12 @@ def test = def wrapper[T](x: T): Wrapper[T] = [X] => (op: T ->{cap} X) => op(x) - def strictMap[A <: Top, sealed B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] = + def strictMap[A <: Top, B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] = mx((x: A) => wrapper(f(x))) def force[A](thunk: Unit ->{cap} A): A = thunk(u) - def forceWrapper[sealed A](mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = + def forceWrapper[A](mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not strictMap[Unit ->{cap} A, A](mx)(t => force[A](t)) // error diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index bc20d20ffd92..78991e4377c0 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -19,8 +19,9 @@ object Generic: object Monomorphic: class Pair(x: Cap => Unit, y: Cap => Unit): - def fst: Cap ->{x} Unit = x - def snd: Cap ->{y} Unit = y + type PCap = Cap + def fst: PCap ->{x} Unit = x + def snd: PCap ->{y} Unit = y def test(c: Cap, d: Cap) = def f(x: Cap): Unit = if c == x then () @@ -30,3 +31,23 @@ object Monomorphic: val x1c: Cap ->{c} Unit = x1 val y1 = p.snd val y1c: Cap ->{d} Unit = y1 + +object Monomorphic2: + + class Pair(x: Cap => Unit, y: Cap => Unit): + def fst: Cap^{cap[Pair]} ->{x} Unit = x + def snd: Cap^{cap[Pair]} ->{y} Unit = y + + class Pair2(x: Cap => Unit, y: Cap => Unit): + def fst: Cap^{cap[Pair2]} => Unit = x + def snd: Cap^{cap[Pair2]} => Unit = y + + def test(c: Cap, d: Cap) = + def f(x: Cap): Unit = if c == x then () + def g(x: Cap): Unit = if d == x then () + val p = Pair(f, g) + val x1 = p.fst + val x1c: Cap ->{c} Unit = x1 + val y1 = p.snd + val y1c: Cap ->{d} Unit = y1 + diff --git a/tests/pos-custom-args/captures/selftypes.scala b/tests/pos-custom-args/captures/selftypes.scala index c1b8eefce506..fff7445c419a 100644 --- a/tests/pos-custom-args/captures/selftypes.scala +++ b/tests/pos-custom-args/captures/selftypes.scala @@ -13,3 +13,12 @@ class D(@constructorOnly op: Int => Int) extends C: val x = 1//op(1) +// Demonstrates root mapping for self types +class IM: + this: IM^ => + + def coll: IM^{this} = ??? + foo(coll) + +def foo(im: IM^): Unit = ??? + diff --git a/tests/pos-custom-args/captures/test.scala b/tests/pos-custom-args/captures/test.scala new file mode 100644 index 000000000000..cf532bbdf34a --- /dev/null +++ b/tests/pos-custom-args/captures/test.scala @@ -0,0 +1,9 @@ +class C +type Cap = C^ + +class Foo(x: Cap): + this: Foo^{x} => + +def test(c: Cap) = + val x = Foo(c) + () diff --git a/tests/pos-custom-args/captures/try3.scala b/tests/pos-custom-args/captures/try3.scala index b8937bec00f3..b44ea57ccae4 100644 --- a/tests/pos-custom-args/captures/try3.scala +++ b/tests/pos-custom-args/captures/try3.scala @@ -13,7 +13,7 @@ def raise[E <: Exception](ex: E)(using CanThrow[E]): Nothing = throw ex def test1: Int = - def f(a: Boolean): Boolean -> CanThrow[IOException] ?-> Int = + def f(a: Boolean) = handle { if !a then raise(IOException()) (b: Boolean) => (_: CanThrow[IOException]) ?=> @@ -22,6 +22,7 @@ def test1: Int = } { ex => (b: Boolean) => (_: CanThrow[IOException]) ?=> -1 } + def fc(a: Boolean): Boolean -> CanThrow[IOException] ?-> Int = f(a) handle { val g = f(true) g(false) // can raise an exception diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala index 56548e5a9c30..8e3253bcd22d 100644 --- a/tests/pos-custom-args/captures/vars1.scala +++ b/tests/pos-custom-args/captures/vars1.scala @@ -1,30 +1,23 @@ import caps.unsafe.* -import annotation.unchecked.uncheckedCaptures object Test: type ErrorHandler = (Int, String) => Unit - @uncheckedCaptures var defaultIncompleteHandler: ErrorHandler = ??? - @uncheckedCaptures var incompleteHandler: ErrorHandler = defaultIncompleteHandler - val x = incompleteHandler.unsafeUnbox + private val x = incompleteHandler.unsafeUnbox val _ : ErrorHandler = x val _ = x(1, "a") def defaultIncompleteHandler1(): ErrorHandler = ??? val defaultIncompleteHandler2: ErrorHandler = ??? - @uncheckedCaptures var incompleteHandler1: ErrorHandler = defaultIncompleteHandler1() - @uncheckedCaptures var incompleteHandler2: ErrorHandler = defaultIncompleteHandler2 - @uncheckedCaptures private var incompleteHandler7 = defaultIncompleteHandler1() - @uncheckedCaptures private var incompleteHandler8 = defaultIncompleteHandler2 incompleteHandler1 = defaultIncompleteHandler2 incompleteHandler1 = defaultIncompleteHandler2 - val saved = incompleteHandler1 + private val saved = incompleteHandler1 diff --git a/tests/pos-special/stdlib/Test1.scala b/tests/pos-special/stdlib/Test1.scala index e5ae39027f94..786e3aaa2bf1 100644 --- a/tests/pos-special/stdlib/Test1.scala +++ b/tests/pos-special/stdlib/Test1.scala @@ -6,7 +6,7 @@ import java.io.* object Test0: - def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + def usingLogFile[T](op: (lcap: caps.Cap) ?-> FileOutputStream^ => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() diff --git a/tests/pos-special/stdlib/collection/Iterator.scala b/tests/pos-special/stdlib/collection/Iterator.scala index 4903a1dae0a6..6e8d15ea99a4 100644 --- a/tests/pos-special/stdlib/collection/Iterator.scala +++ b/tests/pos-special/stdlib/collection/Iterator.scala @@ -1202,7 +1202,7 @@ object Iterator extends IterableFactory[Iterator] { } else Iterator.empty.next() override def concat[B >: A](that: => IterableOnce[B]^): Iterator[B]^{this, that} = { - val c = new ConcatIteratorCell[B](that, null).asInstanceOf[ConcatIteratorCell[A]] + val c: ConcatIteratorCell[A] = new ConcatIteratorCell[B](that, null).asInstanceOf if (tail == null) { tail = c last = c diff --git a/tests/pos-with-compiler-cc/dotc/Run.scala b/tests/pos-with-compiler-cc/dotc/Run.scala index 96f8c6a7b06f..16a955afca1a 100644 --- a/tests/pos-with-compiler-cc/dotc/Run.scala +++ b/tests/pos-with-compiler-cc/dotc/Run.scala @@ -32,7 +32,7 @@ import scala.collection.mutable import scala.util.control.NonFatal import scala.io.Codec import annotation.constructorOnly -import annotation.unchecked.uncheckedCaptures +import caps.unsafe.unsafeUnbox /** A compiler run. Exports various methods to compile source files */ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunInfo with ConstraintRunInfo { @@ -165,7 +165,6 @@ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunIn val staticRefs = util.EqHashMap[Name, Denotation](initialCapacity = 1024) /** Actions that need to be performed at the end of the current compilation run */ - @uncheckedCaptures private var finalizeActions = mutable.ListBuffer[() => Unit]() /** Will be set to true if any of the compiled compilation units contains @@ -276,7 +275,7 @@ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunIn Rewrites.writeBack() suppressions.runFinished(hasErrors = ctx.reporter.hasErrors) while (finalizeActions.nonEmpty) { - val action = finalizeActions.remove(0) + val action = finalizeActions.remove(0).unsafeUnbox action() } compiling = false diff --git a/tests/pos-with-compiler-cc/dotc/core/Scopes.scala b/tests/pos-with-compiler-cc/dotc/core/Scopes.scala index f5a108a13c19..0e8edd55ed08 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Scopes.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Scopes.scala @@ -1,3 +1,8 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2012 LAMP/EPFL + * @author Martin Odersky + */ + package dotty.tools package dotc package core @@ -12,7 +17,6 @@ import Denotations._ import printing.Texts._ import printing.Printer import SymDenotations.NoDenotation -import annotation.unchecked.uncheckedCaptures import collection.mutable @@ -216,7 +220,6 @@ object Scopes { private var elemsCache: List[Symbol] | Null = null /** The synthesizer to be used, or `null` if no synthesis is done on this scope */ - @uncheckedCaptures private var synthesize: SymbolSynthesizer | Null = null /** Use specified synthesize for this scope */ diff --git a/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala b/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala index 0e1fc277865a..67b9f063e9d0 100644 --- a/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala +++ b/tests/pos-with-compiler-cc/dotc/core/TypeComparer.scala @@ -25,7 +25,7 @@ import reporting.trace import annotation.constructorOnly import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure} import language.experimental.pureFunctions -import annotation.unchecked.uncheckedCaptures +import caps.unsafe.* /** Provides methods to compare types. */ @@ -33,18 +33,17 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling import TypeComparer._ Stats.record("TypeComparer") - @uncheckedCaptures - private var myContext: Context = initctx - def comparerContext: Context = myContext + private var myContext: Context = initctx.unsafeBox + def comparerContext: Context = myContext.unsafeUnbox - protected given [DummySoItsADef]: Context = myContext + protected given [DummySoItsADef]: Context = myContext.unsafeUnbox protected var state: TyperState = compiletime.uninitialized def constraint: Constraint = state.constraint def constraint_=(c: Constraint): Unit = state.constraint = c def init(c: Context): Unit = - myContext = c + myContext = c.unsafeBox state = c.typerState monitored = false GADTused = false diff --git a/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala b/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala index fcc449af3632..b87cde4a6ad1 100644 --- a/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala +++ b/tests/pos-with-compiler-cc/dotc/core/tasty/TreeUnpickler.scala @@ -47,7 +47,7 @@ import dotty.tools.tasty.TastyFormat._ import scala.annotation.constructorOnly import scala.annotation.internal.sharable import language.experimental.pureFunctions -import annotation.unchecked.uncheckedCaptures +import caps.unsafe.{unsafeUnbox, unsafeBox} /** Unpickler for typed trees * @param reader the reader from which to unpickle @@ -1086,15 +1086,15 @@ class TreeUnpickler(reader: TastyReader, def readIndexedStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = val buf = new mutable.ListBuffer[Tree] - @uncheckedCaptures var curCtx = ctx + var curCtx = ctx.unsafeBox while currentAddr.index < end.index do - val stat = readIndexedStat(exprOwner)(using curCtx) + val stat = readIndexedStat(exprOwner)(using curCtx.unsafeUnbox) buf += stat stat match - case stat: Import => curCtx = curCtx.importContext(stat, stat.symbol) + case stat: Import => curCtx = curCtx.unsafeUnbox.importContext(stat, stat.symbol).unsafeBox case _ => assert(currentAddr.index == end.index) - k(buf.toList, curCtx) + k(buf.toList, curCtx.unsafeUnbox) def readStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = { fork.indexStats(end) diff --git a/tests/pos-with-compiler-cc/dotc/typer/Namer.scala b/tests/pos-with-compiler-cc/dotc/typer/Namer.scala index 8487192b9d8a..548f645a23d9 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Namer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Namer.scala @@ -29,7 +29,7 @@ import TypeErasure.erasure import reporting._ import config.Feature.sourceVersion import config.SourceVersion._ -import annotation.unchecked.uncheckedCaptures + /** This class creates symbols from definitions and imports and gives them * lazy types. @@ -930,8 +930,6 @@ class Namer { typer: Typer => class TypeDefCompleter(original: TypeDef)(ictx: DetachedContext) extends Completer(original)(ictx) with TypeParamsCompleter { private var myTypeParams: List[TypeSymbol] | Null = null - - @uncheckedCaptures private var nestedCtx: Context | Null = null assert(!original.isClassDef) diff --git a/tests/pos-with-compiler-cc/dotc/typer/Typer.scala b/tests/pos-with-compiler-cc/dotc/typer/Typer.scala index 0baae1730f4a..aa286446a334 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Typer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Typer.scala @@ -54,8 +54,7 @@ import config.Config import language.experimental.pureFunctions import scala.annotation.constructorOnly -import annotation.unchecked.uncheckedCaptures - +import caps.unsafe.{unsafeBox, unsafeUnbox} object Typer { @@ -1674,11 +1673,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * and the patterns of the Match tree and the MatchType correspond. */ def typedDependentMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: MatchType)(using Context): Tree = { - @uncheckedCaptures var caseCtx = ctx + var caseCtx = ctx.unsafeBox val cases1 = tree.cases.zip(pt.cases) .map { case (cas, tpe) => - val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx) - caseCtx = Nullables.afterPatternContext(sel, case1.pat) + val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx.unsafeUnbox) + caseCtx = Nullables.afterPatternContext(sel, case1.pat).unsafeBox case1 } .asInstanceOf[List[CaseDef]] @@ -1693,10 +1692,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType: Type, pt: Type)(using Context): List[CaseDef] = - @uncheckedCaptures var caseCtx = ctx + var caseCtx = ctx.unsafeBox cases.mapconserve { cas => - val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx) - caseCtx = Nullables.afterPatternContext(sel, case1.pat) + val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx.unsafeUnbox) + caseCtx = Nullables.afterPatternContext(sel, case1.pat).unsafeBox case1 } diff --git a/tests/pos/dotty-experimental.scala b/tests/pos/dotty-experimental.scala index ada386143a0a..9cffddc0b8ba 100644 --- a/tests/pos/dotty-experimental.scala +++ b/tests/pos/dotty-experimental.scala @@ -3,6 +3,6 @@ import language.experimental.captureChecking object test { - val x = caps.cap + val x: caps.Cap = caps.cap }