diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index d34fa247b155..c195c85c6b57 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -4,7 +4,6 @@ package dotc import core._ import Contexts._ import typer.{TyperPhase, RefChecks} -import cc.CheckCaptures import parsing.Parser import Phases.Phase import transform._ @@ -84,8 +83,8 @@ class Compiler { new PatternMatcher) :: // Compile pattern matches List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test - List(new CheckCaptures.Pre) :: // Preparations for check captures phase, enabled under captureChecking - List(new CheckCaptures) :: // Check captures, enabled under captureChecking + List(new cc.Setup) :: // Preparations for check captures phase, enabled under captureChecking + List(new cc.CheckCaptures) :: // Check captures, enabled under captureChecking List(new ElimOpaque, // Turn opaque into normal aliases new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only) new ExplicitOuter, // Add accessors to outer classes from nested ones. diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 6024eab29722..0baa694d6a9f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -437,7 +437,7 @@ object desugar { private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = { var mods = tparam.rawMods if (!keepAnnotations) mods = mods.withAnnotations(Nil) - tparam.withMods(mods & EmptyFlags | Param) + tparam.withMods(mods & (EmptyFlags | Sealed) | Param) } private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean): ValDef = { var mods = vparam.rawMods diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 90c8211b3b60..ac7fec4b02df 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -810,7 +810,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } - /** An extractor for def of a closure contained the block of the closure, + /** An extractor for the method of a closure contained the block of the closure, * possibly with type ascriptions. */ object possiblyTypedClosureDef: diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 973af0e8781d..eefb45c7d602 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1284,6 +1284,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { !(sym.is(Method) && sym.info.isInstanceOf[MethodOrPoly]) // if is a method it is parameterless } + /** A tree traverser that generates the the same import contexts as original typer for statements. + * TODO: Should we align TreeMapWithPreciseStatContexts and also keep track of exprOwners? + */ + abstract class TreeTraverserWithPreciseImportContexts extends TreeTraverser: + override def apply(x: Unit, trees: List[Tree])(using Context): Unit = + def recur(trees: List[Tree]): Unit = trees match + case (imp: Import) :: rest => + traverse(rest)(using ctx.importContext(imp, imp.symbol)) + case tree :: rest => + traverse(tree) + traverse(rest) + case Nil => + recur(trees) + end TreeTraverserWithPreciseImportContexts + extension (xs: List[tpd.Tree]) def tpes: List[Type] = xs match { case x :: xs1 => x.tpe :: xs1.tpes diff --git a/compiler/src/dotty/tools/dotc/cc/BoxedTypeCache.scala b/compiler/src/dotty/tools/dotc/cc/BoxedTypeCache.scala deleted file mode 100644 index 56b3f5ba5047..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/BoxedTypeCache.scala +++ /dev/null @@ -1,19 +0,0 @@ -package dotty.tools -package dotc -package cc - -import core.* -import Types.*, Symbols.*, Contexts.* - -/** A one-element cache for the boxed version of an unboxed capturing type */ -class BoxedTypeCache: - private var boxed: Type = compiletime.uninitialized - private var unboxed: Type = NoType - - def apply(tp: AnnotatedType)(using Context): Type = - if tp ne unboxed then - unboxed = tp - val CapturingType(parent, refs) = tp: @unchecked - boxed = CapturingType(parent, refs, boxed = true) - boxed -end BoxedTypeCache \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala index fd89159e2076..206734bccc18 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala @@ -24,8 +24,17 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte import CaptureAnnotation.* import tpd.* - /** A cache for boxed version of a capturing type with this annotation */ - val boxedType = BoxedTypeCache() + /** A cache for the version of this annotation which differs in its boxed status. */ + var boxDual: CaptureAnnotation | Null = null + + /** A boxed annotation which is either the same annotation or its boxDual */ + def boxedAnnot(using Context): CaptureAnnotation = + if boxed then this + else if boxDual != null then boxDual.nn + else + val dual = CaptureAnnotation(refs, boxed = true)(cls) + dual.boxDual = this + dual /** Reconstitute annotation tree from capture set */ override def tree(using Context) = diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index dfaa3c701576..68dd0ac0c79a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -4,6 +4,7 @@ package cc import core.* import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* +import Names.TermName import ast.{tpd, untpd} import Decorators.*, NameOps.* import config.SourceVersion @@ -15,39 +16,48 @@ import config.Feature import collection.mutable private val Captures: Key[CaptureSet] = Key() -private val BoxedType: Key[BoxedTypeCache] = Key() -/** Switch whether unpickled function types and byname types should be mapped to - * impure types. With the new gradual typing using Fluid capture sets, this should - * be no longer needed. Also, it has bad interactions with pickling tests. - */ -private val adaptUnpickledFunctionTypes = false +object ccConfig: -/** 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 + /** Switch whether unpickled function types and byname types should be mapped to + * impure types. With the new gradual typing using Fluid capture sets, this should + * be no longer needed. Also, it has bad interactions with pickling tests. + */ + private[cc] val adaptUnpickledFunctionTypes = false -/** 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 - case _ => Nil + /** If true, use `sealed` as encapsulation mechanism instead of the + * previous global retriction that `cap` can't be boxed or unboxed. + */ + def allowUniversalInBoxed(using Context) = + Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) +end ccConfig -def allowUniversalInBoxed(using Context) = - Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) -/** An exception thrown if a @retains argument is not syntactically a CaptureRef */ -class IllegalCaptureRef(tpe: Type) extends Exception +/** Are we at checkCaptures phase? */ +def isCaptureChecking(using Context): Boolean = + ctx.phaseId == Phases.checkCapturesPhase.id -/** Capture checking state, which is stored in a context property */ -class CCState: +/** Are we at checkCaptures or Setup phase? */ +def isCaptureCheckingOrSetup(using Context): Boolean = + val ccId = Phases.checkCapturesPhase.id + val ctxId = ctx.phaseId + ctxId == ccId || ctxId == ccId - 1 - val rhsClosure: mutable.HashSet[Symbol] = new mutable.HashSet +/** A dependent function type with given arguments and result type + * TODO Move somewhere else where we treat all function type related ops together. + */ +def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames: List[TermName] = Nil)(using Context): Type = + val make = MethodType.companion(isContextual = isContextual) + val mt = + if paramNames.length == args.length then make(paramNames, args, resultType) + else make(args, resultType) + mt.toFunctionType(alwaysDependent = true) - val levelOwners: mutable.HashSet[Symbol] = new mutable.HashSet +/** An exception thrown if a @retains argument is not syntactically a CaptureRef */ +class IllegalCaptureRef(tpe: Type) extends Exception(tpe.toString) - /** Associates certain symbols (the nesting level owners) with their ccNestingLevel */ - val nestingLevels: mutable.HashMap[Symbol, Int] = new mutable.HashMap +/** Capture checking state, which is known to other capture checking components */ +class CCState: /** Associates nesting level owners with the local roots valid in their scopes. */ val localRoots: mutable.HashMap[Symbol, Symbol] = new mutable.HashMap @@ -55,54 +65,17 @@ class CCState: /** 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 + var levelError: Option[CaptureSet.CompareResult.LevelError] = 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 +def ccState(using Context) = + Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState -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 +class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( + i"No common capture root nested in ${rs.mkString(" and ")}" +) extension (tree: Tree) @@ -110,7 +83,7 @@ extension (tree: Tree) 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 + .orElse(defn.RootClass) // non-existing outer roots are reported in Setup's checkQualifiedRoots .localRoot.termRef case _ => tree.tpe match case ref: CaptureRef => ref @@ -123,16 +96,21 @@ extension (tree: Tree) tree.getAttachment(Captures) match case Some(refs) => refs case None => - val refs = CaptureSet(retainedElems(tree).map(_.toCaptureRef)*) + val refs = CaptureSet(tree.retainedElems.map(_.toCaptureRef)*) .showing(i"toCaptureSet $tree --> $result", capt) tree.putAttachment(Captures, refs) refs + /** The arguments of a @retains or @retainsByName annotation */ + def retainedElems(using Context): List[Tree] = tree match + case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems + case _ => Nil + /** Under pureFunctions, add a @retainsByName(*)` annotation to the argument of * a by name parameter type, turning the latter into an impure by name parameter type. */ def adaptByNameArgUnderPureFuns(using Context): Tree = - if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then + if ccConfig.adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then val rbn = defn.RetainsByNameAnnot Annotated(tree, New(rbn.typeRef).select(rbn.primaryConstructor).appliedTo( @@ -159,27 +137,24 @@ extension (tp: Type) def boxed(using Context): Type = tp.dealias match case tp @ CapturingType(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty => tp.annot match - case ann: CaptureAnnotation => - ann.boxedType(tp) - case ann => - ann.tree.getAttachment(BoxedType) match - case None => ann.tree.putAttachment(BoxedType, BoxedTypeCache()) - case _ => - ann.tree.attachment(BoxedType)(tp) + case ann: CaptureAnnotation => AnnotatedType(parent, ann.boxedAnnot) + case ann => tp case tp: RealTypeBounds => tp.derivedTypeBounds(tp.lo.boxed, tp.hi.boxed) case _ => tp - /** If `sym` is a type parameter, the boxed version of `tp`, otherwise `tp` */ - def boxedIfTypeParam(sym: Symbol)(using Context) = - if sym.is(TypeParam) then tp.boxed else tp - - /** The boxed version of `tp`, unless `tycon` is a function symbol */ - def boxedUnlessFun(tycon: Type)(using Context) = - if ctx.phase != Phases.checkCapturesPhase || defn.isFunctionSymbol(tycon.typeSymbol) - then tp - else tp.boxed + /** If this is a unboxed capturing type with nonempty capture set, its boxed version. + * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. + * The identity for all other types. + */ + def unboxed(using Context): Type = tp.dealias match + case tp @ CapturingType(parent, refs) if tp.isBoxed && !refs.isAlwaysEmpty => + CapturingType(parent, refs) + case tp: RealTypeBounds => + tp.derivedTypeBounds(tp.lo.unboxed, tp.hi.unboxed) + case _ => + tp /** The capture set consisting of all top-level captures of `tp` that appear under a box. * Unlike for `boxed` this also considers parents of capture types, unions and @@ -228,7 +203,7 @@ extension (tp: Type) */ def adaptFunctionTypeUnderPureFuns(using Context): Type = tp match case AppliedType(fn, args) - if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere && defn.isFunctionClass(fn.typeSymbol) => + if ccConfig.adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere && defn.isFunctionClass(fn.typeSymbol) => val fname = fn.typeSymbol.name defn.FunctionType( fname.functionArity, @@ -241,22 +216,12 @@ extension (tp: Type) * a by name parameter type, turning the latter into an impure by name parameter type. */ def adaptByNameArgUnderPureFuns(using Context): Type = - if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then + if ccConfig.adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then AnnotatedType(tp, CaptureAnnotation(CaptureSet.universal, boxed = false)(defn.RetainsByNameAnnot)) else tp - def isCapturingType(using Context): Boolean = - tp match - case CapturingType(_, _) => true - case _ => false - - def isEventuallyCapturingType(using Context): Boolean = - tp match - case EventuallyCapturingType(_, _) => true - case _ => false - /** Is type known to be always pure by its class structure, * so that adding a capture set to it would not make sense? */ @@ -276,15 +241,30 @@ extension (tp: Type) case _ => false + def isCapabilityClassRef(using Context) = tp.dealiasKeepAnnots match + case _: TypeRef | _: AppliedType => tp.typeSymbol.hasAnnotation(defn.CapabilityAnnot) + case _ => false + + /** Drop @retains annotations everywhere */ + def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling + val tm = new TypeMap: + def apply(t: Type) = t match + case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => + apply(parent) + case _ => + mapOver(t) + tm(tp) + extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = - cls.baseClasses.find(bc => + cls.baseClasses.find: bc => defn.pureBaseClasses.contains(bc) - || { - val selfType = bc.givenSelfType - selfType.exists && selfType.captureSet.isAlwaysEmpty - }) + || bc.is(CaptureChecked) + && bc.givenSelfType.dealiasKeepAnnots.match + case CapturingType(_, refs) => refs.isAlwaysEmpty + case RetainingType(_, refs) => refs.isEmpty + case selfType => selfType.exists && selfType.captureSet.isAlwaysEmpty extension (sym: Symbol) @@ -330,7 +310,23 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox - def isLevelOwner(using Context): Boolean = ccState.levelOwners.contains(sym) + /** Can this symbol possibly own a local root? + * TODO: Disallow anonymous functions? + */ + def isLevelOwner(using Context): Boolean = + sym.isClass + || sym.is(Method, butNot = Accessor) + + /** The level owner enclosing `sym` which has the given name, or NoSymbol + * if none exists. + */ + def levelOwnerNamed(name: String)(using Context): Symbol = + def recur(sym: Symbol): Symbol = + if sym.name.toString == name then + if sym.isLevelOwner then sym else NoSymbol + else if sym == defn.RootClass then NoSymbol + else recur(sym.owner) + recur(sym) /** The owner of the current level. Qualifying owners are * - methods other than constructors and anonymous functions @@ -340,93 +336,38 @@ extension (sym: Symbol) * - _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 + def recur(sym: Symbol): Symbol = + if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass + else if sym.isLevelOwner then sym + else recur(sym.owner) + recur(sym) /** The 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. + nme.LOCAL_CAPTURE_ROOT, Synthetic, defn.Caps_Cap.typeRef) + ccState.localRoots.getOrElseUpdate(owner, newRoot) + + /** The outermost symbol owned by both `sym` and `other`. if none exists + * since the owning scopes of `sym` and `other` are not nested, invoke + * `onConflict` to return a symbol. */ - def 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 maxNested(other: Symbol, onConflict: (Symbol, Symbol) => Context ?=> Symbol)(using Context): Symbol = + if !sym.exists || other.isContainedIn(sym) then other + else if !other.exists || sym.isContainedIn(other) then sym + else onConflict(sym, other) + /** The innermost symbol owning both `sym` and `other`. + */ 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 + if !other.exists || other.isContainedIn(sym) then sym + else if !sym.exists || sym.isContainedIn(other) then other + else sym.owner.minNested(other.owner) extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match case ann: CaptureAnnotation => ann.boxed case _ => false - -extension (ts: List[Type]) - /** Equivalent to ts.mapconserve(_.boxedUnlessFun(tycon)) but more efficient where - * it is the identity. - */ - def boxedUnlessFun(tycon: Type)(using Context) = - if ctx.phase != Phases.checkCapturesPhase || defn.isFunctionClass(tycon.typeSymbol) - then ts - else ts.mapconserve(_.boxed) - diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala deleted file mode 100644 index 4e1241518963..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ /dev/null @@ -1,112 +0,0 @@ -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 5fdbea2a1bc6..a08230c6a198 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -4,7 +4,7 @@ package cc import core.* import Types.*, Symbols.*, Flags.*, Contexts.*, Decorators.* -import config.Printers.capt +import config.Printers.{capt, captDebug} import Annotations.Annotation import annotation.threadUnsafe import annotation.constructorOnly @@ -12,7 +12,7 @@ import annotation.internal.sharable import reporting.trace import printing.{Showable, Printer} import printing.Texts.* -import util.{SimpleIdentitySet, Property, optional}, optional.{break, ?} +import util.{SimpleIdentitySet, Property} import typer.ErrorReporting.Addenda import util.common.alwaysTrue import scala.collection.mutable @@ -56,10 +56,10 @@ 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. + /** An optional level limit, or NoSymbol if none exists. All elements of the set + * must be in scopes visible from the level limit. */ - def owner: Symbol + def levelLimit: Symbol /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -78,21 +78,74 @@ sealed abstract class CaptureSet extends Showable: /** Does this capture set contain the root reference `cap` as element? */ final def isUniversal(using Context) = - elems.exists { - case ref: TermRef => ref.symbol == defn.captureRoot - case _ => false - } + elems.exists(_.isUniversalRootCapability) + + /** Does this capture set contain the root reference `cap` as element? */ + final def containsRoot(using Context) = + elems.exists(_.isRootCapability) - /** Add new elements to this capture set if allowed. - * @pre `newElems` is not empty and does not overlap with `this.elems`. - * Constant capture sets never allow to add new elements. - * Variables allow it if and only if the new elements can be included - * in all their dependent sets. - * @param origin The set where the elements come from, or `empty` if not known. - * @return CompareResult.OK if elements were added, or a conflicting - * capture set that prevents addition otherwise. + /** Does this capture set disallow an addiiton of `cap`, whereas it + * might allow an addition of a local root? + */ + final def disallowsUniversal(using Context) = + if isConst then !isUniversal && elems.exists(_.isLocalRootCapability) + else asVar.noUniversal + + /** Try to include an element in this capture set. + * @param elem The element to be added + * @param origin The set that originated the request, or `empty` if the request came from outside. + * + * If the set already accounts for the element, return OK. + * Otherwise, try to add a new element to the set. This is OK if + * - the set is a variable, and + * - the element is not at a deeper nesting level than the set, and + * - the element can also be added (in mapped/filtered form) to all + * dependent sets. + * If the `origin` is the same as the `source` of the set variable, the + * element might be filtered or mapped according to the class of the variable. + * Otherwise, the element might have to be back-propagated to the source + * of the variable. + * + * If the element itself cannot be added to the set for some reason, and the + * element is not the root capability, try instead to include its underlying + * capture set. */ - protected def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult + protected def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + if accountsFor(elem) then CompareResult.OK + else addNewElem(elem) + + /** Try to include all element in `refs` to this capture set. */ + protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + (CompareResult.OK /: newElems): (r, elem) => + r.andAlso(tryInclude(elem, origin)) + + /** Add an element to this capture set, assuming it is not already accounted for, + * and omitting any mapping or filtering. + * + * If the element itself cannot be added to the set for some reason, and the + * element is not the root capability, try instead to include its underlying + * capture set. + */ + protected final def addNewElem(elem: CaptureRef)(using Context, VarState): CompareResult = + if elem.isRootCapability || summon[VarState] == FrozenState then + addThisElem(elem) + else + addThisElem(elem).orElse: + val underlying = elem.captureSetOfInfo + tryInclude(underlying.elems, this).andAlso: + underlying.addDependent(this) + CompareResult.OK + + /** Add new elements one by one using `addNewElem`, abort on first failure */ + protected final def addNewElems(newElems: Refs)(using Context, VarState): CompareResult = + (CompareResult.OK /: newElems): (r, elem) => + r.andAlso(addNewElem(elem)) + + /** Add a specific element, assuming it is not already accounted for, + * and omitting any mapping or filtering, without possibility to backtrack + * to the underlying capture set. + */ + protected def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult /** If this is a variable, add `cs` as a dependent set */ protected def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult @@ -102,42 +155,38 @@ sealed abstract class CaptureSet extends Showable: cs.addDependent(this)(using ctx, UnrecordedState) this - /** Try to include all references of `elems` that are not yet accounted for by this - * capture set. Inclusion is via `addNewElems`. - * @param origin The set where the elements come from, or `empty` if not known. - * @return CompareResult.OK if all unaccounted elements could be added, - * capture set that prevents addition otherwise. - */ - protected final def tryInclude(elems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - val unaccounted = elems.filter(!accountsFor(_)) - if unaccounted.isEmpty then CompareResult.OK - else addNewElems(unaccounted, origin) - - /** Equivalent to `tryInclude({elem}, origin)`, but more efficient */ - protected final def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - if accountsFor(elem) then CompareResult.OK - else addNewElems(elem.singletonCaptureSet.elems, origin) - - /* 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) + + /* 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 is a super root of y + */ private def subsumes(y: CaptureRef) = (x eq y) + || x.isSuperRootOf(y) || y.match - case y: TermRef => (y.prefix eq x) || x.isRootIncluding(y) - case y: CaptureRoot.Var => x.isRootIncluding(y) + case y: TermRef => y.prefix eq x 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]) + /** x <:< cap, cap[x] <:< cap + * cap[y] <:< cap[x] if y encloses x + * y <:< cap[x] if y's level owner encloses x's local root owner + */ + private def isSuperRootOf(y: CaptureRef): Boolean = x match + case x: TermRef => + x.isUniversalRootCapability + || x.isLocalRootCapability && !y.isUniversalRootCapability && { + val xowner = x.localRootOwner + y match + case y: TermRef => + xowner.isContainedIn(y.symbol.levelOwner) + case y: ThisType => + xowner.isContainedIn(y.cls) + case _ => + false + } + case _ => false end extension /** {x} <:< this where <:< is subcapturing, but treating all variables @@ -187,20 +236,14 @@ sealed abstract class CaptureSet extends Showable: /** The subcapturing test, using a given VarState */ private def subCaptures(that: CaptureSet)(using Context, VarState): CompareResult = - def recur(elems: List[CaptureRef]): CompareResult = elems match - case elem :: elems1 => - var result = that.tryInclude(elem, this) - if !result.isOK && !elem.isRootCapability && summon[VarState] != FrozenState then - result = elem.captureSetOfInfo.subCaptures(that) - if result.isOK then - recur(elems1) - else - varState.rollBack() - result - case Nil => - addDependent(that) - recur(elems.toList) - .showing(i"subcaptures $this <:< $that = ${result.show}", capt) + val result = that.tryInclude(elems, this) + if result.isOK then + addDependent(that) + else + ccState.levelError = ccState.levelError.orElse(result.levelError) + varState.rollBack() + result + //.showing(i"subcaptures $this <:< $that = ${result.show}", capt) /** Two capture sets are considered =:= equal if they mutually subcapture each other * in a frozen state. @@ -216,7 +259,9 @@ 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.owner.maxNested(that.owner), this.elems ++ that.elems) + else Var( + this.levelLimit.maxNested(that.levelLimit, onConflict = (sym1, sym2) => sym1), + this.elems ++ that.elems) .addAsDependentTo(this).addAsDependentTo(that) /** The smallest superset (via <:<) of this capture set that also contains `ref`. @@ -305,8 +350,8 @@ sealed abstract class CaptureSet extends Showable: /** 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) + def ensureWellformed(handler: CaptureRef => Context ?=> Unit)(using Context): this.type = + elems.foreach(handler(_)) this /** An upper approximation of this capture set, i.e. a constant set that is @@ -370,20 +415,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 def isAlwaysEmpty = elems.isEmpty - def addNewElems(elems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - CompareResult.fail(this) + def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = + CompareResult.Fail(this :: Nil) def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -391,7 +429,7 @@ object CaptureSet: def withDescription(description: String): Const = Const(elems, description) - def owner = NoSymbol + def levelLimit = NoSymbol override def toString = elems.toString end Const @@ -404,7 +442,7 @@ object CaptureSet: */ object Fluid extends Const(emptySet): override def isAlwaysEmpty = false - override def addNewElems(elems: Refs, origin: CaptureSet)(using Context, VarState) = CompareResult.OK + override def addThisElem(elem: CaptureRef)(using Context, VarState) = CompareResult.OK override def accountsFor(x: CaptureRef)(using Context): Boolean = true override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true override def toString = "" @@ -418,16 +456,14 @@ object CaptureSet: varId += 1 varId - override val owner = directOwner.levelOwner + //assert(id != 40) + + override val levelLimit = + if directOwner.exists then directOwner.levelOwner else NoSymbol /** A variable is solved if it is aproximated to a from-then-on constant set. */ private var isSolved: Boolean = false - 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 @@ -442,13 +478,13 @@ object CaptureSet: /** A handler to be invoked if the root reference `cap` is added to this set */ var rootAddedHandler: () => Context ?=> Unit = () => () + private[CaptureSet] var noUniversal = false + /** A handler to be invoked when new elems are added to this set */ - var newElemAddedHandler: List[CaptureRef] => Context ?=> Unit = _ => () + var newElemAddedHandler: CaptureRef => Context ?=> Unit = _ => () 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. */ @@ -473,50 +509,39 @@ object CaptureSet: def resetDeps()(using state: VarState): Unit = deps = state.deps(this) - def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = 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 + CompareResult.Fail(this :: Nil) // fail if variable is solved or given VarState is frozen + else if !levelOK(elem) then + CompareResult.LevelError(this, elem) else - //assert(id != 2, newElems) - elems ++= newElems - if isUniversal then rootAddedHandler() - newElemAddedHandler(newElems.toList) + //if id == 34 then assert(!elem.isUniversalRootCapability) + elems += elem + if elem.isUniversalRootCapability then + rootAddedHandler() + newElemAddedHandler(elem) // assert(id != 5 || elems.size != 3, this) - (CompareResult.OK /: deps) { (r, dep) => - r.andAlso(dep.tryInclude(newElems, 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 + val res = (CompareResult.OK /: deps): (r, dep) => + r.andAlso(dep.tryInclude(elem, this)) + res.orElse: + elems -= elem + res.addToTrace(this) + + private def levelOK(elem: CaptureRef)(using Context): Boolean = + if elem.isUniversalRootCapability then !noUniversal + else elem match + case elem: TermRef => + if levelLimit.exists then + var sym = elem.symbol + if sym.isLevelOwner then sym = sym.owner + levelLimit.isContainedIn(sym.levelOwner) + else true + case elem: ThisType => + if levelLimit.exists then + levelLimit.isContainedIn(elem.cls.levelOwner) + else true + case elem: TermParamRef => + true def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = if (cs eq this) || cs.isUniversal || isConst then @@ -525,13 +550,14 @@ object CaptureSet: deps += cs CompareResult.OK else - CompareResult.fail(this) + CompareResult.Fail(this :: Nil) override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = + noUniversal = true rootAddedHandler = handler super.disallowRootCapability(handler) - override def ensureWellformed(handler: List[CaptureRef] => (Context) ?=> Unit)(using Context): this.type = + override def ensureWellformed(handler: CaptureRef => (Context) ?=> Unit)(using Context): this.type = newElemAddedHandler = handler super.ensureWellformed(handler) @@ -545,7 +571,8 @@ object CaptureSet: if isConst then this else if elems.exists(_.isRootCapability) then CaptureSet(elems.filter(_.isRootCapability).toList*) - else if computingApprox then universal + else if computingApprox then + universal else computingApprox = true try computeApprox(origin).ensuring(_.isConst) @@ -565,7 +592,7 @@ object CaptureSet: .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 + if tryInclude(newElems, empty)(using ctx, VarState()).isOK then markSolved() /** Mark set as solved and propagate this info to all dependent sets */ @@ -586,11 +613,11 @@ object CaptureSet: 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"" + val limitInfo = + if ctx.settings.YprintLevel.value && levelLimit.exists + then i"" else "" - debugInfo ++ nestingInfo + debugInfo ++ limitInfo /** Used for diagnostics and debugging: A string that traces the creation * history of a variable by following source links. Each variable on the @@ -602,19 +629,11 @@ object CaptureSet: val trail = this.match case dv: DerivedVar => dv.source.ids case _ => "" - s"$id${getClass.getSimpleName.nn.take(1)}$trail" - + val descr = getClass.getSimpleName.nn.take(1) + s"$id$descr$trail" 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(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context) extends Var(owner, initialElems): @@ -643,7 +662,7 @@ object CaptureSet: */ class Mapped private[CaptureSet] (val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context) - extends DerivedVar(source.owner, initial.elems): + extends DerivedVar(source.levelLimit, initial.elems): addAsDependentTo(initial) // initial mappings could change by propagation private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap] @@ -656,39 +675,43 @@ object CaptureSet: |Stack trace of variable creation:" |${stack.mkString("\n")}""" - override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - val added = - if origin eq source then // elements have to be mapped - mapRefs(newElems, tm, variance) + override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + def propagate: CompareResult = + if (origin ne source) && (origin ne initial) && mapIsIdempotent then + // `tm` is idempotent, propagate back elems from image set. + // This is sound, since we know that for `r in newElems: tm(r) = r`, hence + // `r` is _one_ possible solution in `source` that would make an `r` appear in this set. + // It's not necessarily the only possible solution, so the scheme is incomplete. + source.tryInclude(elem, this) + else if !mapIsIdempotent && variance <= 0 && !origin.isConst && (origin ne initial) && (origin ne source) then + // The map is neither a BiTypeMap nor an idempotent type map. + // In that case there's no much we can do. + // The scheme then does not propagate added elements back to source and rejects adding + // elements from variable sources in contra- and non-variant positions. In essence, + // we approximate types resulting from such maps by returning a possible super type + // from the actual type. But this is neither sound nor complete. + report.warning(em"trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated") + CompareResult.Fail(this :: Nil) else - // elements are added by subcapturing propagation with this Mapped set - // as superset; no mapping is necessary or allowed. - Const(newElems) - super.addNewElems(added.elems, origin) - .andAlso { - if added.isConst then CompareResult.OK - else if added.asVar.recordDepsState() then { addAsDependentTo(added); CompareResult.OK } - else CompareResult.fail(this) - } - .andAlso { - if (origin ne source) && (origin ne initial) && mapIsIdempotent then - // `tm` is idempotent, propagate back elems from image set. - // This is sound, since we know that for `r in newElems: tm(r) = r`, hence - // `r` is _one_ possible solution in `source` that would make an `r` appear in this set. - // It's not necessarily the only possible solution, so the scheme is incomplete. - source.tryInclude(newElems, this) - else if !mapIsIdempotent && variance <= 0 && !origin.isConst && (origin ne initial) && (origin ne source) then - // The map is neither a BiTypeMap nor an idempotent type map. - // In that case there's no much we can do. - // The scheme then does not propagate added elements back to source and rejects adding - // elements from variable sources in contra- and non-variant positions. In essence, - // we approximate types resulting from such maps by returning a possible super type - // from the actual type. But this is neither sound nor complete. - report.warning(em"trying to add elems ${CaptureSet(newElems)} from unrecognized source $origin of mapped set $this$whereCreated") - CompareResult.fail(this) - else - CompareResult.OK - } + CompareResult.OK + def propagateIf(cond: Boolean): CompareResult = + if cond then propagate else CompareResult.OK + + if origin eq source then // elements have to be mapped + val mapped = extrapolateCaptureRef(elem, tm, variance) + val added = mapped.elems.filter(!accountsFor(_)) + addNewElems(added) + .andAlso: + if mapped.isConst then CompareResult.OK + else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult.OK } + else CompareResult.Fail(this :: Nil) + .andAlso: + propagateIf(!added.isEmpty) + else if accountsFor(elem) then + CompareResult.OK + else + addNewElem(elem).andAlso(propagate) + end tryInclude override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = if source eq origin then @@ -709,17 +732,20 @@ object CaptureSet: */ final class BiMapped private[CaptureSet] (val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) - extends DerivedVar(source.owner, initialElems): + extends DerivedVar(source.levelLimit, initialElems): - override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if origin eq source then - super.addNewElems(newElems.map(bimap.forward), origin) + val mappedElem = bimap.forward(elem) + if accountsFor(mappedElem) then CompareResult.OK + else addNewElem(mappedElem) + else if accountsFor(elem) then + CompareResult.OK else - super.addNewElems(newElems, origin) - .andAlso { - source.tryInclude(newElems.map(bimap.backward), this) - .showing(i"propagating new elems ${CaptureSet(newElems)} backward from $this to $source", capt) - } + source.tryInclude(bimap.backward(elem), this) + .showing(i"propagating new elem $elem backward from $this to $source = $result", capt) + .andAlso: + addNewElem(elem) /** For a BiTypeMap, supertypes of the mapped type also constrain * the source via the inverse type mapping and vice versa. That is, if @@ -739,20 +765,19 @@ 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.owner, source.elems.filter(p)): + extends DerivedVar(source.levelLimit, source.elems.filter(p)): - override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - val filtered = newElems.filter(p) - if origin eq source then - super.addNewElems(filtered, origin) + override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + if accountsFor(elem) then + CompareResult.OK + else if origin eq source then + if p(elem) then addNewElem(elem) + else CompareResult.OK else // Filtered elements have to be back-propagated to source. // Elements that don't satisfy `p` are not allowed. - super.addNewElems(newElems, origin) - .andAlso { - if filtered.size == newElems.size then source.tryInclude(newElems, this) - else CompareResult.fail(this) - } + if p(elem) then source.tryInclude(elem, this) + else CompareResult.Fail(this :: Nil) override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = if source eq origin then @@ -770,20 +795,19 @@ object CaptureSet: extends Filtered(source, !other.accountsFor(_)) class Intersected(cs1: CaptureSet, cs2: CaptureSet)(using Context) - extends Var(cs1.owner.minNested(cs2.owner), elemIntersection(cs1, cs2)): + extends Var(cs1.levelLimit.minNested(cs2.levelLimit), elemIntersection(cs1, cs2)): addAsDependentTo(cs1) addAsDependentTo(cs2) deps += cs1 deps += cs2 - override def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - val added = - if origin eq cs1 then newElems.filter(cs2.accountsFor) - else if origin eq cs2 then newElems.filter(cs1.accountsFor) - else newElems - // If origin is not cs1 or cs2, then newElems will be propagated to - // cs1, cs2 since they are in deps. - super.addNewElems(added, origin) + override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + val present = + if origin eq cs1 then cs2.accountsFor(elem) + else if origin eq cs2 then cs1.accountsFor(elem) + else true + if present && !accountsFor(elem) then addNewElem(elem) + else CompareResult.OK override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = if (origin eq cs1) || (origin eq cs2) then @@ -848,25 +872,47 @@ object CaptureSet: /** A TypeMap that is the identity on capture references */ trait IdentityCaptRefMap extends TypeMap - type CompareResult = CompareResult.TYPE + enum CompareResult extends Showable: + case OK + case Fail(trace: List[CaptureSet]) + case LevelError(cs: CaptureSet, elem: CaptureRef) + + override def toText(printer: Printer): Text = + inContext(printer.printerContext): + this match + case OK => Str("OK") + case Fail(trace) => + if ctx.settings.YccDebug.value then printer.toText(trace, ", ") + else blocking.show + case LevelError(cs: CaptureSet, elem: CaptureRef) => + Str(i"($elem at wrong level for $cs in ${cs.levelLimit})") + + /** The result is OK */ + def isOK: Boolean = this == OK + + /** If not isOK, the blocking capture set */ + def blocking: CaptureSet = (this: @unchecked) match + case Fail(cs) => cs.last + case LevelError(cs, _) => cs + + /** Optionally, this result if it is a level error */ + def levelError: Option[LevelError] = this match + case result: LevelError => Some(result) + case _ => None + + inline def andAlso(op: Context ?=> CompareResult)(using Context): CompareResult = + if isOK then op else this + + inline def orElse(op: Context ?=> CompareResult)(using Context): CompareResult = + if isOK then this + else + val alt = op + if alt.isOK then alt + else this - /** The result of subcapturing comparisons is an opaque type CompareResult.TYPE. - * This is either OK, indicating success, or - * another capture set, indicating failure. The failure capture set - * is the one that did not allow propagaton of elements into it. - */ - object CompareResult: - opaque type TYPE = CaptureSet - val OK: TYPE = Const(emptySet) - def fail(cs: CaptureSet): TYPE = cs - - extension (result: TYPE) - /** The result is OK */ - def isOK: Boolean = result eq OK - /** If not isOK, the blocking capture set */ - def blocking: CaptureSet = result - inline def andAlso(op: Context ?=> TYPE)(using Context): TYPE = if result.isOK then op else result - def show(using Context): String = if result.isOK then "OK" else i"$result" + inline def addToTrace(cs: CaptureSet): CompareResult = this match + case Fail(trace) => Fail(cs :: trace) + case _ => this end CompareResult /** A VarState serves as a snapshot mechanism that can undo @@ -954,7 +1000,7 @@ object CaptureSet: css.foldLeft(empty)(_ ++ _) */ - /** The capture set of the type underlying a CaptureRef */ + /** The capture set of the type underlying CaptureRef */ def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match case ref: TermRef if ref.isRootCapability => ref.singletonCaptureSet case _ => ofType(ref.underlying, followResult = true) @@ -971,8 +1017,6 @@ object CaptureSet: 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 => @@ -998,7 +1042,7 @@ object CaptureSet: case _ => empty recur(tp) - .showing(i"capture set of $tp = $result", capt) + .showing(i"capture set of $tp = $result", captDebug) private val ShownVars: Property.Key[mutable.Set[Var]] = Property.Key() @@ -1035,16 +1079,20 @@ object CaptureSet: 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}""" + for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield + ccState.levelError = None + if ref.isUniversalRootCapability then + i""" + | + |Note that the universal capability `cap` + |cannot be included in capture set $cs""" + else + val levelStr = ref match + case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}" + case _ => "" + i""" + | + |Note that reference ${ref}$levelStr + |cannot be included in outer capture set $cs which is associated with ${cs.levelLimit}""" end CaptureSet diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index a7c283f4cc3b..2a5cb91b45d3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -4,6 +4,7 @@ package cc import core.* import Types.*, Symbols.*, Contexts.* +import Decorators.i /** A (possibly boxed) capturing type. This is internally represented as an annotated type with a @retains * or @retainsByName annotation, but the extractor will succeed only at phase CheckCaptures. @@ -25,58 +26,62 @@ import Types.*, Symbols.*, Contexts.* */ object CapturingType: - /** Smart constructor that drops empty capture sets and fuses compatible capturiong types. + /** Smart constructor that + * - drops empty capture sets + * - drops a capability class expansion if it is further refined with another capturing type + * - fuses compatible capturing types. * An outer type capturing type A can be fused with an inner capturing type B if their * boxing status is the same or if A is boxed. */ def apply(parent: Type, refs: CaptureSet, boxed: Boolean = false)(using Context): Type = if refs.isAlwaysEmpty then parent else parent match + case parent @ CapturingType(parent1, refs1) if refs1 eq defn.expandedUniversalSet => + apply(parent1, refs, boxed) case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed => apply(parent1, refs ++ refs1, boxed) case _ => AnnotatedType(parent, CaptureAnnotation(refs, boxed)(defn.RetainsAnnot)) - /** An extractor that succeeds only during CheckCapturingPhase. Boxing statis is - * returned separately by CaptureOps.isBoxed. + /** An extractor for CapturingTypes. Capturing types are recognized if + * - the annotation is a CaptureAnnotation and we are not past CheckCapturingPhase, or + * - the annotation is a @retains and we are in CheckCapturingPhase, + * but not if the IgnoreCaptures mode is set. + * Boxing status is returned separately by CaptureOps.isBoxed. */ def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] = - if ctx.phase == Phases.checkCapturesPhase - && tp.annot.symbol == defn.RetainsAnnot - && !ctx.mode.is(Mode.IgnoreCaptures) - then - EventuallyCapturingType.unapply(tp) - else None + if ctx.mode.is(Mode.IgnoreCaptures) then None + else decomposeCapturingType(tp) + + /** Decompose `tp` as a capturing type without taking IgnoreCaptures into account */ + def decomposeCapturingType(tp: Type)(using Context): Option[(Type, CaptureSet)] = tp match + case AnnotatedType(parent, ann: CaptureAnnotation) + if isCaptureCheckingOrSetup => + Some((parent, ann.refs)) + case AnnotatedType(parent, ann) + if ann.symbol == defn.RetainsAnnot && isCaptureChecking => + // There are some circumstances where we cannot map annotated types + // with retains annotations to capturing types, so this second recognizer + // path still has to exist. One example is when checking capture sets + // of dependent function type results for well-formedness. E.g. in + // `(x: C^{f}) -> () ->{x} Unit` we need to check that the capture set of + // `x` is not empty. We use the original, untransformed type for that + // since the transformed type already normalizes capture sets which would + // drop subsumed references. But the original type refers to the untransfomed + // type `C^{f}` which does not have a capture annotation yet. The transformed + // type would be in a copy of the dependent function type, but it is useless + // since we need to check the original reference. + try Some((parent, ann.tree.toCaptureSet)) + catch case ex: IllegalCaptureRef => None + case _ => + None /** Check whether a type is uncachable when computing `baseType`. - * - Avoid caching all the types during the setup phase, since at that point - * the capture set variables are not fully installed yet. - * - Avoid caching capturing types when IgnoreCaptures mode is set, since the - * capture sets may be thrown away in the computed base type. - */ + * We avoid caching capturing types when IgnoreCaptures mode is set. + */ def isUncachable(tp: Type)(using Context): Boolean = - ctx.phase == Phases.checkCapturesPhase && - (Setup.isDuringSetup || ctx.mode.is(Mode.IgnoreCaptures) && tp.isEventuallyCapturingType) + ctx.mode.is(Mode.IgnoreCaptures) && decomposeCapturingType(tp).isDefined end CapturingType -/** An extractor for types that will be capturing types at phase CheckCaptures. Also - * included are types that indicate captures on enclosing call-by-name parameters - * before phase ElimByName. - */ -object EventuallyCapturingType: - - def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] = - val sym = tp.annot.symbol - if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then - tp.annot match - case ann: CaptureAnnotation => - Some((tp.parent, ann.refs)) - case ann => - try Some((tp.parent, ann.tree.toCaptureSet)) - catch case ex: IllegalCaptureRef => None - else None - -end EventuallyCapturingType - diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b9904db4ea41..c52e1aa6714e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -6,19 +6,20 @@ import core.* import Phases.*, DenotTransformers.*, SymDenotations.* import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* import Types.*, StdNames.*, Denotations.* -import config.Printers.{capt, recheckr} +import config.Printers.{capt, recheckr, noPrinter} import config.{Config, Feature} import ast.{tpd, untpd, Trees} import Trees.* import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} -import typer.ErrorReporting.Addenda +import typer.ErrorReporting.{Addenda, err} +import typer.ProtoTypes.{AnySelectionProto, LhsProto} import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property} import transform.SymUtils.* import transform.{Recheck, PreRecheck} import Recheck.* import scala.collection.mutable -import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, LooseRootChecking} +import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult} import StdNames.nme import NameKinds.DefaultGetterName import reporting.trace @@ -27,26 +28,6 @@ import reporting.trace object CheckCaptures: import ast.tpd.* - class Pre extends PreRecheck, SymTransformer: - - override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere - - /** - Reset `private` flags of parameter accessors so that we can refine them - * in Setup if they have non-empty capture sets. - * - Special handling of some symbols defined for case classes. - * Enabled only until recheck is finished, and provided some compilation unit - * is CC-enabled. - */ - def transformSym(sym: SymDenotation)(using Context): SymDenotation = - if !pastRecheck && Feature.ccEnabledSomewhere then - if sym.isAllOf(PrivateParamAccessor) && !sym.hasAnnotation(defn.ConstructorOnlyAnnot) then - sym.copySymDenotation(initFlags = sym.flags &~ Private | Recheck.ResetPrivate) - else if Synthetics.needsTransform(sym) then - Synthetics.transform(sym) - else sym - else sym - end Pre - enum EnvKind: case Regular // normal case case NestedInOwner // environment is a temporary one nested in the owner's environment, @@ -73,6 +54,14 @@ object CheckCaptures: /** If an environment is open it tracks free references */ def isOpen = !captured.isAlwaysEmpty && kind != EnvKind.Boxed + + def outersIterator: Iterator[Env] = new: + private var cur = Env.this + def hasNext = !cur.isOutermost + def next(): Env = + val res = cur + cur = cur.outer + res end Env /** Similar normal substParams, but this is an approximating type map that @@ -140,7 +129,7 @@ object CheckCaptures: case _: SingletonType => report.error(em"Singleton type $parent cannot have capture set", parent.srcPos) case _ => - for elem <- retainedElems(ann) do + for elem <- ann.retainedElems do elem match case QualifiedRoot(outer) => // Will be checked by Setup's checkOuterRoots @@ -151,37 +140,54 @@ object CheckCaptures: 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. - * This check is performed after capture sets are computed in phase cc. - */ - def checkWellformedPost(tp: Type, pos: SrcPos)(using Context): Unit = tp match - case CapturingType(parent, refs) => - for ref <- refs.elems do - 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 in $tp", pos) - case _ => - - /** Warn if `ann`, which is the tree of a @retains annotation, defines some elements that - * are already accounted for by other elements of the same annotation. - * 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. + /** Report an error if some part of `tp` contains the root capability in its capture set + * or if it refers to an unsealed type parameter that could possibly be instantiated with + * cap in a way that's visible at the type. */ - def warnIfRedundantCaptureSet(ann: Tree, tpt: Tree)(using Context): Unit = - var retained = retainedElems(ann).toArray - for i <- 0 until retained.length do - 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 - 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) + private def disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = + val check = new TypeTraverser: + + extension (tparam: Symbol) def isParametricIn(carrier: Symbol): Boolean = + val encl = carrier.owner.enclosingMethodOrClass + if encl.isClass then tparam.isParametricIn(encl) + else + def recur(encl: Symbol): Boolean = + if tparam.owner == encl then true + else if encl.isStatic || !encl.exists then false + else recur(encl.owner.enclosingMethodOrClass) + recur(encl) + + def traverse(t: Type) = + t.dealiasKeepAnnots match + case t: TypeRef => + capt.println(i"disallow $t, $tp, $what, ${t.symbol.is(Sealed)}") + t.info match + case TypeBounds(_, hi) + if !t.symbol.is(Sealed) && !t.symbol.isParametricIn(carrier) => + if hi.isAny then + report.error( + em"""$what cannot $have $tp since + |that type refers to the type variable $t, which is not sealed. + |$addendum""", + pos) + else + traverse(hi) + case _ => + traverseChildren(t) + case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot => + () + case t => + 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) + end disallowRootCapabilitiesIn /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -198,16 +204,13 @@ 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 - - override def printingContext(ctx: Context) = ctx.withProperty(ccStateKey, Some(new CCState)) + + val ccState = new CCState class CaptureChecker(ictx: Context) extends Rechecker(ictx): - import ast.tpd.* override def keepType(tree: Tree) = super.keepType(tree) @@ -218,30 +221,16 @@ class CheckCaptures extends Recheck, SymTransformer: */ private def interpolator(startingVariance: Int = 1)(using Context) = new TypeTraverser: variance = startingVariance - override def traverse(t: Type) = - t match - case CapturingType(parent, refs: CaptureSet.Var) => - 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) - case tp: TypeVar => - case tp: TypeRef => - traverse(tp.prefix) - case _ => - traverseChildren(t) + override def traverse(t: Type) = t match + case t @ CapturingType(parent, refs) => + refs match + case refs: CaptureSet.Var if variance < 0 => refs.solve() + case _ => + traverse(parent) + case t @ defn.RefinedFunctionOf(rinfo) => + traverse(rinfo) + case _ => + traverseChildren(t) /** If `tpt` is an inferred type, interpolate capture set variables appearing contra- * variantly in it. @@ -279,21 +268,28 @@ class CheckCaptures extends Recheck, SymTransformer: pos, provenance) /** The current environment */ - private var curEnv: Env = inContext(ictx): + private val rootEnv: Env = inContext(ictx): Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) + private var curEnv = rootEnv /** 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() + /** A list of actions to perform at postCheck. The reason to defer these actions + * is that it is sometimes better for type inference to not constrain too early + * with a checkConformsExpr. + */ + private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] + /** If `sym` is a class or method nested inside a term, a capture set variable representing * the captured variables of the environment associated with `sym`. */ - def capturedVars(sym: Symbol)(using Context) = + def capturedVars(sym: Symbol)(using Context): CaptureSet = myCapturedVars.getOrElseUpdate(sym, - if sym.ownersIterator.exists(_.isTerm) then - CaptureSet.Var(if sym.isConstructor then sym.owner.owner else sym.owner) + if sym.ownersIterator.exists(_.isTerm) + then CaptureSet.Var(sym.owner) else CaptureSet.empty) /** For all nested environments up to `limit` or a closed environment perform `op`, @@ -356,46 +352,14 @@ class CheckCaptures extends Recheck, SymTransformer: def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) - private def handleBackwardsCompat(tp: Type, sym: Symbol, initialVariance: Int = 1)(using Context): Type = - val fluidify = new TypeMap with IdempotentCaptRefMap: - variance = initialVariance - def apply(t: Type): Type = t match - case t: MethodType => - mapOver(t) - case t: TypeLambda => - t.derivedLambdaType(resType = this(t.resType)) - case CapturingType(_, _) => - t - case _ => - val t1 = t match - case t @ defn.RefinedFunctionOf(rinfo: MethodType) => - t.derivedRefinedType(refinedInfo = this(rinfo)) - case _ => - mapOver(t) - if variance > 0 then t1 - else setup.decorate(t1, mapRoots = false, addedSet = Function.const(CaptureSet.Fluid)) - - def isPreCC(sym: Symbol): Boolean = - sym.isTerm && sym.maybeOwner.isClass - && !sym.owner.is(CaptureChecked) - && !defn.isFunctionSymbol(sym.owner) - - if isPreCC(sym) then - val tpw = tp.widen - val fluidTp = fluidify(tpw) - if fluidTp eq tpw then tp - else fluidTp.showing(i"fluid for ${sym.showLocated}, ${sym.is(JavaDefined)}: $tp --> $result", capt) - else tp - end handleBackwardsCompat - - override def recheckIdent(tree: Ident)(using Context): Type = + override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = if tree.symbol.is(Method) then if tree.symbol.info.isParameterless then // there won't be an apply; need to include call captures now includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - handleBackwardsCompat(super.recheckIdent(tree), tree.symbol) + super.recheckIdent(tree, pt) /** A specialized implementation of the selection rule. * @@ -424,8 +388,12 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet - if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then - handleBackwardsCompat(selType, tree.symbol) + if selCs.isAlwaysEmpty + || selType.widen.isBoxedCapturing + || qualType.isBoxedCapturing + || pt == LhsProto + then + selType else val qualCs = qualType.captureSet capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") @@ -439,16 +407,6 @@ 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 @@ -473,11 +431,11 @@ class CheckCaptures extends Recheck, SymTransformer: if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.capturing(CaptureSet(CaptureRoot.Var(ctx.owner)))) + val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing - capt.println(i"rechecking $arg with ${pt.capturing(CaptureSet.universal)}: $argType") + capt.println(i"rechecking $arg with $pt: $argType") super.recheckFinish(argType, tree, pt) else if meth == defn.Caps_unsafeBox then mapArgUsing(_.forceBoxStatus(true)) @@ -524,7 +482,7 @@ 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, + * If all argument types are mutually different 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. @@ -579,6 +537,20 @@ class CheckCaptures extends Recheck, SymTransformer: else ownType end instantiate + override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = + if ccConfig.allowUniversalInBoxed then + val TypeApply(fn, args) = tree + val polyType = atPhase(thisPhase.prev): + 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 "" + disallowRootCapabilitiesIn(arg.knownType, fn.symbol, + i"Sealed type variable $pname", "be instantiated to", + i"This is often caused by a local capability$where\nleaking as part of its result.", + tree.srcPos) + super.recheckTypeApply(tree, pt) + 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") @@ -608,18 +580,20 @@ class CheckCaptures extends Recheck, SymTransformer: // 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 = + override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try - if !sym.is(Module) then // Modules are checked by checking the module class - super.recheckValDef(tree, sym) + if sym.is(Module) then sym.info // Modules are checked by checking the module class + else + if sym.is(Mutable) && !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then + disallowRootCapabilitiesIn(tree.tpt.knownType, sym, + i"mutable $sym", "have type", "", sym.srcPos) + checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then // Parameters with inferred types belong to anonymous methods. We need to wait @@ -628,17 +602,62 @@ class CheckCaptures extends Recheck, SymTransformer: // function is compiled since we do not propagate expected types into blocks. interpolateVarsIn(tree.tpt) - override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Unit = - if !Synthetics.isExcluded(sym) then + override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = + if Synthetics.isExcluded(sym) then sym.info + else val saved = curEnv val localSet = capturedVars(sym) if !localSet.isAlwaysEmpty then curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) - try super.recheckDefDef(tree, sym) + try checkInferredResult(super.recheckDefDef(tree, sym), tree) finally - interpolateVarsIn(tree.tpt) + if !sym.isAnonymousFunction then + // Anonymous functions propagate their type to the enclosing environment + // so it is not in general sound to interpolate their types. + interpolateVarsIn(tree.tpt) curEnv = saved + /** If val or def definition with inferred (result) type is visible + * in other compilation units, check that the actual inferred type + * conforms to the expected type where all inferred capture sets are dropped. + * This ensures that if files compile separately, they will also compile + * in a joint compilation. + */ + def checkInferredResult(tp: Type, tree: ValOrDefDef)(using Context): Type = + val sym = tree.symbol + + def isLocal = + 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 + // 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 + + def addenda(expected: Type) = new Addenda: + override def toAdd(using Context) = + def result = if tree.isInstanceOf[ValDef] then"" else " result" + i""" + | + |Note that the expected type $expected + |is the previously inferred$result type of $sym + |which is also the type seen in separately compiled sources. + |The new inferred type $tp + |must conform to this type.""" :: Nil + + tree.tpt match + case tpt: InferredTypeTree if !canUseInferred => + val expected = tpt.tpe.dropAllRetains + todoAtPostCheck += (() => checkConformsExpr(tp, expected, tree.rhs, addenda(expected))) + case _ => + tp + end checkInferredResult + /** Class-specific capture set relations: * 1. The capture set of a class includes the capture sets of its parents. * 2. The capture set of the self type of a class includes the capture set of the class. @@ -652,7 +671,8 @@ class CheckCaptures extends Recheck, SymTransformer: for parent <- impl.parents do // (1) 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) + 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") checkSubset(localSet, thisSet, tree.srcPos) // (2) @@ -682,14 +702,13 @@ class CheckCaptures extends Recheck, SymTransformer: super.recheckTyped(tree) override def recheckTry(tree: Try, pt: Type)(using Context): Type = - 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 + val tp = super.recheckTry(tree, pt) + if ccConfig.allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then + disallowRootCapabilitiesIn(tp, ctx.owner, + "result of `try`", "have type", + "This is often caused by a locally generated exception capability leaking as part of its result.", + tree.srcPos) + tp /* Currently not needed, since capture checking takes place after ElimByName. * Keep around in case we need to get back to it @@ -721,19 +740,20 @@ class CheckCaptures extends Recheck, SymTransformer: curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner), curEnv) case _ => val res = - try super.recheck(tree, pt) + try + if capt eq noPrinter then + super.recheck(tree, pt) + else + trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): + super.recheck(tree, pt) + catch case ex: NoCommonRoot => + report.error(ex.getMessage.nn) + tree.tpe finally curEnv = saved if tree.isTerm && !pt.isBoxedCapturing then markFree(res.boxedCaptureSet, tree.srcPos) res - /** If `tree` is a reference or an application where the result type refers - * to an enclosing class or method parameter of the reference, check that the result type - * does not capture the universal capability. This is justified since the - * result type would have to be implicitly unboxed. - * TODO: Can we find a cleaner way to achieve this? Logically, this should be part - * of simulated boxing and unboxing. - */ override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = def needsUniversalCheck = tree match case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult @@ -749,9 +769,13 @@ class CheckCaptures extends Recheck, SymTransformer: } checkNotUniversal(parent) case _ => - if !allowUniversalInBoxed && needsUniversalCheck then - checkNotUniversal(tpe) - super.recheckFinish(tpe, tree, pt) + val adapted = + if ccConfig.allowUniversalInBoxed then + adaptUniversal(tpe, pt, tree) + else + if needsUniversalCheck then checkNotUniversal(tpe) + tpe + super.recheckFinish(adapted, tree, pt) end recheckFinish // ------------------ Adaptation ------------------------------------- @@ -765,32 +789,75 @@ class CheckCaptures extends Recheck, SymTransformer: // - 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 - } - } + /** The local root that is implied for the expression `tree`. + * This is the local root of the outermost level owner that includes + * all free variables of the expression that have in their types + * some capturing type occuring in covariant or invariant position. + */ + def impliedRoot(tree: Tree)(using Context) = + def isTrackedSomewhere(sym: Symbol): Boolean = + val search = new TypeAccumulator[Boolean]: + def apply(found: Boolean, tp: Type) = + def isTrackedHere = variance >= 0 && !tp.captureSet.isAlwaysEmpty + found || isTrackedHere || foldOver(found, tp) + if sym.is(Method) + then !capturedVars(sym).elems.isEmpty || search(false, sym.info.finalResultType) + else search(false, sym.info) + + val acc = new TreeAccumulator[Symbol]: + val locals = mutable.Set[Symbol]() + private def max(sym1: Symbol, sym2: Symbol)(using Context) = + sym1.maxNested(sym2, onConflict = (_, _) => throw NoCommonRoot(sym1, sym2)) + def apply(s: Symbol, t: Tree)(using Context) = t match + case t: (Ident | This) + if !locals.contains(t.symbol) && isTrackedSomewhere(t.symbol) => + max(s, t.symbol.levelOwner) + case t: DefTree => + locals += t.symbol + foldOver(s, t) + case _ => + foldOver(s, t) + acc(NoSymbol, tree).orElse(ctx.owner).localRoot - /** Massage `actual` and `expected` types using the methods below before checking conformance */ - override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Unit = + /** Assume `actual` is the type of an expression `tree` with the given + * `expected` type. If `actual` captures `cap` and `cap` is not allowed + * in the capture set of `expected`, narrow `cap` to the root capability + * that is implied for `tree`. + */ + def adaptUniversal(actual: Type, expected: Type, tree: Tree)(using Context): Type = + if expected.captureSet.disallowsUniversal && actual.captureSet.isUniversal then + val localRoot = impliedRoot(tree) + CapturingType( + actual.stripCapturing, + localRoot.termRef.singletonCaptureSet, + actual.isBoxedCapturing) + .showing(i"adapt universal $actual vs $expected = $result", capt) + else actual + + private inline val debugSuccesses = false + + /** Massage `actual` and `expected` types before checking conformance. + * Massaging is done by the methods following this one: + * - align dependent function types and add outer references in the expected type + * - adapt boxing in the actual type + * If the resulting types are not compatible, try again with an actual type + * where local capture roots are instantiated to root variables. + */ + override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type = 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, addenda ++ CaptureSet.levelErrors) - - private def toDepFun(args: List[Type], resultType: Type, isContextual: Boolean)(using Context): Type = - MethodType.companion(isContextual = isContextual)(args, resultType) - .toFunctionType(alwaysDependent = true) + val actualBoxed = adaptBoxed(actual, expected1, tree.srcPos) + //println(i"check conforms $actualBoxed <<< $expected1") + if isCompatible(actualBoxed, expected1) then + if debugSuccesses then tree match + case Ident(_) => + println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}") + case _ => + actualBoxed + else + capt.println(i"conforms failed for ${tree}: $actual vs $expected") + err.typeMismatch(tree.withType(actualBoxed), expected1, addenda ++ CaptureSet.levelErrors) + actual + end checkConformsExpr /** Turn `expected` into a dependent function when `actual` is dependent. */ private def alignDependentFunction(expected: Type, actual: Type)(using Context): Type = @@ -800,11 +867,13 @@ class CheckCaptures extends Recheck, SymTransformer: if eparent1 eq eparent then expected else CapturingType(eparent1, refs, boxed = expected0.isBoxed) case expected @ defn.FunctionOf(args, resultType, isContextual) - if defn.isNonRefinedFunction(expected) && defn.isFunctionNType(actual) && !defn.isNonRefinedFunction(actual) => - val expected1 = toDepFun(args, resultType, isContextual) - expected1 - case _ => - expected + if defn.isNonRefinedFunction(expected) => + actual match + case RefinedType(parent, nme.apply, rinfo: MethodType) + if defn.isFunctionNType(actual) => + depFun(args, resultType, isContextual, rinfo.paramNames) + case _ => expected + case _ => expected recur(expected) /** For the expected type, implement the rule outlined in #14390: @@ -853,6 +922,7 @@ class CheckCaptures extends Recheck, SymTransformer: expected end addOuterRefs + /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions * * @param alwaysConst always make capture set variables constant after adaptation @@ -956,17 +1026,25 @@ class CheckCaptures extends Recheck, SymTransformer: } // Capture set of the term after adaptation - val cs1 = cs ++ leaked + val cs1 = + if covariant then cs ++ leaked + else + if !leaked.subCaptures(cs, frozen = false).isOK then + report.error( + em"""$expected cannot be box-converted to $actual + |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos) + cs // Compute the adapted type def adaptedType(resultBoxed: Boolean) = - styp1.capturing(if alwaysConst then CaptureSet(cs1.elems) else cs1).forceBoxStatus(resultBoxed) + if (styp1 eq styp) && leaked.isAlwaysEmpty && boxed == resultBoxed then actual + else styp1.capturing(if alwaysConst then CaptureSet(cs1.elems) else cs1).forceBoxStatus(resultBoxed) if needsAdaptation then val criticalSet = // the set which is not allowed to have `cap` if covariant then cs1 // can't box with `cap` else expected.captureSet // can't unbox with `cap` - if criticalSet.isUniversal && expected.isValueType && !allowUniversalInBoxed then + if criticalSet.isUniversal && expected.isValueType && !ccConfig.allowUniversalInBoxed then // We can't box/unbox the universal capability. Leave `actual` as it is // so we get an error in checkConforms. This tends to give better error // messages than disallowing the root capability in `criticalSet`. @@ -974,7 +1052,7 @@ class CheckCaptures extends Recheck, SymTransformer: println(i"cannot box/unbox $actual vs $expected") actual else - if !allowUniversalInBoxed then + if !ccConfig.allowUniversalInBoxed then // Disallow future addition of `cap` to `criticalSet`. criticalSet.disallowRootCapability { () => report.error( @@ -989,7 +1067,7 @@ class CheckCaptures extends Recheck, SymTransformer: adaptedType(boxed) } - if expected.isSingleton && actual.isSingleton then + if expected == LhsProto || expected.isSingleton && actual.isSingleton then actual else var actualw = actual.widenDealias @@ -1038,9 +1116,8 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved actual1 frozen_<:< expected1 - override def adjustInfo(tp: Type, member: Symbol)(using Context): Type = - handleBackwardsCompat(tp, member, initialVariance = 0) - //.showing(i"adjust $other: $tp --> $result") + override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = + !setup.isPreCC(overriding) && !setup.isPreCC(overridden) end OverridingPairsCheckerCC def traverse(t: Tree)(using Context) = @@ -1050,20 +1127,47 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) - private var setup: Setup = compiletime.uninitialized + /** Check a ValDef or DefDef as an action performed in a completer. Since + * these checks can appear out of order, we need to firsty create the correct + * environment for checking the definition. + */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + val saved = curEnv + try + // Setup environment to reflect the new owner. + val envForOwner: Map[Symbol, Env] = curEnv.outersIterator + .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyind this point + .map(e => (e.owner, e)) + .toMap + def restoreEnvFor(sym: Symbol): Env = + val localSet = capturedVars(sym) + if localSet.isAlwaysEmpty then rootEnv + else envForOwner.get(sym) match + case Some(e) => e + case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) + curEnv = restoreEnvFor(sym.owner) + capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") + recheckDef(tree, sym) + finally + curEnv = saved + + private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = - 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 + setup.setupUnit(ctx.compilationUnit.tpdTree, completeDef) + + if ctx.settings.YccPrintSetup.value then + val echoHeader = "[[syntax tree at end of cc setup]]" + val treeString = show(ctx.compilationUnit.tpdTree) + report.echo(s"$echoHeader\n$treeString\n") + + 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 @@ -1094,12 +1198,7 @@ class CheckCaptures extends Recheck, SymTransformer: } assert(roots.nonEmpty) for case root: ClassSymbol <- roots do - 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. + inContext(ctx.fresh.setOwner(root)): checkSelfAgainstParents(root, root.baseClasses) val selfType = root.asClass.classInfo.selfType interpolator(startingVariance = -1).traverse(selfType) @@ -1142,7 +1241,7 @@ class CheckCaptures extends Recheck, SymTransformer: * compensate this by pushing the widened capture set of `f` into ?1. * This solves the soundness issue caused by the ill-formness of ?1. */ - private def healTypeParam(tree: Tree)(using Context): Unit = + private def healTypeParam(tree: Tree, paramName: TypeName, meth: Symbol)(using Context): Unit = val checker = new TypeTraverser: private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty @@ -1151,22 +1250,29 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => true private def healCaptureSet(cs: CaptureSet): Unit = - cs.ensureWellformed: elems => + cs.ensureWellformed: elem => 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) + def recur(ref: CaptureRef): Unit = ref match + case ref: TermParamRef + if !allowed.contains(ref) && !seen.contains(ref) => + 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") + if !added.subCaptures(cs, frozen = false).isOK then + val location = if meth.exists then i" of $meth" else "" + val debugSetInfo = if ctx.settings.YccDebug.value then i" $cs" else "" + report.error( + i"local reference ${ref.paramName} leaks into outer capture set$debugSetInfo of type parameter $paramName$location", + 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) + widened.elems.foreach(recur) + case _ => + recur(elem) def traverse(tp: Type) = tp match @@ -1190,9 +1296,6 @@ class CheckCaptures extends Recheck, SymTransformer: /** Perform the following kinds of checks * - Check all explicitly written capturing types for well-formedness using `checkWellFormedPost`. - * - Check that externally visible `val`s or `def`s have empty capture sets. If not, - * suggest an explicit type. This is so that separate compilation (where external - * symbols have empty capture sets) gives the same results as joint compilation. * - Check that arguments of TypeApplys and AppliedTypes conform to their bounds. * - Heal ill-formed capture sets of type parameters. See `healTypeParam`. */ @@ -1205,76 +1308,32 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(tree)(using lctx) check(tree) 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, tree) - case _ => - } - case t: ValOrDefDef - if t.tpt.isInstanceOf[InferredTypeTree] && !Synthetics.isExcluded(t.symbol) => - val sym = t.symbol - val isLocal = - 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 - // 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 - } - if !canUseInferred then - val inferred = t.tpt.knownType - def checkPure(tp: Type) = tp match - case CapturingType(_, refs) - if !refs.elems.filter(isNotPureThis).isEmpty => - val resultStr = if t.isInstanceOf[DefDef] then " result" else "" - report.error( - em"""Non-local $sym cannot have an inferred$resultStr type - |$inferred - |with non-empty capture set $refs. - |The type needs to be declared explicitly.""".withoutDisambiguation(), - t.srcPos) - case _ => - inferred.foreachPart(checkPure, StopAt.Static) case t @ TypeApply(fun, args) => fun.knownType.widen match case tl: PolyType => - val normArgs = args.lazyZip(tl.paramInfos).map { (arg, bounds) => + val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) => arg.withType(arg.knownType.forceBoxStatus( bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing)) - } checkBounds(normArgs, tl) + args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) case _ => - - args.foreach(healTypeParam(_)) case _ => end check end checker checker.traverse(unit)(using ctx.withOwner(defn.RootClass)) + for chk <- todoAtPostCheck do chk() + setup.postCheck() + if !ctx.reporter.errorsReported then - //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) + // 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/RetainingType.scala b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala new file mode 100644 index 000000000000..7902b03445fb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala @@ -0,0 +1,35 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Types.*, Symbols.*, Contexts.* +import ast.tpd.* +import Annotations.Annotation +import Decorators.i + +/** A builder and extractor for annotated types with @retains or @retainsByName annotations. + */ +object RetainingType: + + def apply(tp: Type, refs: List[Tree], byName: Boolean = false)(using Context): Type = + val annotCls = if byName then defn.RetainsByNameAnnot else defn.RetainsAnnot + val annotTree = + New(annotCls.typeRef, + Typed( + SeqLiteral(refs, TypeTree(defn.AnyType)), + TypeTree(defn.RepeatedParamClass.typeRef.appliedTo(defn.AnyType))) :: Nil) + AnnotatedType(tp, Annotation(annotTree)) + + def unapply(tp: AnnotatedType)(using Context): Option[(Type, List[Tree])] = + val sym = tp.annot.symbol + if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then + tp.annot match + case _: CaptureAnnotation => + assert(ctx.mode.is(Mode.IgnoreCaptures), s"bad retains $tp at ${ctx.phase}") + None + case ann => + Some((tp.parent, ann.tree.retainedElems)) + else + None +end RetainingType diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index e18c5e559aba..68fd79048f41 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -8,12 +8,33 @@ 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 config.Printers.{capt, captDebug} +import ast.tpd, tpd.* +import transform.{PreRecheck, Recheck}, Recheck.* +import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded import util.Property +import printing.{Printer, Texts}, Texts.{Text, Str} +import collection.mutable + +/** Operations accessed from CheckCaptures */ +trait SetupAPI: + type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type + def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + def isPreCC(sym: Symbol)(using Context): Boolean + def postCheck()(using Context): Unit + +object Setup: + + /** Recognizer for `res $throws exc`, returning `(res, exc)` in case of success */ + object throwsAlias: + def unapply(tp: Type)(using Context): Option[(Type, Type)] = tp match + case AppliedType(tycon, res :: exc :: Nil) if tycon.typeSymbol == defn.throwsAlias => + Some((res, exc)) + case _ => + None +end Setup +import Setup.* /** A tree traverser that prepares a compilation unit to be capture checked. * It does the following: @@ -27,29 +48,81 @@ import util.Property * are boxed on access). * - Link the external types of val and def symbols with the inferred types based on their parameter symbols. */ -class Setup( - preRecheckPhase: DenotTransformer, - thisPhase: DenotTransformer, - recheckDef: (tpd.ValOrDefDef, Symbol) => Context ?=> Unit) -extends tpd.TreeTraverser: - import tpd.* - - /** Create dependent function with underlying function class `tycon` and given - * arguments `argTypes` and result `resType`. +class Setup extends PreRecheck, SymTransformer, SetupAPI: + thisPhase => + + override def isRunnable(using Context) = + super.isRunnable && Feature.ccEnabledSomewhere + + private val toBeUpdated = new mutable.HashSet[Symbol] + + private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = + if symd.isAllOf(PrivateParamAccessor) && symd.owner.is(CaptureChecked) && !symd.hasAnnotation(defn.ConstructorOnlyAnnot) + then symd.flags &~ Private | Recheck.ResetPrivate + else symd.flags + + def isPreCC(sym: Symbol)(using Context): Boolean = + sym.isTerm && sym.maybeOwner.isClass + && !sym.is(Module) + && !sym.owner.is(CaptureChecked) + && !defn.isFunctionSymbol(sym.owner) + + private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: + def apply(t: Type): Type = t match + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t + case _ => + val t1 = t match + case t @ defn.RefinedFunctionOf(rinfo: MethodType) => + t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) + case _ => + mapOver(t) + if variance > 0 then t1 + else decorate(t1, addedSet = Function.const(CaptureSet.Fluid)) + + /** - Reset `private` flags of parameter accessors so that we can refine them + * in Setup if they have non-empty capture sets. + * - Special handling of some symbols defined for case classes. + * Enabled only until recheck is finished, and provided some compilation unit + * is CC-enabled. */ - private def depFun(tycon: Type, argTypes: List[Type], resType: Type)(using Context): Type = - MethodType.companion( - isContextual = defn.isContextFunctionClass(tycon.classSymbol), - )(argTypes, resType) - .toFunctionType(alwaysDependent = true) + def transformSym(symd: SymDenotation)(using Context): SymDenotation = + if !pastRecheck && Feature.ccEnabledSomewhere then + val sym = symd.symbol + def mappedInfo = + if toBeUpdated.contains(sym) then symd.info + else transformExplicitType(symd.info) + if Synthetics.needsTransform(symd) then + Synthetics.transform(symd, mappedInfo) + else if isPreCC(sym) then + symd.copySymDenotation(info = fluidify(sym.info)) + else if symd.owner.isTerm || symd.is(CaptureChecked) || symd.owner.is(CaptureChecked) then + val newFlags = newFlagsFor(symd) + val newInfo = mappedInfo + if sym.isClass then + sym.thisType.asInstanceOf[ThisType].invalidateCaches() + if newFlags != symd.flags || (newInfo ne sym.info) + then symd.copySymDenotation(initFlags = newFlags, info = newInfo) + else symd + else symd + else symd + end transformSym /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, * convert it to be boxed. */ private def box(tp: Type)(using Context): Type = - def recur(tp: Type): Type = tp.dealias match - case tp @ CapturingType(parent, refs) if !tp.isBoxed => - tp.boxed + def recur(tp: Type): Type = tp.dealiasKeepAnnots match + case tp @ CapturingType(parent, refs) => + if tp.isBoxed then tp else tp.boxed + case tp @ AnnotatedType(parent, ann) => + if ann.symbol == defn.RetainsAnnot + then CapturingType(parent, ann.tree.toCaptureSet, boxed = true) + else tp.derivedAnnotatedType(box(parent), ann) case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) => val res = args.last val boxedRes = recur(res) @@ -78,186 +151,177 @@ extends tpd.TreeTraverser: * pos.../lists.scala and pos/...curried-shorthands.scala fail. * Need to figure out why. * 3. Refine other class types C by adding capture set variables to their parameter getters - * (see addCaptureRefinements) + * (see addCaptureRefinements), provided `refine` is true. * 4. Add capture set variables to all types that can be tracked * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - private def mapInferred(mapRoots: Boolean)(using Context) = new TypeMap: - override def toString = "map inferred" - - /** Drop @retains annotations everywhere */ - object cleanup extends TypeMap: - def apply(t: Type) = t match - case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => - apply(parent) - case _ => - mapOver(t) + private def transformInferredType(tp: Type)(using Context): Type = + def mapInferred(refine: Boolean): TypeMap = new TypeMap: + override def toString = "map inferred" - /** Refine a possibly applied class type C where the class has tracked parameters - * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } - * where CV_1, ..., CV_n are fresh capture sets. - */ - def addCaptureRefinements(tp: Type): Type = tp match - case _: TypeRef | _: AppliedType if tp.typeParams.isEmpty => - tp.typeSymbol match - case cls: ClassSymbol - if !defn.isFunctionClass(cls) && !cls.is(JavaDefined) => - // We assume that Java classes can refer to capturing Scala types only indirectly, - // using type parameters. Hence, no need to refine them. - cls.paramGetters.foldLeft(tp) { (core, getter) => - if getter.termRef.isTracked then - val getterType = tp.memberInfo(getter).strippedDealias - RefinedType(core, getter.name, - CapturingType(getterType, CaptureSet.RefiningVar(ctx.owner, getter))) - .showing(i"add capture refinement $tp --> $result", capt) + /** Refine a possibly applied class type C where the class has tracked parameters + * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } + * where CV_1, ..., CV_n are fresh capture sets. + */ + def addCaptureRefinements(tp: Type): Type = tp match + case _: TypeRef | _: AppliedType if refine && tp.typeParams.isEmpty => + tp.typeSymbol match + case cls: ClassSymbol + if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) => + cls.paramGetters.foldLeft(tp) { (core, getter) => + if atPhase(thisPhase.next)(getter.termRef.isTracked) then + val getterType = + mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias + RefinedType(core, getter.name, + CapturingType(getterType, CaptureSet.Var(ctx.owner))) + .showing(i"add capture refinement $tp --> $result", capt) + else + core + } + case _ => tp + case _ => tp + + private var isTopLevel = true + + private def mapNested(ts: List[Type]): List[Type] = + val saved = isTopLevel + isTopLevel = false + try ts.mapConserve(this) + finally isTopLevel = saved + + def apply(tp: Type) = + val tp1 = tp match + case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => + // Drop explicit retains annotations + apply(parent) + case tp @ AppliedType(tycon, args) => + val tycon1 = this(tycon) + if defn.isNonRefinedFunction(tp) then + // Convert toplevel generic function types to dependent functions + if !defn.isFunctionSymbol(tp.typeSymbol) && (tp.dealias ne tp) then + // This type is a function after dealiasing, so we dealias and recurse. + // See #15925. + this(tp.dealias) else - core - } - case _ => tp - case _ => tp - - private var isTopLevel = true - - private def mapNested(ts: List[Type]): List[Type] = - val saved = isTopLevel - isTopLevel = false - try ts.mapConserve(this) finally isTopLevel = saved - - def apply(tp: Type) = - val tp1 = tp match - case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => - // Drop explicit retains annotations - apply(parent) - case tp @ AppliedType(tycon, args) => - val tycon1 = this(tycon) - if defn.isNonRefinedFunction(tp) then - // Convert toplevel generic function types to dependent functions - if !defn.isFunctionSymbol(tp.typeSymbol) && (tp.dealias ne tp) then - // This type is a function after dealiasing, so we dealias and recurse. - // See #15925. - this(tp.dealias) + val args0 = args.init + var res0 = args.last + val args1 = mapNested(args0) + val res1 = this(res0) + if isTopLevel then + depFun(args1, res1, + isContextual = defn.isContextFunctionClass(tycon1.classSymbol)) + .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", capt) + else if (tycon1 eq tycon) && (args1 eq args0) && (res1 eq res0) then + tp + else + tp.derivedAppliedType(tycon1, args1 :+ res1) else - val args0 = args.init - var res0 = args.last - val args1 = mapNested(args0) - val res1 = this(res0) - if isTopLevel then - depFun(tycon1, args1, res1) - .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", capt) - else if (tycon1 eq tycon) && (args1 eq args0) && (res1 eq res0) then - tp - else - tp.derivedAppliedType(tycon1, args1 :+ res1) - else - tp.derivedAppliedType(tycon1, args.mapConserve(arg => this(arg))) - case defn.RefinedFunctionOf(rinfo: MethodType) => - val rinfo1 = apply(rinfo) - if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true) - else tp - case tp: MethodType => - tp.derivedLambdaType( - paramInfos = mapNested(tp.paramInfos), - resType = this(tp.resType)) - case tp: TypeLambda => - // Don't recurse into parameter bounds, just cleanup any stray retains annotations - tp.derivedLambdaType( - paramInfos = tp.paramInfos.mapConserve(cleanup(_).bounds), - resType = this(tp.resType)) - case _ => - mapOver(tp) - addVar(addCaptureRefinements(tp1), ctx.owner, mapRoots) - end apply - end mapInferred - - 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 */ - object throwsAlias: - def unapply(tp: Type)(using Context): Option[(Type, Type)] = tp match - case AppliedType(tycon, res :: exc :: Nil) if tycon.typeSymbol == defn.throwsAlias => - Some((res, exc)) - case _ => - None - - /** Expand $throws aliases. This is hard-coded here since $throws aliases in stdlib - * are defined with `?=>` rather than `?->`. - * We also have to add a capture set to the last expanded throws alias. I.e. - * T $throws E1 $throws E2 - * expands to - * (erased x$0: CanThrow[E1]) ?-> (erased x$1: CanThrow[E1]) ?->{x$0} T - */ - private def expandThrowsAlias(tp: Type, encl: List[MethodType] = Nil)(using Context): Type = tp match - case throwsAlias(res, exc) => - val paramType = AnnotatedType( - defn.CanThrowClass.typeRef.appliedTo(exc), - Annotation(defn.ErasedParamAnnot, defn.CanThrowClass.span)) - val isLast = throwsAlias.unapply(res).isEmpty - val paramName = nme.syntheticParamName(encl.length) - val mt = ContextualMethodType(paramName :: Nil)( - _ => paramType :: Nil, - mt => if isLast then res else expandThrowsAlias(res, mt :: encl)) - val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && isLast then - val cs = CaptureSet(encl.map(_.paramRefs.head)*) - CapturingType(fntpe, cs, boxed = false) - else fntpe - case _ => tp - - extension (tp: Type) def isCapabilityClassRef(using Context) = tp match - case _: TypeRef | _: AppliedType => tp.typeSymbol.hasAnnotation(defn.CapabilityAnnot) - case _ => false - - /** 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 + tp.derivedAppliedType(tycon1, args.mapConserve(arg => box(this(arg)))) + case defn.RefinedFunctionOf(rinfo: MethodType) => + val rinfo1 = apply(rinfo) + if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true) + else tp + case tp: MethodType => + tp.derivedLambdaType( + paramInfos = mapNested(tp.paramInfos), + resType = this(tp.resType)) + case tp: TypeLambda => + // Don't recurse into parameter bounds, just cleanup any stray retains annotations + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), + resType = this(tp.resType)) + case _ => + mapOver(tp) + addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) + end apply + end mapInferred + + mapInferred(refine = true)(tp) + end transformInferredType + + private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = + val expandAliases = new DeepTypeMap: + override def toString = "expand aliases" + + /** Expand $throws aliases. This is hard-coded here since $throws aliases in stdlib + * are defined with `?=>` rather than `?->`. + * We also have to add a capture set to the last expanded throws alias. I.e. + * T $throws E1 $throws E2 + * expands to + * (erased x$0: CanThrow[E1]) ?-> (erased x$1: CanThrow[E1]) ?->{x$0} T + */ + private def expandThrowsAlias(res: Type, exc: Type, encl: List[MethodType]): Type = + val paramType = AnnotatedType( + defn.CanThrowClass.typeRef.appliedTo(exc), + Annotation(defn.ErasedParamAnnot, defn.CanThrowClass.span)) + val resDecomposed = throwsAlias.unapply(res) + val paramName = nme.syntheticParamName(encl.length) + val mt = ContextualMethodType(paramName :: Nil)( + _ => paramType :: Nil, + mt => resDecomposed match + case Some((res1, exc1)) => expandThrowsAlias(res1, exc1, mt :: encl) + case _ => res + ) + val fntpe = defn.PolyFunctionOf(mt) + if !encl.isEmpty && resDecomposed.isEmpty then + val cs = CaptureSet(encl.map(_.paramRefs.head)*) + CapturingType(fntpe, cs, boxed = false) + else fntpe + + /** Map references to capability classes C to C^ */ + private def expandCapabilityClass(tp: Type): Type = + if tp.isCapabilityClassRef + then CapturingType(tp, defn.expandedUniversalSet, boxed = false) + else tp + + private def checkQualifiedRoots(tree: Tree): Unit = + for case elem @ QualifiedRoot(outer) <- tree.retainedElems 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 recur(t: Type): Type = normalizeCaptures(mapOver(t)) + + def apply(t: Type) = + t match + case t @ CapturingType(parent, refs) => + checkQualifiedRoots(t.annot.tree) // TODO: NEEDED? + t.derivedCapturingType(this(parent), refs) + case t @ AnnotatedType(parent, ann) => + val parent1 = this(parent) + if ann.symbol == defn.RetainsAnnot then + for tpt <- tptToCheck do + checkQualifiedRoots(ann.tree) + checkWellformedLater(parent1, ann.tree, tpt) + CapturingType(parent1, ann.tree.toCaptureSet) + else + t.derivedAnnotatedType(parent1, ann) + case throwsAlias(res, exc) => + this(expandThrowsAlias(res, exc, Nil)) + case t: LazyRef => + val t1 = this(t.ref) + if t1 ne t.ref then t1 else t + case t: TypeVar => + this(t.underlying) + case t => + if t.isCapabilityClassRef + then CapturingType(t, defn.expandedUniversalSet, boxed = false) + else recur(t) + end expandAliases + + val tp1 = expandAliases(tp) // TODO: Do we still need to follow aliases? + if tp1 ne tp then capt.println(i"expanded in ${ctx.owner}: $tp --> $tp1") + tp1 + end transformExplicitType /** Transform type of type tree, and remember the transformed type as the type the tree */ - private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean, mapRoots: Boolean)(using Context): Unit = + private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit = if !tree.hasRememberedType then - tree.rememberType( + val transformed = if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tree.tpe, boxed, mapRoots) - else transformExplicitType(tree.tpe, boxed, mapRoots)) + then transformInferredType(tree.tpe) + else transformExplicitType(tree.tpe, tptToCheck = Some(tree)) + tree.rememberType(if boxed then box(transformed) else transformed) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -266,7 +330,6 @@ 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 => @@ -293,278 +356,197 @@ extends tpd.TreeTraverser: recur(to, from) case _ => mapOver(t) - def inverse = thisMap + def inverse = SubstParams.this 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.updateInfo(preRecheckPhase, info, newOwnerFor(sym)) + toBeUpdated += sym + sym.updateInfo(thisPhase, info, newFlagsFor(sym)) + toBeUpdated -= sym sym.namedType match - case ref: CaptureRef => ref.invalidateCaches() + case ref: CaptureRef => ref.invalidateCaches() // TODO: needed? 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(_, paramss, tpt: TypeTree, _) => - val meth = tree.symbol - if isExcluded(meth) then - return - - 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, _) => - 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 + extension (sym: Symbol) def nextInfo(using Context): Type = + atPhase(thisPhase.next)(sym.info) - 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, 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) - postProcess(tree) - end traverse - - 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 + def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + + def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = + transformTT(tpt, + boxed = !ccConfig.allowUniversalInBoxed && sym.is(Mutable, butNot = Method), + // types of mutable variables are boxed in pre 3.3 codee + exact = sym.allOverriddenSymbols.hasNext, + // types of symbols that override a parent don't get a capture set TODO drop ) - case tree: ValOrDefDef => - val sym = tree.symbol + val addDescription = new TypeTraverser: + def traverse(tp: Type) = tp match + case tp @ CapturingType(parent, refs) => + if !refs.isConst then refs.withDescription(i"of $sym") + traverse(parent) + case _ => + traverseChildren(tp) + addDescription.traverse(tpt.knownType) + + def traverse(tree: Tree)(using Context): Unit = + tree match + case tree @ DefDef(_, paramss, tpt: TypeTree, _) => + val meth = tree.symbol + if isExcluded(meth) then + return + + inContext(ctx.withOwner(meth)): + paramss.foreach(traverse) + transformResultType(tpt, meth) + traverse(tree.rhs) + //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") + + case tree @ ValDef(_, tpt: TypeTree, _) => + val sym = tree.symbol + val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) + inContext(defCtx): + transformResultType(tpt, sym) + 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 + + case tree: TypeDef if tree.symbol.isClass => + inContext(ctx.withOwner(tree.symbol)): + traverseChildren(tree) + + case tree @ SeqLiteral(elems, tpt: TypeTree) => + traverse(elems) + transformTT(tpt, boxed = true, exact = false) - /** 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 + traverseChildren(tree) + postProcess(tree) + end traverse - /** 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) + def postProcess(tree: Tree)(using Context): Unit = tree match + case tree: TypeTree => + transformTT(tree, boxed = false, exact = false) + 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 + + def paramSignatureChanges = tree.match + case tree: DefDef => tree.paramss.nestedExists: + case param: ValDef => param.tpt.hasRememberedType + case param: TypeDef => param.rhs.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)) - 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 = + def signatureChanges = + tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges + + // 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` + resType: Type, // the locally computed return type + 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 !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then + mt.paramInfos + else + val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) + psyms.map(psym => subst(psym.nextInfo).asInstanceOf[mt.PInfo]), + mt1 => + integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) + ) + case info: ExprType => + info.derivedExprType(resType = + integrateRT(info.resType, psymss, resType, prevPsymss, prevLambdas)) + case info => + if prevLambdas.isEmpty then resType + else SubstParams(prevPsymss, prevLambdas)(resType) + + if sym.exists && signatureChanges then + val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) + .showing(i"update info $sym: ${sym.info} = $result", capt) + if newInfo ne sym.info then + val updatedInfo = + if sym.isAnonymousFunction + || sym.is(Param) + || sym.is(ParamAccessor) + || sym.isPrimaryConstructor + 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 + assert(ctx.phase == thisPhase.next, i"$sym") + capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") + //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() + denot.info = newInfo + recheckDef(tree, sym) + updateInfo(sym, updatedInfo) + + case tree: Bind => + val sym = tree.symbol + updateInfo(sym, transformInferredType(sym.info)) + case tree: TypeDef => + tree.symbol match + case cls: ClassSymbol => + val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo 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. - // 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 + val newSelfType = CapturingType(cinfo.selfType, CaptureSet.Var(cls)) + val ps1 = inContext(ctx.withOwner(cls)): + ps.mapConserve(transformExplicitType(_)) + val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) + updateInfo(cls, newInfo) + capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo") + cls.thisType.asInstanceOf[ThisType].invalidateCaches() + if cls.is(ModuleClass) then + // if it's a module, the capture set of the module reference is the capture set of the self type + val modul = cls.sourceModule + updateInfo(modul, CapturingType(modul.info, newSelfType.captureSet)) + modul.termRef.invalidateCaches() + case _ => + case _ => + end postProcess + end setupTraverser private def superTypeIsImpure(tp: Type)(using Context): Boolean = { - tp.dealias match + tp.dealiasKeepAnnots match case CapturingType(_, refs) => !refs.isAlwaysEmpty + case RetainingType(parent, refs) => + !refs.isEmpty case tp: (TypeRef | AppliedType) => val sym = tp.typeSymbol if sym.isClass then @@ -576,7 +558,7 @@ extends tpd.TreeTraverser: case tp: (RefinedOrRecType | MatchType) => superTypeIsImpure(tp.underlying) case tp: AndType => - superTypeIsImpure(tp.tp1) || needsVariable(tp.tp2) + superTypeIsImpure(tp.tp1) || superTypeIsImpure(tp.tp2) case tp: OrType => superTypeIsImpure(tp.tp1) && superTypeIsImpure(tp.tp2) case _ => @@ -591,7 +573,7 @@ extends tpd.TreeTraverser: if sym.isClass then !sym.isPureClass && sym != defn.AnyClass else - val tp1 = tp.dealias + val tp1 = tp.dealiasKeepAnnots if tp1 ne tp then needsVariable(tp1) else superTypeIsImpure(tp1) case tp: (RefinedOrRecType | MatchType) => @@ -602,13 +584,18 @@ extends tpd.TreeTraverser: needsVariable(tp.tp1) || needsVariable(tp.tp2) case CapturingType(parent, refs) => 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 + && refs.isConst // if refs is a variable, no need to add another + && !refs.containsRoot // if refs is {cap}, an added variable would not change anything + case RetainingType(parent, refs) => + needsVariable(parent) + && !refs.tpes.exists: + case ref: TermRef => ref.isUniversalRootCapability + case _ => false case AnnotatedType(parent, _) => needsVariable(parent) case _ => false - }.showing(i"can have inferred capture $tp = $result", capt) + }.showing(i"can have inferred capture $tp = $result", captDebug) /** Pull out an embedded capture set from a part of `tp` */ def normalizeCaptures(tp: Type)(using Context): Type = tp match @@ -632,6 +619,10 @@ extends tpd.TreeTraverser: 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 tp @ AppliedType(tycon, args) if !defn.isFunctionClass(tp.dealias.typeSymbol) => + tp.derivedAppliedType(tycon, args.mapConserve(box)) + case tp: RealTypeBounds => + tp.derivedTypeBounds(tp.lo, box(tp.hi)) case tp: LazyRef => normalizeCaptures(tp.ref) case _ => @@ -640,7 +631,7 @@ extends tpd.TreeTraverser: /** 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, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type = + def decorate(tp: Type, 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 @@ -648,28 +639,70 @@ extends tpd.TreeTraverser: 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) + val dealiased = tp.dealiasKeepAnnots + if dealiased ne tp then + val transformed = transformInferredType(dealiased) + maybeAdd(transformed, if transformed ne dealiased then transformed else tp) + else maybeAdd(tp, 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, owner: Symbol, mapRoots: Boolean)(using Context): Type = - decorate(tp, mapRoots, + def addVar(tp: Type, owner: Symbol)(using Context): Type = + decorate(tp, addedSet = _.dealias.match case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems) case _ => CaptureSet.Var(owner)) - def apply(tree: Tree)(using Context): Unit = - traverse(tree)(using ctx.withProperty(Setup.IsDuringSetupKey, Some(()))) + def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = + setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) -object Setup: - val IsDuringSetupKey = new Property.Key[Unit] + // ------ Checks to run after main capture checking -------------------------- - def isDuringSetup(using Context): Boolean = - ctx.property(IsDuringSetupKey).isDefined + /** A list of actions to perform at postCheck */ + private val todoAtPostCheck = new mutable.ListBuffer[Context => Unit] + + /** If `tp` is a capturing type, check that all references it mentions have non-empty + * capture sets. + * Also: warn about redundant capture annotations. + * This check is performed after capture sets are computed in phase cc. + * 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. + */ + private def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + capt.println(i"checkWF post $parent ${ann.retainedElems} in $tpt") + var retained = ann.retainedElems.toArray + for i <- 0 until retained.length do + val refTree = retained(i) + val ref = refTree.toCaptureRef + + def pos = + if refTree.span.exists then refTree.srcPos + else if ann.span.exists then ann.srcPos + else tpt.srcPos + + def check(others: CaptureSet, dom: Type | CaptureSet): Unit = + if others.accountsFor(ref) then + report.warning(em"redundant capture: $dom already accounts for $ref", pos) + + if ref.captureSetOfInfo.elems.isEmpty then + report.error(em"$ref cannot be tracked since its capture set is empty", pos) + if parent.captureSet ne defn.expandedUniversalSet then + check(parent.captureSet, parent) + + val others = + for j <- 0 until retained.length if j != i yield retained(j).toCaptureRef + val remaining = CaptureSet(others*) + check(remaining, remaining) + end for + end checkWellformedPost + + /** Check well formed at post check time */ + private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + if !tpt.span.isZeroExtent then + todoAtPostCheck += (ctx1 => + checkWellformedPost(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + + def postCheck()(using Context): Unit = + for chk <- todoAtPostCheck do chk(ctx) end Setup \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 1509fd838265..352c5170bef0 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -7,7 +7,6 @@ import Symbols.*, SymDenotations.*, Contexts.*, Flags.*, Types.*, Decorators.* import StdNames.nme import Names.Name import NameKinds.DefaultGetterName -import Phases.checkCapturesPhase import config.Printers.capt /** Classification and transformation methods for function methods and @@ -59,9 +58,9 @@ object Synthetics: /** Transform the type of a method either to its type under capture checking * or back to its previous type. * @param sym The method to transform @pre needsTransform(sym) must hold. - * @param toCC Whether to transform the type to capture checking or back. + * @param info The possibly already mapped info of sym */ - def transform(sym: SymDenotation)(using Context): SymDenotation = + def transform(symd: SymDenotation, info: Type)(using Context): SymDenotation = /** Add capture dependencies to the type of the `apply` or `copy` method of a case class. * An apply method in a case class like this: @@ -73,7 +72,7 @@ object Synthetics: */ def addCaptureDeps(info: Type): Type = info match case info: MethodType => - val trackedParams = info.paramRefs.filter(atPhase(checkCapturesPhase)(_.isTracked)) + val trackedParams = info.paramRefs.filter(atPhase(Phases.checkCapturesPhase)(_.isTracked)) def augmentResult(tp: Type): Type = tp match case tp: MethodOrPoly => tp.derivedLambdaType(resType = augmentResult(tp.resType)) @@ -99,7 +98,7 @@ object Synthetics: info.derivedLambdaType(resType = transformDefaultGetterCaptures(info.resType, owner, idx)) case info: ExprType => info.derivedExprType(transformDefaultGetterCaptures(info.resType, owner, idx)) - case EventuallyCapturingType(parent, _) => + case CapturingType(parent, _) => transformDefaultGetterCaptures(parent, owner, idx) case info @ AnnotatedType(parent, annot) => info.derivedAnnotatedType(transformDefaultGetterCaptures(parent, owner, idx), annot) @@ -129,17 +128,17 @@ object Synthetics: case info: PolyType => info.derivedLambdaType(resType = transformUnapplyCaptures(info.resType)) - def transformComposeCaptures(symd: SymDenotation) = - val (pt: PolyType) = symd.info: @unchecked + def transformComposeCaptures(info: Type, owner: Symbol) = + val (pt: PolyType) = info: @unchecked val (mt: MethodType) = pt.resType: @unchecked - val (enclThis: ThisType) = symd.owner.thisType: @unchecked + val (enclThis: ThisType) = owner.thisType: @unchecked pt.derivedLambdaType(resType = MethodType(mt.paramNames)( mt1 => mt.paramInfos.map(_.capturing(CaptureSet.universal)), mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head)))) - def transformCurriedTupledCaptures(symd: SymDenotation) = - val (et: ExprType) = symd.info: @unchecked - val (enclThis: ThisType) = symd.owner.thisType: @unchecked + def transformCurriedTupledCaptures(info: Type, owner: Symbol) = + val (et: ExprType) = info: @unchecked + val (enclThis: ThisType) = owner.thisType: @unchecked def mapFinalResult(tp: Type, f: Type => Type): Type = val defn.FunctionOf(args, res, isContextual) = tp: @unchecked if defn.isFunctionNType(res) then @@ -151,17 +150,17 @@ object Synthetics: def transformCompareCaptures = MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType) - sym.copySymDenotation(info = sym.name match + symd.copySymDenotation(info = symd.name match case DefaultGetterName(nme.copy, n) => - transformDefaultGetterCaptures(sym.info, sym.owner, n) + transformDefaultGetterCaptures(info, symd.owner, n) case nme.unapply => - transformUnapplyCaptures(sym.info) + transformUnapplyCaptures(info) case nme.apply | nme.copy => - addCaptureDeps(sym.info) + addCaptureDeps(info) case nme.andThen | nme.compose => - transformComposeCaptures(sym) + transformComposeCaptures(info, symd.owner) case nme.curried | nme.tupled => - transformCurriedTupledCaptures(sym) + transformCurriedTupledCaptures(info, symd.owner) case n if n == nme.eq || n == nme.ne => transformCompareCaptures) end transform diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 63d616e1ce3d..81fd60497025 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -1,4 +1,6 @@ -package dotty.tools.dotc.config +package dotty.tools.dotc +package config +import core.Contexts.{Context, ctx} object Printers { @@ -12,7 +14,18 @@ object Printers { val default = new Printer - val capt = noPrinter + /** Enabled via Ycc-log flag. This is not super-efficient but helps debug + * variants of capture checking faster. + * TODO: Revert to static scheme once capture checking has stabilized + */ + def capt(using Context): Printer = + if ctx.settings.YccLog.value then captActive else noPrinter + val captActive = new Printer + + def captDebug(using Context): Printer = + if ctx.settings.YccDebug.value then captDebugActive else noPrinter + val captDebugActive = new Printer + val constr = noPrinter val core = noPrinter val checks = noPrinter diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 925be029746e..9f75f6afa028 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -414,6 +414,9 @@ private sealed trait YSettings: val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation.") val YrecheckTest: Setting[Boolean] = BooleanSetting("-Yrecheck-test", "Run basic rechecking (internal test only).") val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references.") + val YccNew: Setting[Boolean] = BooleanSetting("-Ycc-new", "Used in conjunction with captureChecking language import, try out new variants (debug option)") + val YccLog: Setting[Boolean] = BooleanSetting("-Ycc-log", "Used in conjunction with captureChecking language import, print tracing and debug info") + val YccPrintSetup: Setting[Boolean] = BooleanSetting("-Ycc-print-setup", "Used in conjunction with captureChecking language import, print trees after cc.Setup phase") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index c7ceada9884b..bbe46c344890 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -54,7 +54,7 @@ trait ConstraintHandling { protected var homogenizeArgs: Boolean = false /** We are currently comparing type lambdas. Used as a flag for - * optimization: when `false`, no need to do an expensive `pruneLambdaParams` + * optimization: when `false`, no need to do an expensive `avoidLambdaParams` */ protected var comparedTypeLambdas: Set[TypeLambda] = Set.empty diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index edd054375b05..d2c141d72f47 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -16,7 +16,8 @@ import Comments.Comment import util.Spans.NoSpan import config.Feature import Symbols.requiredModuleRef -import cc.{CapturingType, CaptureSet, EventuallyCapturingType} +import cc.{CaptureSet, RetainingType} +import ast.tpd.ref import scala.annotation.tailrec @@ -121,8 +122,8 @@ class Definitions { denot.info = TypeAlias( HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)( tl => List.fill(arity + 1)(TypeBounds.empty), - tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs), - CaptureSet.universal) + tl => RetainingType(underlyingClass.typeRef.appliedTo(tl.paramRefs), + ref(captureRoot.termRef) :: Nil) )) else val cls = denot.asClass.classSymbol @@ -982,6 +983,8 @@ class Definitions { @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 expandedUniversalSet: CaptureSet = CaptureSet(captureRoot.termRef) @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") @@ -1032,6 +1035,7 @@ 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") @@ -1249,8 +1253,8 @@ class Definitions { */ object ByNameFunction: def apply(tp: Type)(using Context): Type = tp match - case tp @ EventuallyCapturingType(tp1, refs) if tp.annot.symbol == RetainsByNameAnnot => - CapturingType(apply(tp1), refs) + case tp @ RetainingType(tp1, refs) if tp.annot.symbol == RetainsByNameAnnot => + RetainingType(apply(tp1), refs) case _ => defn.ContextFunction0.typeRef.appliedTo(tp :: Nil) def unapply(tp: Type)(using Context): Option[Type] = tp match diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 4b06140f75c2..fa57c503d61b 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -405,7 +405,7 @@ object Flags { val (_, _, ChildrenQueried @ _) = newFlags(56, "") /** A module variable (Scala 2.x only) / a capture-checked class - * (re-used as a flag for private parameter accessors in Recheck) + * (Scala2ModuleVar is re-used as a flag for private parameter accessors in Recheck) */ val (_, Scala2ModuleVar @ _, CaptureChecked @ _) = newFlags(57, "/") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b18c6993303f..38de11915095 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -24,7 +24,7 @@ import config.Config import reporting._ import collection.mutable import transform.TypeUtils._ -import cc.{CapturingType, derivedCapturingType, Setup, EventuallyCapturingType, isEventuallyCapturingType} +import cc.{CapturingType, derivedCapturingType} import scala.annotation.internal.sharable diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 9155af668c7a..1cd1a3ad4d39 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -12,7 +12,6 @@ import Names._ import StdNames.nme import Flags.{Module, Provisional} import dotty.tools.dotc.config.Config -import cc.boxedUnlessFun import dotty.tools.dotc.transform.TypeUtils.isErasedValueType object TypeApplications { @@ -354,7 +353,7 @@ class TypeApplications(val self: Type) extends AnyVal { } if ((dealiased eq stripped) || followAlias) try - val instantiated = dealiased.instantiate(args.mapConserve(_.boxedUnlessFun(self))) + val instantiated = dealiased.instantiate(args) if (followAlias) instantiated.normalized else instantiated catch case ex: IndexOutOfBoundsException => @@ -502,7 +501,7 @@ class TypeApplications(val self: Type) extends AnyVal { * Existential types in arguments are returned as TypeBounds instances. */ final def argInfos(using Context): List[Type] = self.stripped match - case AppliedType(tycon, args) => args.boxedUnlessFun(tycon) + case AppliedType(tycon, args) => args case _ => Nil /** If this is an encoding of a function type, return its arguments, otherwise return Nil. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8df809dc9ee6..126e1da0a626 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, mapRoots, localRoot} +import cc.* import NameKinds.WildcardParamName /** Provides methods to compare types. @@ -256,7 +256,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling report.log(explained(_.isSubType(tp1, tp2, approx))) } // Eliminate LazyRefs before checking whether we have seen a type before - val normalize = new TypeMap { + val normalize = new TypeMap with CaptureSet.IdempotentCaptRefMap { val DerefLimit = 10 var derefCount = 0 def apply(t: Type) = t match { @@ -538,12 +538,19 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling res - case CapturingType(parent1, refs1) => - if tp2.isAny then true - else if subCaptures(refs1, tp2.captureSet, frozenConstraint).isOK && sameBoxed(tp1, tp2, refs1) - || !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure - then recur(parent1, tp2) - else thirdTry + case tp1 @ CapturingType(parent1, refs1) => + def compareCapturing = + if tp2.isAny then true + else if subCaptures(refs1, tp2.captureSet, frozenConstraint).isOK && sameBoxed(tp1, tp2, refs1) + || !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure + then + val tp2a = + if tp1.isBoxedCapturing && !parent1.isBoxedCapturing + then tp2.unboxed + else tp2 + recur(parent1, tp2a) + else thirdTry + compareCapturing case tp1: AnnotatedType if !tp1.isRefining => recur(tp1.parent, tp2) case tp1: MatchType => @@ -574,7 +581,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || narrowGADTBounds(tp2, tp1, approx, isUpper = false)) && (isBottom(tp1) || GADTusage(tp2.symbol)) - isSubApproxHi(tp1, info2.lo.boxedIfTypeParam(tp2.symbol)) && (trustBounds || isSubApproxHi(tp1, info2.hi)) + isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) || compareGADT || tryLiftedToThis2 || fourthTry @@ -641,7 +648,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def compareRefined: Boolean = val tp1w = tp1.widen - if ctx.phase == Phases.checkCapturesPhase then + if isCaptureCheckingOrSetup then // A relaxed version of subtyping for dependent functions where method types // are treated as contravariant. @@ -655,10 +662,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case (info1: MethodType, info2: MethodType) => matchingMethodParams(info1, info2, precise = false) && isSubInfo(info1.resultType, info2.resultType.subst(info2, info1)) - case (info1 @ CapturingType(parent1, refs1), info2: Type) => + case (info1 @ CapturingType(parent1, refs1), info2: Type) + if info2.stripCapturing.isInstanceOf[MethodOrPoly] => subCaptures(refs1, info2.captureSet, frozenConstraint).isOK && sameBoxed(info1, info2, refs1) && isSubInfo(parent1, info2) - case (info1: Type, CapturingType(parent2, refs2)) => + case (info1: Type, CapturingType(parent2, refs2)) + if info1.stripCapturing.isInstanceOf[MethodOrPoly] => val refs1 = info1.captureSet (refs1.isAlwaysEmpty || subCaptures(refs1, refs2, frozenConstraint).isOK) && sameBoxed(info1, info2, refs1) && isSubInfo(info1, parent2) @@ -673,18 +682,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling 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) @@ -908,7 +905,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling canWidenAbstract && acc(true, tp) def tryBaseType(cls2: Symbol) = - val base = nonExprBaseType(tp1, cls2).boxedIfTypeParam(tp1.typeSymbol) + val base = nonExprBaseType(tp1, cls2) if base.exists && (base ne tp1) && (!caseLambda.exists || widenAbstractOKFor(tp2) @@ -942,7 +939,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && (tp2.isAny || GADTusage(tp1.symbol)) (!caseLambda.exists || widenAbstractOKFor(tp2)) - && isSubType(hi1.boxedIfTypeParam(tp1.symbol), tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow)) + && isSubType(hi1, tp2, approx.addLow) && (trustBounds || isSubType(lo1, tp2, approx.addLow)) || compareGADT || tryLiftedToThis1 case _ => @@ -995,7 +992,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def tp1widened = val tp1w = tp1.underlying.widenExpr tp1 match - case tp1: CaptureRef if tp1.isTracked => + case tp1: CaptureRef if isCaptureCheckingOrSetup && tp1.isTracked => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w @@ -1792,7 +1789,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling else if v > 0 then isSubType(arg1, arg2) else isSameType(arg2, arg1) - isSubArg(args1.head.boxedUnlessFun(tp1), args2.head.boxedUnlessFun(tp1)) + isSubArg(args1.head, args2.head) } && recurArgs(args1.tail, args2.tail, tparams2.tail) recurArgs(args1, args2, tparams2) @@ -2096,15 +2093,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling 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)) @@ -2220,7 +2208,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val paramsMatch = if precise then isSameTypeWhenFrozen(formal1, formal2a) - else if ctx.phase == Phases.checkCapturesPhase then + else if isCaptureCheckingOrSetup then // allow to constrain capture set variables isSubType(formal2a, formal1) else @@ -2372,7 +2360,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** The least upper bound of two types * @param canConstrain If true, new constraints might be added to simplify the lub. * @param isSoft If the lub is a union, this determines whether it's a soft union. - * @note We do not admit singleton types in or-types as lubs. */ def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false, isSoft: Boolean = true): Type = /*>|>*/ trace(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain, isSoft=$isSoft)", subtyping, show = true) /*<|<*/ { if (tp1 eq tp2) tp1 diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 2b4dc05c1a16..1166e287ed27 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -99,7 +99,8 @@ object TypeOps: tp match { case tp: NamedType => val sym = tp.symbol - if (sym.isStatic && !sym.maybeOwner.seesOpaques || (tp.prefix `eq` NoPrefix)) tp + if sym.isStatic && !sym.maybeOwner.seesOpaques || (tp.prefix `eq` NoPrefix) + then tp else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix))) case tp: LambdaType => mapOverLambda(tp) // special cased common case diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0f1aed020c6d..7957b979d7d1 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, ccNestingLevel} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, isCaptureChecking} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -834,26 +834,19 @@ object Types { pinfo recoverable_& rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } - 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 + 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 } def goApplied(tp: AppliedType, tycon: HKTypeLambda) = @@ -1456,12 +1449,12 @@ object Types { if (tp1.exists) tp1.dealias1(keep, keepOpaques) else tp case tp: AnnotatedType => val parent1 = tp.parent.dealias1(keep, keepOpaques) - tp match + if keep(tp) then tp.derivedAnnotatedType(parent1, tp.annot) + else tp match case tp @ CapturingType(parent, refs) => tp.derivedCapturingType(parent1, refs) case _ => - if keep(tp) then tp.derivedAnnotatedType(parent1, tp.annot) - else parent1 + parent1 case tp: LazyRef => tp.ref.dealias1(keep, keepOpaques) case _ => this @@ -2178,24 +2171,22 @@ object Types { /** Is the reference tracked? This is true if it can be tracked and the capture * set of the underlying type is not always empty. */ - final def isTracked(using Context): Boolean = isTrackableRef && !captureSetOfInfo.isAlwaysEmpty + final def isTracked(using Context): Boolean = + isTrackableRef && (isRootCapability || !captureSetOfInfo.isAlwaysEmpty) /** Is this reference the generic root capability `cap` ? */ - def isGenericRootCapability(using Context): Boolean = false + def isUniversalRootCapability(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 + def isLocalRootCapability(using Context): Boolean = this match + case tp: TermRef => tp.localRootOwner.exists + case _ => false /** Is this reference the a (local or generic) root capability? */ def isRootCapability(using Context): Boolean = - isGenericRootCapability || isLocalRootCapability + isUniversalRootCapability || isLocalRootCapability /** Normalize reference so that it can be compared with `eq` for equality */ def normalizedRef(using Context): CaptureRef = this @@ -2213,7 +2204,7 @@ object Types { else myCaptureSet = CaptureSet.Pending val computed = CaptureSet.ofInfo(this) - if ctx.phase != Phases.checkCapturesPhase || underlying.isProvisional then + if !isCaptureChecking || underlying.isProvisional then myCaptureSet = null else myCaptureSet = computed @@ -2714,7 +2705,7 @@ object Types { if (tparams.head.eq(tparam)) return args.head match { case _: TypeBounds if !widenAbstract => TypeRef(pre, tparam) - case arg => arg.boxedUnlessFun(tycon) + case arg => arg } tparams = tparams.tail args = args.tail @@ -2929,17 +2920,14 @@ object Types { || isRootCapability ) && !symbol.isOneOf(UnstableValueFlags) - override def isGenericRootCapability(using Context): Boolean = + override def isUniversalRootCapability(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 + def localRootOwner(using Context): Symbol = + // TODO Try to make local class roots be NonMembers owned directly by the class + val owner = symbol.maybeOwner + def normOwner = if owner.isLocalDummy then owner.owner else owner + if name == nme.LOCAL_CAPTURE_ROOT then normOwner else NoSymbol override def normalizedRef(using Context): CaptureRef = if isTrackableRef then symbol.termRef else this @@ -4090,10 +4078,15 @@ 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(tl.integrate(params, param.paramInfo))), + tl => params.map(param => toPInfo(addSealed(param, tl.integrate(params, param.paramInfo)))), tl => tl.integrate(params, resultType)) } @@ -4415,6 +4408,16 @@ 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)) } @@ -5124,8 +5127,8 @@ object Types { else if (clsd.is(Module)) givenSelf else if (ctx.erasedTypes) appliedRef else givenSelf.dealiasKeepAnnots match - case givenSelf1 @ EventuallyCapturingType(tp, _) => - givenSelf1.derivedAnnotatedType(tp & appliedRef, givenSelf1.annot) + case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol == defn.RetainsAnnot => + givenSelf1.derivedAnnotatedType(tp & appliedRef, ann) case _ => AndType(givenSelf, appliedRef) } @@ -5966,17 +5969,16 @@ object Types { } /** A type map that maps also parents and self type of a ClassInfo */ - abstract class DeepTypeMap(using Context) extends TypeMap { - override def mapClassInfo(tp: ClassInfo): ClassInfo = { + abstract class DeepTypeMap(using Context) extends TypeMap: + override def mapClassInfo(tp: ClassInfo): ClassInfo = val prefix1 = this(tp.prefix) - val parents1 = tp.declaredParents mapConserve this - val selfInfo1: TypeOrSymbol = tp.selfInfo match { - case selfInfo: Type => this(selfInfo) - case selfInfo => selfInfo - } - tp.derivedClassInfo(prefix1, parents1, tp.decls, selfInfo1) - } - } + inContext(ctx.withOwner(tp.cls)): + val parents1 = tp.declaredParents.mapConserve(this) + val selfInfo1: TypeOrSymbol = tp.selfInfo match + case selfInfo: Type => inContext(ctx.withOwner(tp.cls))(this(selfInfo)) + case selfInfo => selfInfo + tp.derivedClassInfo(prefix1, parents1, tp.decls, selfInfo1) + end DeepTypeMap @sharable object IdentityTypeMap extends TypeMap()(NoContext) { def apply(tp: Type): Type = tp diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7fa335afbf44..7c0f1ac5518b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -654,6 +654,7 @@ class TreeUnpickler(reader: TastyReader, if (isClass) { if sym.owner.is(Package) then if annots.exists(_.hasSymbol(defn.CaptureCheckedAnnot)) then + sym.setFlag(CaptureChecked) withCaptureChecks = true withPureFuns = true else if annots.exists(_.hasSymbol(defn.WithPureFunsAnnot)) then diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 05e5c34b5a0f..191dbba1b4ab 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3194,7 +3194,9 @@ object Parsers { * id [HkTypeParamClause] TypeParamBounds * * DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ - * DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds + * DefTypeParam ::= {Annotation} + * [`sealed`] -- under captureChecking + * id [HkTypeParamClause] TypeParamBounds * * TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ * TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds @@ -3204,24 +3206,24 @@ object Parsers { */ def typeParamClause(ownerKind: ParamOwner): List[TypeDef] = inBrackets { - 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 checkVarianceOK(): Boolean = + val ok = ownerKind != ParamOwner.Def && ownerKind != ParamOwner.TypeParam + if !ok then syntaxError(em"no `+/-` variance annotation allowed here") + in.nextToken() + ok def typeParam(): TypeDef = { val isAbstractOwner = ownerKind == ParamOwner.Type || ownerKind == ParamOwner.TypeParam val start = in.offset - 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) + var mods = annotsAsMods() | Param + if ownerKind == ParamOwner.Class then mods |= PrivateLocal + if Feature.ccEnabled && in.token == SEALED then + mods |= Sealed + in.nextToken() + if isIdent(nme.raw.PLUS) && checkVarianceOK() then + mods |= Covariant + else if isIdent(nme.raw.MINUS) && checkVarianceOK() then + mods |= Contravariant 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 83ff03e05592..967379b62f65 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, CaptureRoot, isBoxed, ccNestingLevel, levelOwner} +import cc.{CapturingType, RetainingType, CaptureSet, isBoxed, levelOwner, retainedElems} class PlainPrinter(_ctx: Context) extends Printer { @@ -48,11 +48,6 @@ 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 { @@ -161,8 +156,20 @@ class PlainPrinter(_ctx: Context) extends Printer { val core: Text = if !cs.isConst && cs.elems.isEmpty then "?" else "{" ~ Text(cs.elems.toList.map(toTextCaptureRef), ", ") ~ "}" + // ~ Str("?").provided(!cs.isConst) core ~ cs.optionalInfo + private def toTextRetainedElem[T <: Untyped](ref: Tree[T]): Text = ref match + case ref: RefTree[_] if ref.typeOpt.exists => + toTextCaptureRef(ref.typeOpt) + case Apply(fn, Literal(str) :: Nil) if fn.symbol == defn.Caps_capIn => + s"cap[${str.stringValue}]" + case _ => + toText(ref) + + private def toTextRetainedElems[T <: Untyped](refs: List[Tree[T]]): Text = + "{" ~ Text(refs.map(ref => toTextRetainedElem(ref))) ~ "}" + /** Print capturing type, overridden in RefinedPrinter to account for * capturing function types. */ @@ -173,6 +180,11 @@ class PlainPrinter(_ctx: Context) extends Printer { final protected def rootSetText = Str("{cap}") // TODO Use disambiguation + // Lazy version of isRootCapability; used to not force completers when printing + private def isRootCap(tp: CaptureRef): Boolean = tp match + case tp: TermRef => tp.symbol.isCompleted && tp.isRootCapability + case _ => tp.isRootCapability + def toText(tp: Type): Text = controlled { homogenize(tp) match { case tp: TypeType => @@ -180,7 +192,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView // always print underlying when testing picklers - && !tp.isRootCapability + && !isRootCap(tp) || tp.symbol.is(Module) || tp.symbol.name == nme.IMPORT => toTextRef(tp) ~ ".type" @@ -230,25 +242,23 @@ class PlainPrinter(_ctx: Context) extends Printer { keywordStr(" match ") ~ "{" ~ casesText ~ "}" ~ (" <: " ~ toText(bound) provided !bound.isAny) }.close - case tp @ EventuallyCapturingType(parent, refs) => + case tp @ CapturingType(parent, refs) => val boxText: Text = Str("box ") provided tp.isBoxed //&& ctx.settings.YccDebug.value - val rootsInRefs = refs.elems.filter(_.isRootCapability).toList + val rootsInRefs = refs.elems.filter(isRootCap(_)).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 + tp.symbol == defn.captureRoot && (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 case _ => false val refsText = if showAsCap then rootSetText else toTextCaptureSet(refs) toTextCapturing(parent, refsText, boxText) + case tp @ RetainingType(parent, refs) => + val refsText = refs match + case ref :: Nil if ref.symbol == defn.captureRoot => rootSetText + case _ => toTextRetainedElems(refs) + toTextCapturing(parent, refsText, "") ~ Str("R").provided(printDebug) 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 case tp: ErrorType => @@ -271,8 +281,10 @@ class PlainPrinter(_ctx: Context) extends Printer { } case ExprType(restp) => def arrowText: Text = restp match - case ct @ EventuallyCapturingType(parent, refs) if ct.annot.symbol == defn.RetainsByNameAnnot => - if refs.isUniversal then Str("=>") else Str("->") ~ toTextCaptureSet(refs) + case AnnotatedType(parent, ann) if ann.symbol == defn.RetainsByNameAnnot => + val refs = ann.tree.retainedElems + if refs.exists(_.symbol == defn.captureRoot) then Str("=>") + else Str("->") ~ toTextRetainedElems(refs) case _ => if Feature.pureFunsEnabled then "->" else "=>" changePrec(GlobalPrec)(arrowText ~ " " ~ toText(restp)) @@ -349,10 +361,7 @@ class PlainPrinter(_ctx: Context) extends Printer { */ protected def idString(sym: Symbol): String = (if (showUniqueIds || Printer.debugPrintUnique) "#" + sym.id else "") + - (if showNestingLevel then - if ctx.phase == Phases.checkCapturesPhase then "%" + sym.ccNestingLevel - else "%" + sym.nestingLevel - else "") + (if showNestingLevel then "%" + sym.nestingLevel else "") def nameString(sym: Symbol): String = simpleNameString(sym) + idString(sym) // + "<" + (if (sym.exists) sym.owner else "") + ">" @@ -383,11 +392,7 @@ class PlainPrinter(_ctx: Context) extends Printer { tp match { case tp: TermRef => 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) + Str(s"cap[${nameString(tp.localRootOwner)}]") else toTextPrefixOf(tp) ~ selectionString(tp) case tp: ThisType => nameString(tp.cls) + ".this" @@ -407,15 +412,6 @@ 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/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 7465b5c60aa3..4f63bfb38691 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, CapturingType, toCaptureSet, IllegalCaptureRef, ccNestingLevelOpt} +import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef} class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -868,13 +868,10 @@ 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"%${nestingLevel(tp.symbol)}" + case tp: NamedType if !tp.symbol.isStatic => s"%${tp.symbol.nestingLevel}" 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/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 48dc7c818360..0e38e9c074cd 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -8,6 +8,7 @@ import NameKinds.DefaultGetterName import NullOpsDecorator._ import collection.immutable.BitSet import scala.annotation.tailrec +import cc.isCaptureChecking /** A module that can produce a kind of iterator (`Cursor`), * which yields all pairs of overriding/overridden symbols @@ -31,7 +32,7 @@ object OverridingPairs: */ protected def exclude(sym: Symbol): Boolean = !sym.memberCanMatchInheritedSymbols - || ctx.phase == Phases.checkCapturesPhase && sym.is(Recheck.ResetPrivate) + || isCaptureChecking && sym.is(Recheck.ResetPrivate) /** The parents of base that are checked when deciding whether an overriding * pair has already been treated in a parent class. diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 306ca2b0eb9c..9833b3cf177f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -46,27 +46,23 @@ object Recheck: case Some(tpe) => tree1.withType(tpe) case None => tree1 - extension (sym: Symbol) + extension (sym: Symbol)(using Context) /** Update symbol's info to newInfo after `prevPhase`. * Also update owner to newOwnerOrNull if it is not null. * The update is valid until after Recheck. After that the symbol's denotation * is reset to what it was before PreRecheck. */ - def updateInfo(prevPhase: 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( - owner = newOwner, - info = newInfo, - initFlags = if newInfo.isInstanceOf[LazyType] then flags &~ Touched else flags - ).installAfter(prevPhase) + def updateInfo(prevPhase: DenotTransformer, newInfo: Type, newFlags: FlagSet = sym.flags, newOwner: Symbol = sym.owner): Unit = + if (sym.info ne newInfo) || sym.flags != newFlags || (sym.maybeOwner ne newOwner) then + val flags = if newInfo.isInstanceOf[LazyType] then newFlags &~ Touched else newFlags + sym.copySymDenotation(owner = newOwner, info = newInfo, initFlags = flags) + .installAfter(prevPhase) /** Does symbol have a new denotation valid from phase.next that is different * from the denotation it had before? */ - def isUpdatedAfter(phase: Phase)(using Context) = + def isUpdatedAfter(phase: Phase) = val symd = sym.denot symd.validFor.firstPhaseId == phase.id + 1 && (sym.originDenotation ne symd) @@ -142,19 +138,28 @@ abstract class Recheck extends Phase, SymTransformer: import ast.tpd.* import Recheck.* + /** The phase before rechecking, used to setup symbol infos. */ def preRecheckPhase = this.prev.asInstanceOf[PreRecheck] + /** The first phase that pepares for rechecking. This is usually preRecheckPhase + * but could also be before. Updated symbols will snap back to their + * denotations at firestPrepPhase after rechecking. + */ + def firstPrepPhase: Phase = preRecheckPhase + override def changesBaseTypes: Boolean = true override def isCheckable = false // TODO: investigate what goes wrong we Ycheck directly after rechecking. // One failing test is pos/i583a.scala - /** Change any `ResetPrivate` flags back to `Private` */ + /** Change denotation back to what it was before (pre-)rechecking` */ def transformSym(symd: SymDenotation)(using Context): SymDenotation = val sym = symd.symbol - if sym.isUpdatedAfter(preRecheckPhase) - then atPhase(preRecheckPhase)(sym.denot.copySymDenotation()) + def updatedAfter(p: Phase): Boolean = + sym.isUpdatedAfter(p) || p != preRecheckPhase && updatedAfter(p.next) + if updatedAfter(firstPrepPhase) + then atPhase(firstPrepPhase)(sym.denot.copySymDenotation()) else symd def run(using Context): Unit = @@ -202,7 +207,7 @@ abstract class Recheck extends Phase, SymTransformer: val tree2 = ConstFold(tree1) if tree2 ne tree1 then tree2.tpe else tp - def recheckIdent(tree: Ident)(using Context): Type = + def recheckIdent(tree: Ident, pt: Type)(using Context): Type = tree.tpe def recheckSelect(tree: Select, pt: Type)(using Context): Type = @@ -250,13 +255,17 @@ abstract class Recheck extends Phase, SymTransformer: val exprType = recheck(expr, defn.UnitType) bindType.symbol.info - def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit = - if !tree.rhs.isEmpty then recheck(tree.rhs, sym.info) + def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = + val resType = recheck(tree.tpt) + if tree.rhs.isEmpty then resType + else recheck(tree.rhs, resType) - def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Unit = - val rhsCtx = linkConstructorParams(sym).withOwner(sym) - if !tree.rhs.isEmpty && !sym.isInlineMethod && !sym.isEffectivelyErased then - inContext(rhsCtx) { recheck(tree.rhs, recheck(tree.tpt)) } + def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = + inContext(linkConstructorParams(sym).withOwner(sym)): + val resType = recheck(tree.tpt) + if tree.rhs.isEmpty || sym.isInlineMethod || sym.isEffectivelyErased + then resType + else recheck(tree.rhs, resType) def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type = recheck(tree.rhs) @@ -286,6 +295,7 @@ abstract class Recheck extends Phase, SymTransformer: protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = mt.instantiate(argTypes) + /** A hook to massage the type of an applied method; currently not overridden */ protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe def recheckApply(tree: Apply, pt: Type)(using Context): Type = @@ -330,7 +340,7 @@ abstract class Recheck extends Phase, SymTransformer: tptType def recheckAssign(tree: Assign)(using Context): Type = - val lhsType = recheck(tree.lhs) + val lhsType = recheck(tree.lhs, LhsProto) recheck(tree.rhs, lhsType.widen) defn.UnitType @@ -425,7 +435,7 @@ abstract class Recheck extends Phase, SymTransformer: seqLitType(tree, TypeComparer.lub(declaredElemType :: elemTypes)) def recheckTypeTree(tree: TypeTree)(using Context): Type = - knownType(tree) // allows to install new types at Setup + tree.knownType // allows to install new types at Setup def recheckAnnotated(tree: Annotated)(using Context): Type = tree.tpe match @@ -451,7 +461,7 @@ abstract class Recheck extends Phase, SymTransformer: case _ => traverse(stats) - def recheckDef(tree: ValOrDefDef, sym: Symbol)(using Context): Unit = + def recheckDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = inContext(ctx.localContext(tree, sym)) { tree match case tree: ValDef => recheckValDef(tree, sym) @@ -467,7 +477,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckNamed(tree: NameTree, pt: Type)(using Context): Type = val sym = tree.symbol tree match - case tree: Ident => recheckIdent(tree) + case tree: Ident => recheckIdent(tree, pt) case tree: Select => recheckSelect(tree, pt) case tree: Bind => recheckBind(tree, pt) case tree: ValOrDefDef => @@ -521,17 +531,15 @@ abstract class Recheck extends Phase, SymTransformer: * @param pt the expected type */ def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = - checkConforms(tpe, pt, tree) - if keepType(tree) then tree.rememberType(tpe) - tpe + val tpe1 = checkConforms(tpe, pt, tree) + if keepType(tree) then tree.rememberType(tpe1) + tpe1 def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = - trace(i"rechecking $tree with pt = $pt", recheckr, show = true) { - try recheckFinish(recheckStart(tree, pt), tree, pt) - catch case ex: Exception => - println(i"error while rechecking $tree") - throw ex - } + try recheckFinish(recheckStart(tree, pt), tree, pt) + catch case ex: Exception => + println(i"error while rechecking $tree") + throw ex /** Typing and previous transforms sometimes leaves skolem types in prefixes of * NamedTypes in `expected` that do not match the `actual` Type. -Ycheck does @@ -560,10 +568,9 @@ abstract class Recheck extends Phase, SymTransformer: private val debugSuccesses = false /** 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 => - case _ => - checkConformsExpr(tpe.widenExpr, pt.widenExpr, tree) + def checkConforms(tpe: Type, pt: Type, tree: Tree)(using Context): Type = tree match + case _: DefTree | EmptyTree | _: TypeTree => tpe + case _ => checkConformsExpr(tpe.widenExpr, pt.widenExpr, tree) def isCompatible(actual: Type, expected: Type)(using Context): Boolean = actual <:< expected @@ -575,17 +582,12 @@ abstract class Recheck extends Phase, SymTransformer: (widened ne expected) && isCompatible(actual, widened) } - def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Unit = + def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Type = //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, addenda) - else if debugSuccesses then - tree match - case _: Ident => - println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}") - case _ => - end checkConformsExpr + actual def checkUnit(unit: CompilationUnit)(using Context): Unit = recheck(unit.tpdTree) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c98be88487f5..e9265031221b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -39,6 +39,7 @@ import config.Feature.sourceVersion import config.SourceVersion._ import printing.Formatting.hlAsKeyword import transform.TypeUtils.* +import cc.isCaptureChecking import collection.mutable import reporting._ @@ -67,7 +68,7 @@ object Checking { */ def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type, app: Type = NoType, tpt: Tree = EmptyTree)(using Context): Unit = - if ctx.phase != Phases.checkCapturesPhase then + if !isCaptureChecking then args.lazyZip(boundss).foreach { (arg, bound) => if !bound.isLambdaSub && !arg.tpe.hasSimpleKind then errorTree(arg, @@ -152,7 +153,7 @@ object Checking { // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. // Do check all bounds in Scala units and those bounds in Java units that // occur in applications of Scala type constructors. - && !(ctx.phase == Phases.checkCapturesPhase && !tycon.typeSymbol.is(CaptureChecked)) + && !isCaptureChecking || tycon.typeSymbol.is(CaptureChecked) // Don't check bounds when capture checking type constructors that were not // themselves capture checked. Since the type constructor could not foresee // possible capture sets, it's better to be lenient for backwards compatibility. @@ -517,7 +518,12 @@ 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 - fail(em"only classes can be ${(sym.flags & ClassOnlyFlags).flagsString}") + val illegal = sym.flags & ClassOnlyFlags + if sym.is(TypeParam) + && illegal == Sealed + && Feature.ccEnabled && cc.ccConfig.allowUniversalInBoxed + then () // OK + else fail(em"only classes can be ${illegal.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/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index a21a94aab271..779485936b3b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -14,6 +14,7 @@ import Implicits.{hasExtMethod, Candidate} import java.util.{Timer, TimerTask} import collection.mutable import scala.util.control.NonFatal +import cc.isCaptureChecking /** This trait defines the method `importSuggestionAddendum` that adds an addendum * to error messages suggesting additional imports. @@ -319,7 +320,7 @@ trait ImportSuggestions: * If there's nothing to suggest, an empty string is returned. */ override def importSuggestionAddendum(pt: Type)(using Context): String = - if ctx.phase == Phases.checkCapturesPhase then + if isCaptureChecking then return "" // it's too late then to look for implicits val (fullMatches, headMatches) = importSuggestions(pt)(using ctx.fresh.setExploreTyperState()) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 051c75522003..4086ccac1851 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -968,7 +968,7 @@ object ProtoTypes { final def wildApprox(tp: Type)(using Context): Type = wildApprox(tp, null, Set.empty, Set.empty) - @sharable object AssignProto extends UncachedGroundType with MatchAlways + @sharable object LhsProto extends UncachedGroundType with MatchAlways private[ProtoTypes] class WildApproxMap(val seen: Set[TypeParamRef], val internal: Set[TypeLambda])(using Context) extends TypeMap { def apply(tp: Type): Type = wildApprox(tp, this, seen, internal) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 061d759e9ca4..0b281e18f482 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -20,7 +20,6 @@ import config.SourceVersion.{`3.0`, `future`} import config.Printers.refcheck import reporting._ import Constants.Constant -import cc.{mapRoots, localRoot} object RefChecks { import tpd._ @@ -105,9 +104,6 @@ object RefChecks { def checkSelfConforms(other: ClassSymbol) = 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), @@ -223,36 +219,39 @@ object RefChecks { false precedesIn(parent.asClass.baseClasses) - // We can exclude pairs safely from checking only under three additional conditions - // - their signatures also match in the parent class. - // See neg/i12828.scala for an example where this matters. - // - They overriding/overridden appear in linearization order. - // See neg/i5094.scala for an example where this matters. - // - The overridden symbol is not `abstract override`. For such symbols - // we need a more extensive test since the virtual super chain depends - // on the precise linearization order, which might be different for the - // subclass. See neg/i14415.scala. + /** We can exclude pairs safely from checking only under three additional conditions + * - their signatures also match in the parent class. + * See neg/i12828.scala for an example where this matters. + * - They overriding/overridden appear in linearization order. + * See neg/i5094.scala for an example where this matters. + * - The overridden symbol is not `abstract override`. For such symbols + * we need a more extensive test since the virtual super chain depends + * on the precise linearization order, which might be different for the + * subclass. See neg/i14415.scala. + */ override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean = isOverridingPair(sym1, sym2, parent.thisType) .showing(i"already handled ${sym1.showLocated}: ${sym1.asSeenFrom(parent.thisType).signature}, ${sym2.showLocated}: ${sym2.asSeenFrom(parent.thisType).signature} = $result", refcheck) && inLinearizationOrder(sym1, sym2, parent) && !sym2.is(AbsOverride) - // Checks the subtype relationship tp1 <:< tp2. - // It is passed to the `checkOverride` operation in `checkAll`, to be used for - // compatibility checking. + /** Checks the subtype relationship tp1 <:< tp2. + * It is passed to the `checkOverride` operation in `checkAll`, to be used for + * compatibility checking. + */ def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2 - /** A hook that allows to adjust the type of `member` and `other` before checking conformance. + /** A hook that allows to omit override checks between `overriding` and `overridden`. * Overridden in capture checking to handle non-capture checked classes leniently. */ - def adjustInfo(tp: Type, member: Symbol)(using Context): Type = tp + def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = true private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = while hasNext do - checkOverride(subtypeChecker, overriding, overridden) + if needsCheck(overriding, overridden) then + checkOverride(subtypeChecker, overriding, overridden) next() // The OverridingPairs cursor does assume that concrete overrides abstract @@ -266,7 +265,7 @@ object RefChecks { if dcl.is(Deferred) then for other <- dcl.allOverriddenSymbols do if !other.is(Deferred) then - checkOverride(checkSubType, dcl, other) + checkOverride(subtypeChecker, dcl, other) end checkAll end OverridingPairsChecker @@ -371,8 +370,9 @@ object RefChecks { def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = def memberTp(self: Type) = if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) - else checker.adjustInfo(self.memberInfo(member), member) - def otherTp(self: Type) = checker.adjustInfo(self.memberInfo(other), other) + else self.memberInfo(member) + def otherTp(self: Type) = + self.memberInfo(other) refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bc41fe457ef6..da1d567810c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -534,7 +534,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer */ def toNotNullTermRef(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match case ref: TermRef - if pt != AssignProto && // Ensure it is not the lhs of Assign + if pt != LhsProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) && // If a reference is in the context, it is already trackable at the point we add it. // Hence, we don't use isTracked in the next line, because checking use out of order is enough. @@ -744,7 +744,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && selName.isTermName && !isDynamicExpansion(tree) then val tree2 = cpy.Select(tree0)(untpd.TypedSplice(qual), selName) - if pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto then + if pt.isInstanceOf[FunOrPolyProto] || pt == LhsProto then assignType(tree2, TryDynamicCallType) else typedDynamicSelect(tree2, Nil, pt) @@ -1090,8 +1090,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typed(appliedUpdate, pt) case lhs => val locked = ctx.typerState.ownedVars - val lhsCore = typedUnadapted(lhs, AssignProto, locked) - def lhs1 = adapt(lhsCore, AssignProto, locked) + val lhsCore = typedUnadapted(lhs, LhsProto, locked) + def lhs1 = adapt(lhsCore, LhsProto, locked) def reassignmentToVal = report.error(ReassignmentToVal(lhsCore.symbol.name), tree.srcPos) @@ -4028,7 +4028,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (implicitFun || caseCompanion) && !isApplyProto(pt) && pt != SingletonTypeProto - && pt != AssignProto + && pt != LhsProto && !ctx.mode.is(Mode.Pattern) && !tree.isInstanceOf[SplicePattern] && !ctx.isAfterTyper diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index a32d27a5c28b..20d1eb124d84 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -46,3 +46,9 @@ 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 159fc0691f42..923758d722f9 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 = ??? // error + val bar: T -> T = ??? 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 4b631472bad4..0052828dbabb 100644 --- a/tests/neg-custom-args/captures/box-adapt-boxing.scala +++ b/tests/neg-custom-args/captures/box-adapt-boxing.scala @@ -32,7 +32,7 @@ def main(io: Cap^, fs: Cap^): Unit = { type Id[X] = Box[X] -> Unit type Op[X] = Unit -> Box[X] val f: Unit -> (Cap^{io}) -> Unit = ??? - val g: Op[Id[Cap^{io}]^{fs}] = f // error + val g: Op[Id[Cap^{io}]^{fs}] = f val h: Op[Id[Cap^{io}]^{io}] = f } } diff --git a/tests/neg-custom-args/captures/box-adapt-contra.scala b/tests/neg-custom-args/captures/box-adapt-contra.scala new file mode 100644 index 000000000000..5541d0a5fdff --- /dev/null +++ b/tests/neg-custom-args/captures/box-adapt-contra.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking + +trait Cap + +def useCap[X](x: X): (X -> Unit) -> Unit = ??? + +def test1(c: Cap^): Unit = + val f: (Cap^{c} -> Unit) -> Unit = useCap[Cap^{c}](c) // error + +def test2(c: Cap^, d: Cap^): Unit = + def useCap1[X](x: X): (X => Unit) -> Unit = ??? + val f1: (Cap^{c} => Unit) ->{c} Unit = useCap1[Cap^{c}](c) // ok + + def useCap2[X](x: X): (X ->{c} Unit) -> Unit = ??? + val f2: (Cap^{c} -> Unit) ->{c} Unit = useCap2[Cap^{c}](c) // ok + + def useCap3[X](x: X): (X ->{d} Unit) -> Unit = ??? + val f3: (Cap^{c} -> Unit) ->{cap} Unit = useCap3[Cap^{c}](c) // error diff --git a/tests/neg-custom-args/captures/box-unsoundness.scala b/tests/neg-custom-args/captures/box-unsoundness.scala index e9436b7236cc..d1331f16df1f 100644 --- a/tests/neg-custom-args/captures/box-unsoundness.scala +++ b/tests/neg-custom-args/captures/box-unsoundness.scala @@ -1,6 +1,13 @@ -@annotation.capability class CanIO { def use(): Unit = () } +//@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 +def test(io: CanIO^): Unit = + val f: (CanIO^ => Unit) -> Unit = use[CanIO^](io) // error + val _: (CanIO^ => Unit) -> Unit = f + + val g1 = () => f(x => x.use()) + + val a1 = f(x => x.use()) + val a2 = () => f(x => x.use()) + val g2: () -> Unit = a2 + // was UNSOUND: g uses the capability io but has an empty capture set diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index 470e3caef967..3ebdeca84c9c 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, R <: Top](op: (lcap: caps.Cap) ?-> (CT[E] @retains(lcap)) => R)(handler: E => R): R = +def handle[E <: Exception, sealed R <: Top](op: (CT[E] @retains(caps.cap)) => 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] { - (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error + val b = handle[Exception, () => Nothing] { // error + (x: CanThrow[Exception]) => () => raise(new Exception)(using x) } { (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index fbf7d5f4d831..5e43a45b67f5 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 (cap : caps.Cap) is not included in the allowed capture set {} of pure base class class C + |reference (caps.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 522279339bef..8affe7005e2e 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,8 +1,8 @@ -- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ---------------------------------------------------------- 16 | def f = println(c) // error | ^ - | (c : Cap^{cap[test]}) cannot be referenced here; it is not included in the allowed capture set {} - | of the enclosing class A + | (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | of the enclosing class A -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 ------------------------------------- 21 | val x: A = this // error | ^^^^ diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index 382ee177aa91..91dfdf06d3cd 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- 6 | bar( () => f ) // error | ^ - | (f : () => Unit) cannot be referenced here; it is not included in the allowed capture set {} + | (f : Proc^) 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 4533cf2c083d..16d623e64f7c 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:2:6 ----------------------------------------------- 2 |class Err extends Exception: // error |^ - |reference (cap : caps.Cap) is not included in the allowed capture set {} of pure base class class Throwable + |reference (caps.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^{cap[test]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class class Throwable + |(c : Any^) 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index a7ef9d987b1d..c8280e2ff3b7 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,7 +5,7 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized + var file: File^{cap[Service]} = uninitialized def log = file.write("log") def withFile[T](op: (l: caps.Cap) ?-> (f: File^{l}) => T): T = diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index f72325a0be8a..8987614eec38 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -1,31 +1,31 @@ import language.experimental.captureChecking -trait Cap { def use(): Unit } +trait Capp { def use(): Unit } -def localCap[T](op: (lcap: caps.Cap) ?-> (c: Cap^{lcap}) => T): T = ??? +def localCap[sealed T](op: (c: Capp^{cap}) => T): T = ??? -def main(io: Cap^{cap}, net: Cap^{cap}): Unit = { +def main(io: Capp^{cap}, net: Capp^{cap}): Unit = { val test1 = localCap { c => // error () => { c.use() } } - val test2: (c: Cap^{cap}) -> () ->{cap} Unit = - localCap { c => // error, was: should work - (c1: Cap^{cap}) => () => { c1.use() } + val test2: (c: Capp^{cap}) -> () ->{cap} Unit = + localCap { c => // should work + (c1: Capp^{cap}) => () => { c1.use() } } - val test3: (c: Cap^{io}) -> () ->{io} Unit = + val test3: (c: Capp^{io}) -> () ->{io} Unit = localCap { c => // should work - (c1: Cap^{io}) => () => { c1.use() } + (c1: Capp^{io}) => () => { c1.use() } } - val test4: (c: Cap^{io}) -> () ->{net} Unit = + val test4: (c: Capp^{io}) -> () ->{net} Unit = localCap { c => // error - (c1: Cap^{io}) => () => { c1.use() } + (c1: Capp^{io}) => () => { c1.use() } } - def localCap2[T](op: (c: Cap^{io}) => T): T = ??? + def localCap2[sealed T](op: (c: Capp^{io}) => T): T = ??? val test5: () ->{io} Unit = localCap2 { c => // ok diff --git a/tests/neg-custom-args/captures/i15049.scala b/tests/neg-custom-args/captures/i15049.scala index e60367946377..b5a696729d18 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[T](f: (local: caps.Cap) ?-> (Session^{local}) => T): T = f(session) + def withSession[sealed T](f: Session^ => 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 765477df7466..df05324866e1 100644 --- a/tests/neg-custom-args/captures/i15116.check +++ b/tests/neg-custom-args/captures/i15116.check @@ -1,28 +1,60 @@ --- Error: tests/neg-custom-args/captures/i15116.scala:3:6 -------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:3:13 ---------------------------------------- 3 | val x = Foo(m) // error - | ^^^^^^^^^^^^^^ - | Non-local value x cannot have an inferred type - | Foo{val m: String^{Bar.this.m}}^{Bar.this.m} - | with non-empty capture set {Bar.this.m}. - | The type needs to be declared explicitly. --- Error: tests/neg-custom-args/captures/i15116.scala:5:6 -------------------------------------------------------------- + | ^^^^^^ + | Found: Foo{val m²: (Bar.this.m : String^)}^{Bar.this.m} + | Required: Foo + | + | where: m is a value in class Bar + | m² is a value in class Foo + | + | + | Note that the expected type Foo + | is the previously inferred type of value x + | which is also the type seen in separately compiled sources. + | The new inferred type Foo{val m: (Bar.this.m : String^)}^{Bar.this.m} + | must conform to this type. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:5:13 ---------------------------------------- 5 | val x = Foo(m) // error - | ^^^^^^^^^^^^^^ - | Non-local value x cannot have an inferred type - | 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 -------------------------------------------------------------- + | ^^^^^^ + | Found: Foo{val m: String^{Baz.this}}^{Baz.this} + | Required: Foo + | + | Note that the expected type Foo + | is the previously inferred type of value x + | which is also the type seen in separately compiled sources. + | The new inferred type Foo{val m: String^{Baz.this}}^{Baz.this} + | must conform to this type. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:7:13 ---------------------------------------- 7 | val x = Foo(m) // error - | ^^^^^^^^^^^^^^ - | Non-local value x cannot have an inferred type - | Foo{val m: String^{Bar1.this.m}}^{Bar1.this.m} - | with non-empty capture set {Bar1.this.m}. - | The type needs to be declared explicitly. --- Error: tests/neg-custom-args/captures/i15116.scala:9:6 -------------------------------------------------------------- + | ^^^^^^ + | Found: Foo{val m²: (Bar1.this.m : String^)}^{Bar1.this.m} + | Required: Foo + | + | where: m is a value in class Bar1 + | m² is a value in class Foo + | + | + | Note that the expected type Foo + | is the previously inferred type of value x + | which is also the type seen in separately compiled sources. + | The new inferred type Foo{val m: (Bar1.this.m : String^)}^{Bar1.this.m} + | must conform to this type. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:9:13 ---------------------------------------- 9 | val x = Foo(m) // error - | ^^^^^^^^^^^^^^ - | Non-local value x cannot have an inferred type - | Foo{val m: String^{Baz2.this}}^{Baz2.this} - | with non-empty capture set {Baz2.this}. - | The type needs to be declared explicitly. + | ^^^^^^ + | Found: Foo{val m: String^{Baz2.this}}^{Baz2.this} + | Required: Foo + | + | Note that the expected type Foo + | is the previously inferred type of value x + | which is also the type seen in separately compiled sources. + | The new inferred type Foo{val m: String^{Baz2.this}}^{Baz2.this} + | must conform to this type. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index cb6b40361add..39f3fff911c6 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -6,8 +6,8 @@ -- [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^{cap[C]}}^{c} ->{'cap[..main1](from instantiating box1), c} Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Found: (C{val arg: C^}^{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 ------------------------------------------------------------ @@ -18,28 +18,15 @@ -- [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^{cap[C]}}^{c} ->{'cap[..main2](from instantiating box2), c} Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Found: (C{val arg: C^}^{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:34 --------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error - | ^ - | 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 + | ^^^^^^^ + | Found: (C{val arg: C^}^ => Unit) ->? Unit + | Required: Observe[C]^ | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- @@ -48,7 +35,4 @@ | 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 7e62fdeffb24..a054eac835c1 100644 --- a/tests/neg-custom-args/captures/i15772.scala +++ b/tests/neg-custom-args/captures/i15772.scala @@ -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)) // error + boxed2((cap: C^) => unsafe(c)) 0 trait File: diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index 3bb276d1fc5c..ce7a1b3dbc2f 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -31,7 +31,7 @@ def main(fs: Cap^): Unit = { } val op4: Unit ->{} Unit = (x: Unit) => // o k - expect[Cap^](io) + expect[Cap^](io) // error val op: Unit -> Unit = (x: Unit) => expect[Cap^] { diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index aeb410f07d65..09352ec648ce 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]^{cap[LazyCons]}}^{cap1}) - | Required: lazylists.LazyList[Int] + | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{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]^{cap[tail]} has incompatible type + | method tail of type -> lazylists.LazyList[Nothing]^ 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 74318b6bb254..3095c1f2f4f9 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,13 +1,11 @@ --- [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 +-- 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 => 38 | if i > 9 then throw Ex1() 39 | i * i 40 | } - | - | longer explanation available when compiling with `-explain` +41 | catch case ex: Ex1 => LazyNil diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.scala b/tests/neg-custom-args/captures/lazylists-exceptions.scala index f70f66cd6950..295147f7f3c5 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 - tabulate(10) { i => // error + try // error + tabulate(10) { i => 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 5038ab1bea93..13b1da6eaf1c 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:18:4 ------------------------------------ 18 | final class Mapped extends LazyList[B]: // error | ^ - | Found: LazyList[B^?]^{f, xs} + | Found: LazyList[box B^?]^{f, xs} | Required: LazyList[B]^{f} 19 | this: (Mapped^{xs, f}) => 20 | def isEmpty = false @@ -13,7 +13,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:27:4 ------------------------------------ 27 | final class Mapped extends LazyList[B]: // error | ^ - | Found: LazyList[B^?]^{f, xs} + | Found: LazyList[box B^?]^{f, xs} | Required: LazyList[B]^{xs} 28 | this: Mapped^{xs, f} => 29 | def isEmpty = false @@ -25,15 +25,15 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(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 + |(f : A => 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 ->{cap[map3]} B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + |(f : A => 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 | ^ - | Found: LazyList[B^?]^{f, xs} + | Found: LazyList[box B^?]^{f, xs} | Required: LazyList[B]^{xs} 46 | this: (Mapped^{xs, f}) => 47 | def isEmpty = false diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 509069797def..c23d1516acf5 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,11 +1,11 @@ --- Error: tests/neg-custom-args/captures/leaked-curried.scala:13:20 ---------------------------------------------------- -13 | () => () => io // error +-- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- +14 | () => () => 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 + |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz +-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/leaked-curried.scala:15:10 ------------------------------- +15 | class Foo extends Box, Pure: // error | ^ - | error overriding value get in trait Box of type () -> () ->{cap[Box]} Cap^{cap[Box]}; - | value get of type () -> () ->{io} Cap^ has incompatible type + | illegal inheritance: self type Foo^{io} of class Foo does not conform to self type Pure + | of parent trait Pure | | 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 a566999d7c39..a7c48219b450 100644 --- a/tests/neg-custom-args/captures/leaked-curried.scala +++ b/tests/neg-custom-args/captures/leaked-curried.scala @@ -8,8 +8,12 @@ trait Box: def main(): Unit = val leaked = withCap: (io: Cap^) => - class Foo extends Box, Pure: - val get: () ->{} () ->{io} Cap^ = // error + class Fuzz extends Box, Pure: + self => + val get: () ->{} () ->{io} Cap^ = () => () => io // error + class Foo extends Box, Pure: // error + val get: () ->{} () ->{io} Cap^ = + () => () => io new Foo val bad = leaked.get()().use() // using a leaked capability diff --git a/tests/neg-custom-args/captures/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check new file mode 100644 index 000000000000..0481a9a4d9e2 --- /dev/null +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:2 -------------------------------------------------- +56 | usingLogFile: log => // error + | ^^^^^^^^^^^^ + | local reference log leaks into outer capture set of type parameter R of method usingLogFile diff --git a/tests/neg-custom-args/captures/leaking-iterators.scala b/tests/neg-custom-args/captures/leaking-iterators.scala new file mode 100644 index 000000000000..50447874a3c3 --- /dev/null +++ b/tests/neg-custom-args/captures/leaking-iterators.scala @@ -0,0 +1,60 @@ +package cctest +import java.io.* + +trait IterableOnce[+A]: + //this: IterableOnce[A]^ => + def iterator: Iterator[A]^{this} + +trait Iterable[+A] extends IterableOnce[A]: + //this: IterableOnce[A]^ => + def iterator: Iterator[A]^{this} + +trait List[+A] extends Iterable[A]: + def head: A + def tail: List[A] + def length: Int + def foldLeft[B](z: B)(op: (B, A) => B): B + def foldRight[B](z: B)(op: (A, B) => B): B + def foreach(f: A => Unit): Unit + def iterator: Iterator[A] + def map[B](f: A => B): List[B] + def flatMap[B](f: A => IterableOnce[B]^): List[B] + def ++[B >: A](xs: IterableOnce[B]^): List[B] +object List: + def apply[A](xs: A*): List[A] = ??? + +trait Iterator[+A] extends IterableOnce[A]: + this: Iterator[A]^ => + def hasNext: Boolean + def next(): A + def foldLeft[B](z: B)(op: (B, A) => B): B + def foldRight[B](z: B)(op: (A, B) => B): B + def foreach(f: A => Unit): Unit + + def map[B](f: A => B): Iterator[B]^{this, f} + def flatMap[B](f: A => IterableOnce[B]^): Iterator[B]^{this, f} + def ++[B >: A](xs: IterableOnce[B]^): Iterator[B]^{this, xs} +end Iterator + +private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head.iterator + +def usingLogFile[sealed R](op: FileOutputStream^ => R): R = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + +def test = + val xs = List(1, 2, 3) + + usingLogFile: log => + xs.map: x => + log.write(x) + x * x + + usingLogFile: log => // error + xs.iterator.map: x => + log.write(x) + x * x + diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 8d11c196b10f..f91f90fb652f 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,10 +1,22 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:15:11 --------------------------------------- -15 | r.setV(g) // error +-- Error: tests/neg-custom-args/captures/levels.scala:6:16 ------------------------------------------------------------- +6 | private var v: T = init // error + | ^ + | mutable variable v cannot have type T since + | that type refers to the type variable T, which is not sealed. +-- Error: tests/neg-custom-args/captures/levels.scala:17:13 ------------------------------------------------------------ +17 | val _ = Ref[String => String]((x: String) => x) // error + | ^^^^^^^^^^^^^^^^^^^^^ + | Sealed type variable T cannot be instantiated to box String => String since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of constructor Ref + | leaking as part of its result. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- +22 | r.setV(g) // error | ^ | 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 + | Note that reference (cap3 : CC^), defined in method scope + | cannot be included in outer capture set ? of value r which is associated with method test2 | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 35fb2d490398..f8bd6e77c449 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,33 +1,23 @@ class CC -def test(cap1: CC^) = +def test1(cap1: CC^) = class Ref[T](init: T): + private var v: T = init // error + def setV(x: T): Unit = v = x + def getV: T = v + +def test2(cap1: CC^) = + + class Ref[sealed T](init: T): private var v: T = init def setV(x: T): Unit = v = x def getV: T = v + val _ = Ref[String => String]((x: String) => x) // error val r = Ref((x: String) => x) - def scope = - val cap3: CC^ = ??? + def scope(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 index 50cbe8e0f8f9..f5227bfef96b 100644 --- a/tests/neg-custom-args/captures/localcaps.scala +++ b/tests/neg-custom-args/captures/localcaps.scala @@ -1,7 +1,9 @@ class C: + this: 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 z = (c0: caps.Cap) => (x: Int) => (c: C^{cap[C]}) => x // ok private val z2 = identity((x: Int) => (c: C^{cap[z2]}) => x) // error diff --git a/tests/neg-custom-args/captures/nesting-inversion.scala b/tests/neg-custom-args/captures/nesting-inversion.scala new file mode 100644 index 000000000000..0460f1243cca --- /dev/null +++ b/tests/neg-custom-args/captures/nesting-inversion.scala @@ -0,0 +1,43 @@ +def f(x: (() => Unit)): (() => Unit) => (() => Unit) = + def g(y: (() => Unit)): (() => Unit) = x + g + +def test1(x: (() => Unit)): Unit = + def test2(y: (() => Unit)) = + val a: (() => Unit) => (() => Unit) = f(y) + a(x) // OK, but should be error + test2(() => ()) + +def test2(x1: (() => Unit), x2: (() => Unit) => Unit) = + class C1(x1: (() => Unit), xx2: (() => Unit) => Unit): + def c2(y1: (() => Unit), y2: (() => Unit) => Unit): C2^{cap} = C2(y1, y2) + class C2(y1: (() => Unit), y2: (() => Unit) => Unit): + val a: (() => Unit) => (() => Unit) = f(y1) + a(x1) //OK, but should be error + C2(() => (), x => ()) + + def test3(y1: (() => Unit), y2: (() => Unit) => Unit) = + val cc1 = C1(y1, y2) + val cc2 = cc1.c2(x1, x2) + val cc3: cc1.C2^{cap[test2]} = cc2 // error + +def test4(x1: () => Unit) = + class C1: + this: C1^ => + class C2(z: () => Unit): + this: C2^ => + val foo: () => Unit = ??? + + def test5(x2: () => Unit) = + val xx1: C1^{cap[test5]} = C1() + val y1 = + val xx2 = xx1.C2(x1) + val xx3: xx1.C2^{cap[test4]} = xx2 // ok, but dubious + // actual capture set is in test4 + // but level constraints would determine that the root should be in test5 + // only, there is no root in the set to be mapped + xx2 + val f1 = y1.foo + val xx4 = xx1.C2(x2) + val xx5: xx1.C2^{cap[test4]} = xx4 // error + diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check new file mode 100644 index 000000000000..b7a438f30092 --- /dev/null +++ b/tests/neg-custom-args/captures/outer-var.check @@ -0,0 +1,44 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- +11 | x = q // error + | ^ + | Found: () ->{q} Unit + | Required: () ->{cap[test]} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:12:9 ------------------------------------- +12 | x = (q: Proc) // error + | ^^^^^^^ + | Found: () ->{cap[inner]} Unit + | Required: () ->{cap[test]} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- +13 | y = (q: Proc) // error + | ^^^^^^^ + | Found: () ->{cap[inner]} Unit + | Required: () ->{p} Unit + | + | Note that reference (cap[inner] : caps.Cap), defined in method inner + | cannot be included in outer capture set {p} of variable y which is associated with method test + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- +14 | y = q // error + | ^ + | Found: () ->{q} Unit + | Required: () ->{p} Unit + | + | Note that reference (q : Proc), defined in method inner + | cannot be included in outer capture set {p} of variable y which is associated with method test + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:16:65 ------------------------------------ +16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: scala.collection.mutable.ListBuffer[box () => Unit] + | Required: scala.collection.mutable.ListBuffer[box () ->? Unit]^? + | + | Note that the universal capability `cap` + | cannot be included in capture set ? of variable finalizeActions + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala new file mode 100644 index 000000000000..c59efefb51c4 --- /dev/null +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -0,0 +1,18 @@ +class CC +type Cap = CC^ + +type Proc = () => Unit + +def test(p: Proc, q: () => Unit) = + var x: () ->{cap[test]} Unit = p + var y = p // OK, y has type () ->{p} Proc + + def inner(q: Proc) = + x = q // error + x = (q: Proc) // error + y = (q: Proc) // error + y = q // error + + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error + + diff --git a/tests/neg-custom-args/captures/pairs.check b/tests/neg-custom-args/captures/pairs.check new file mode 100644 index 000000000000..38712469879f --- /dev/null +++ b/tests/neg-custom-args/captures/pairs.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:14:30 ---------------------------------------- +14 | val x1c: Cap ->{c} Unit = x1 // error + | ^^ + | Found: (x$0: Cap^?) ->{x1} Unit + | Required: Cap^ ->{c} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:16:30 ---------------------------------------- +16 | val y1c: Cap ->{d} Unit = y1 // error + | ^^ + | Found: (x$0: Cap^?) ->{y1} Unit + | Required: Cap^ ->{d} Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/pairs.scala b/tests/neg-custom-args/captures/pairs.scala new file mode 100644 index 000000000000..4fc495d60f95 --- /dev/null +++ b/tests/neg-custom-args/captures/pairs.scala @@ -0,0 +1,17 @@ +@annotation.capability class Cap + +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 + + 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 // error + val y1 = p.snd + val y1c: Cap ->{d} Unit = y1 // error + diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index f57aef60745b..65419793d010 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -4,33 +4,43 @@ | A pure expression does nothing in statement position; you may be omitting necessary parentheses | | longer explanation available when compiling with `-explain` --- [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` +-- 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:10 ---------------------------------------------------------- +18 | val x = try // error + | ^ + | result of `try` cannot have type () => Unit since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. +19 | () => foo(1) +20 | catch +21 | case _: Ex1 => ??? +22 | case _: Ex2 => ??? +-- Error: tests/neg-custom-args/captures/real-try.scala:24:10 ---------------------------------------------------------- +24 | val y = try // error + | ^ + | result of `try` cannot have type () => Cell[Unit]^? since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. +25 | () => Cell(foo(1)) +26 | catch +27 | case _: Ex1 => ??? +28 | case _: Ex2 => ??? +-- Error: tests/neg-custom-args/captures/real-try.scala:30:10 ---------------------------------------------------------- +30 | val b = try // error + | ^ + | result of `try` cannot have type Cell[box () => Unit]^? since + | the part box () => Unit of that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. +31 | Cell(() => foo(1)) +32 | catch +33 | case _: Ex1 => ??? +34 | case _: Ex2 => ??? diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 8020f98f0f10..23961e884ea3 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -9,26 +9,26 @@ def foo(i: Int): (CanThrow[Ex1], CanThrow[Ex2]) ?-> Unit = class Cell[+T](val x: T) def test(): Unit = - try - () => foo(1) // no error, since result type is Unit + try // error + () => foo(1) catch case _: Ex1 => ??? case _: Ex2 => ??? - val x = try - () => foo(1) // error + val x = try // error + () => foo(1) catch case _: Ex1 => ??? case _: Ex2 => ??? - val y = try - () => Cell(foo(1)) // error + val y = try // error + () => Cell(foo(1)) catch case _: Ex1 => ??? case _: Ex2 => ??? - val b = try - Cell(() => foo(1))// // error + val b = try // error + Cell(() => foo(1)) catch case _: Ex1 => ??? case _: Ex2 => ??? diff --git a/tests/neg-custom-args/captures/refs.scala b/tests/neg-custom-args/captures/refs.scala.disabled similarity index 87% rename from tests/neg-custom-args/captures/refs.scala rename to tests/neg-custom-args/captures/refs.scala.disabled index b64b27ef4af0..b20e8029b65e 100644 --- a/tests/neg-custom-args/captures/refs.scala +++ b/tests/neg-custom-args/captures/refs.scala.disabled @@ -18,9 +18,10 @@ def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T): T result def test1 = - usingLogFile[Proc]: (local: caps.Cap) ?=> // error (but with a hard to parse error message) + usingLogFile[Proc]: (local: caps.Cap) ?=> (f: FileOutputStream^{local}) => - () => f.write(1) // this line has type () ->{local} Unit, but usingLogFile + () => f.write(1) // error (but with a hard to parse error message) + // this line has type () ->{local} Unit, but usingLogFile // requires Proc, which expands to () -> 'cap[..test1](from instantiating usingLogFile) def test2 = diff --git a/tests/neg-custom-args/captures/sealed-leaks.scala b/tests/neg-custom-args/captures/sealed-leaks.scala index df438e6973bc..a7acf77b5678 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[T](op: (l: caps.Cap) ?-> FileOutputStream^{l} => T): T = + def usingLogFile[sealed T](op: FileOutputStream^ => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -11,9 +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 + var x: (FileOutputStream^{cap[Test2]}) | Null = null def foo(f: FileOutputStream^, g: FileOutputStream^) = - var y = if ??? then f else g // error + var y = if ??? then f else g // ok + val yc: FileOutputStream^{f,g} = y usingLogFile { f => x = f } // error diff --git a/tests/neg-custom-args/captures/simple-escapes.check b/tests/neg-custom-args/captures/simple-escapes.check index 611d4f0f0dc3..a3b096bbf3c0 100644 --- a/tests/neg-custom-args/captures/simple-escapes.check +++ b/tests/neg-custom-args/captures/simple-escapes.check @@ -1,8 +1,8 @@ -- [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]} + | Found: (f : FileOutputStream^{local}) + | Required: FileOutputStream^{cap[Test1]} | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/simple-escapes.scala:19:15 ---------------------------------------------------- diff --git a/tests/neg-custom-args/captures/simple-escapes.scala b/tests/neg-custom-args/captures/simple-escapes.scala index 0d4666179292..14f38fef0795 100644 --- a/tests/neg-custom-args/captures/simple-escapes.scala +++ b/tests/neg-custom-args/captures/simple-escapes.scala @@ -10,7 +10,7 @@ def Test1 = logFile.close() result - var foo: FileOutputStream^ = FileOutputStream("") + var foo: FileOutputStream^{cap[Test1]} = FileOutputStream("") val later1 = usingLogFile { local => f => foo = f // error diff --git a/tests/neg-custom-args/captures/simple-using.check b/tests/neg-custom-args/captures/simple-using.check new file mode 100644 index 000000000000..2df7c70e0540 --- /dev/null +++ b/tests/neg-custom-args/captures/simple-using.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/simple-using.scala:8:2 -------------------------------------------------------- +8 | usingLogFile { f => () => f.write(2) } // error + | ^^^^^^^^^^^^ + | local reference f leaks into outer capture set of type parameter T of method usingLogFile diff --git a/tests/neg-custom-args/captures/simple-using.scala b/tests/neg-custom-args/captures/simple-using.scala new file mode 100644 index 000000000000..9dcd9dc34c8e --- /dev/null +++ b/tests/neg-custom-args/captures/simple-using.scala @@ -0,0 +1,8 @@ +import java.io.* +def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result +def test() = + usingLogFile { f => () => f.write(2) } // error diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 5994e3901179..3b96927de738 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,27 +1,28 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------ -23 | val a = handle[Exception, CanThrow[Exception]] { // error - | ^ - |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:23:16 --------------------------------------------------------------- +23 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Sealed type variable R cannot be instantiated to box CT[Exception]^ since + | that type captures the root capability `cap`. + | This is often caused by a local capability in an argument of method handle + | leaking as part of its result. -- Error: tests/neg-custom-args/captures/try.scala: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 + | (x : CanThrow[Exception]) 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]) => 49 | () => 50 | raise(new Exception)(using x) 51 | 22 -52 |} { // error +52 |} { // error | ^ - | Found: () ->{x$0, lcap} Int + | Found: () ->{x, x²} Int | Required: () -> Int + | + | where: x is a reference to a value parameter + | x² is a reference to a value parameter 53 | (ex: Exception) => () => 22 54 |} | @@ -29,4 +30,4 @@ -- Error: tests/neg-custom-args/captures/try.scala:35:11 --------------------------------------------------------------- 35 | val xx = handle { // error | ^^^^^^ - | escaping local reference lcap.type + | local reference x leaks into outer capture set of type parameter R of method handle diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index fe58145bca54..0d593b67f77b 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -14,13 +14,13 @@ 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, R <: Top](op: (lcap: caps.Cap) ?-> CT[E]^{lcap} => R)(handler: E => R): R = +def handle[E <: Exception, sealed R <: Top](op: CT[E]^ => R)(handler: E => R): R = val x: CT[E] = ??? try op(x) catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { // error + val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x }{ (ex: Exception) => ??? @@ -49,6 +49,6 @@ val global: () -> Int = handle { () => raise(new Exception)(using x) 22 -} { // error +} { // error (ex: Exception) => () => 22 } diff --git a/tests/neg-custom-args/captures/usingLogFile-alt.scala b/tests/neg-custom-args/captures/usingFile.scala similarity index 83% rename from tests/neg-custom-args/captures/usingLogFile-alt.scala rename to tests/neg-custom-args/captures/usingFile.scala index 36f6ecf1426e..7cfbac9540e7 100644 --- a/tests/neg-custom-args/captures/usingLogFile-alt.scala +++ b/tests/neg-custom-args/captures/usingFile.scala @@ -7,7 +7,7 @@ object Test: class Logger(f: OutputStream^): def log(msg: String): Unit = ??? - def usingFile[T](name: String, op: (lcap: caps.Cap) ?-> OutputStream^{lcap} => T): T = + def usingFile[sealed T](name: String, op: OutputStream^ => T): T = val f = new FileOutputStream(name) val result = op(f) f.close() diff --git a/tests/neg-custom-args/captures/usingLogFile-alt.check b/tests/neg-custom-args/captures/usingLogFile-alt.check index 93fc3ca5edb5..35276914f7b2 100644 --- a/tests/neg-custom-args/captures/usingLogFile-alt.check +++ b/tests/neg-custom-args/captures/usingLogFile-alt.check @@ -3,8 +3,8 @@ | ^^^^^^^^^ | 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 + | Note that reference (file : java.io.OutputStream^{lcap}), defined in method $anonfun + | cannot be included in outer capture set {x$0, x$0} which is associated with 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.check b/tests/neg-custom-args/captures/usingLogFile.check index 9550b5864586..4bb30d27825f 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,28 +1,28 @@ -- 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 + |(f : java.io.FileOutputStream^) cannot be referenced here; it is not included in the allowed capture set {cap[]} + |of an enclosing function literal with expected type () ->{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] + | Required: Test2.Cell[box () ->{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 | ^^^^^^^^^^^^ - | escaping local reference local.type + | local reference f leaks into outer capture set of type parameter T of method usingLogFile -- Error: tests/neg-custom-args/captures/usingLogFile.scala:28:23 ------------------------------------------------------ 28 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error | ^^^^^^^^^^^^ - | escaping local reference local.type --- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:14 ------------------------------------------------------ -47 | val later = usingLogFile { f => () => f.write(0) } // error - | ^^^^^^^^^^^^ - | 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 + | local reference f leaks into outer capture set of type parameter T of method usingLogFile +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:52:16 ------------------------------------------------------ +52 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error + | ^^^^^^^^^ + | local reference f leaks into outer capture set of type parameter T of method usingFile +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:60:16 ------------------------------------------------------ +60 | val later = usingFile("logfile", // error !!! but should be ok, since we can widen `l` to `file` instead of to `cap` | ^^^^^^^^^ - | escaping local reference local.type + | local reference l leaks into outer capture set of type parameter T of method usingFile diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index b87b81d0eda8..8246cfd9377e 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -14,7 +14,7 @@ object Test1: object Test2: - def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T): T = + def usingLogFile[sealed T](op: FileOutputStream^ => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -28,29 +28,19 @@ object Test2: private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error later2.x() - var later3: () => Unit = () => () + var later3: () ->{cap[``]} Unit = () => () usingLogFile { f => later3 = () => f.write(0) } // error later3() - var later4: Cell[() => Unit] = Cell(() => ()) + var later4: Cell[() ->{cap[``]} Unit] = Cell(() => ()) usingLogFile { f => later4 = Cell(() => f.write(0)) } // error later4.x() object Test3: - - def usingLogFile[T](op: (local: caps.Cap) ?-> FileOutputStream^{local} => T) = - val logFile = FileOutputStream("log") - val result = op(logFile) - logFile.close() - result - - val later = usingLogFile { f => () => f.write(0) } // error - -object Test4: class Logger(f: OutputStream^): def log(msg: String): Unit = ??? - def usingFile[T](name: String, op: (local: caps.Cap) ?-> OutputStream^{local} => T): T = + def usingFile[sealed T](name: String, op: OutputStream^ => T): T = val f = new FileOutputStream(name) val result = op(f) f.close() @@ -62,11 +52,11 @@ object Test4: val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error later(1) - def usingLogger[T](f: OutputStream^, op: (local: caps.Cap) ?-> Logger^{f} => T): T = + def usingLogger[sealed T](f: OutputStream^, op: Logger^{f} => T): T = val logger = Logger(f) op(logger) def test = - val later = usingFile("logfile", - usingLogger(_, l => () => l.log("test"))) // ok, since we can widen `l` to `file` instead of to `cap` + val later = usingFile("logfile", // error !!! but should be ok, since we can widen `l` to `file` instead of to `cap` + usingLogger(_, l => () => l.log("test"))) later() diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check new file mode 100644 index 000000000000..873435b8a1aa --- /dev/null +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -0,0 +1,21 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:15:9 ----------------------------------- +15 | a = (g: String => String) // error + | ^^^^^^^^^^^^^^^^^^^ + | Found: String ->{cap[g]} String + | Required: String ->{cap[test]} String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- +16 | a = g // error + | ^ + | Found: (x: String) ->{cap3} String + | Required: (x: String) ->{cap[test]} String + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- +17 | b = List(g) // error + | ^^^^^^^ + | Found: List[box (x$0: String) ->{cap3} String] + | Required: List[box String ->{cap[test]} String] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/vars-simple.scala b/tests/neg-custom-args/captures/vars-simple.scala new file mode 100644 index 000000000000..e7b9723e5b82 --- /dev/null +++ b/tests/neg-custom-args/captures/vars-simple.scala @@ -0,0 +1,18 @@ +class CC +type Cap = CC^ + +def test(cap1: Cap, cap2: Cap) = + var a: String ->{cap[test]} String = ??? + var b: List[String ->{cap[test]} String] = Nil + def f(x: String): String = if cap1 == cap1 then "" else "a" + a = f // ok + val x = List(f) + b = x // ok + b = List(f) // ok + + def scope(cap3: Cap) = + def g(x: String): String = if cap3 == cap3 then "" else "a" + a = (g: String => String) // error + a = g // error + b = List(g) // error + diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index f8abe22e9d53..844fa90a35cd 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,50 +1,28 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:11:24 ----------------------------------------- -11 | val z2c: () -> Unit = z2 // error - | ^^ - | Found: () ->{z2} Unit - | Required: () -> Unit - | - | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:23:14 -------------------------------------------------------------- -23 | a = x => g(x) // error +-- Error: tests/neg-custom-args/captures/vars.scala:22:14 -------------------------------------------------------------- +22 | 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 + | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a + | + | Note that reference (cap3 : Cap), defined in method scope + | cannot be included in outer capture set {cap1} of variable a which is associated with method test +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:23:8 ------------------------------------------ +23 | a = g // error | ^ - | Found: box (x: String) ->{cap3} String - | Required: box (x$0: String) ->{cap[test]} String + | Found: (x: String) ->{cap3} String + | Required: (x$0: String) ->{cap1} String + | + | Note that reference (cap3 : Cap), defined in method scope + | cannot be included in outer capture set {cap1} of variable a which is associated with method test | | 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 +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:12 ----------------------------------------- +25 | 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 + | Required: List[box String ->{cap[test]} String] | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:35:2 --------------------------------------------------------------- -35 | local { root => cap3 => // error +-- Error: tests/neg-custom-args/captures/vars.scala:34:2 --------------------------------------------------------------- +34 | local { cap3 => // error | ^^^^^ - | escaping local reference root.type + | local reference cap3 leaks into outer capture set of type parameter T of method local diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 9b280a42a2f2..ede287e26010 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -8,16 +8,15 @@ def test(cap1: Cap, cap2: Cap) = val z = () => if x("") == "" then "a" else "b" val zc: () ->{cap1} String = z val z2 = () => { x = identity } - val z2c: () -> Unit = z2 // error - var a: String => String = f + val z2c: () -> Unit = z2 + var a = f - var b: List[String => String] = Nil - val u = a // was error, now ok - a("") // was error, now ok - b.head // was error, now ok + var b: List[String ->{cap[test]} String] = Nil + val u = a + a("") + b.head - def scope = - val cap3: Cap = CC() + def scope(cap3: Cap) = def g(x: String): String = if cap3 == cap3 then "" else "a" def h(): String = "" a = x => g(x) // error @@ -27,12 +26,12 @@ def test(cap1: Cap, cap2: Cap) = val gc = g g - 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) + val s = scope(new CC) + val sc: String => String = scope(new CC) - def local[T](op: (local: caps.Cap) -> CC^{local} -> T): T = op(caps.cap)(CC()) + def local[sealed T](op: CC^ -> T): T = op(CC()) - local { root => cap3 => // error + local { cap3 => // error def g(x: String): String = if cap3 == cap3 then "" else "a" g } diff --git a/tests/neg-custom-args/captures/withFile.scala b/tests/neg-custom-args/captures/withFile.scala new file mode 100644 index 000000000000..0f7701289aa5 --- /dev/null +++ b/tests/neg-custom-args/captures/withFile.scala @@ -0,0 +1,10 @@ +import java.io.* +object Test2: + + def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + + private val later = usingLogFile { f => () => f.write(0) } // error diff --git a/tests/neg/x b/tests/neg/x new file mode 100644 index 000000000000..fc4634d7618d --- /dev/null +++ b/tests/neg/x @@ -0,0 +1,151 @@ +2023.10.13 14:54:13 INFO compiling 37 Scala sources to /Users/odersky/workspace/dotty/compiler/target/scala-3.3.1/classes ... +2023.10.13 14:54:22 INFO compiling 36 Scala sources to /Users/odersky/workspace/dotty/compiler/target/scala-3.3.1/classes ... +2023.10.13 14:54:25 ERROR text document: file:///Users/odersky/workspace/dotty/tests/pos-custom-args/captures/outer-roots.scala +java.nio.file.NoSuchFileException: /Users/odersky/workspace/dotty/tests/pos-custom-args/captures/outer-roots.scala + at sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) + at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) + at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) + at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:218) + at java.nio.file.Files.newByteChannel(Files.java:380) + at java.nio.file.Files.newByteChannel(Files.java:432) + at java.nio.file.Files.readAllBytes(Files.java:3288) + at scala.meta.internal.io.PlatformFileIO$.slurp(PlatformFileIO.scala:45) + at scala.meta.internal.io.FileIO$.slurp(FileIO.scala:24) + at scala.meta.internal.metals.InteractiveSemanticdbs.$anonfun$textDocument$2(InteractiveSemanticdbs.scala:90) + at scala.Option.getOrElse(Option.scala:201) + at scala.meta.internal.metals.InteractiveSemanticdbs.$anonfun$textDocument$1(InteractiveSemanticdbs.scala:90) + at java.util.HashMap.compute(HashMap.java:1316) + at java.util.Collections$SynchronizedMap.compute(Collections.java:2770) + at scala.meta.internal.metals.InteractiveSemanticdbs.textDocument(InteractiveSemanticdbs.scala:89) + at scala.meta.internal.metals.InteractiveSemanticdbs.textDocument(InteractiveSemanticdbs.scala:63) + at scala.meta.internal.metals.AggregateSemanticdbs.loop$1(AggregateSemanticdbs.scala:30) + at scala.meta.internal.metals.AggregateSemanticdbs.textDocument(AggregateSemanticdbs.scala:36) + at scala.meta.internal.metals.CodeLensProvider.findLenses(CodeLensProvider.scala:22) + at scala.meta.internal.metals.MetalsLspService.$anonfun$codeLens$2(MetalsLspService.scala:1574) + at scala.meta.internal.metals.TimerProvider.timedThunk(TimerProvider.scala:25) + at scala.meta.internal.metals.MetalsLspService.$anonfun$codeLens$1(MetalsLspService.scala:1572) + at scala.meta.internal.metals.CancelTokens$.$anonfun$apply$2(CancelTokens.scala:26) + at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:687) + at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.lang.Thread.run(Thread.java:833) + +2023.10.13 14:54:27 INFO Deduplicating compilation of project_bd2c96d2de from bsp client 'scala-cli 0.1.19' (since 4h 32m 27.423s) +2023.10.13 14:54:27 INFO compiling project_bd2c96d2de (470 scala sources and 5 java sources) +2023.10.13 14:54:28 INFO time: compiled scala3-compiler in 14s +2023.10.13 14:54:28 INFO compiling scala3-presentation-compiler +2023.10.13 14:54:28 INFO compiling scala3-compiler-test +2023.10.13 14:54:28 INFO compiling 2 Scala sources to /Users/odersky/workspace/dotty/presentation-compiler/target/scala-3.3.1/classes ... +2023.10.13 14:54:28 INFO compiling 7 Scala sources and 1 Java source to /Users/odersky/workspace/dotty/compiler/target/scala-3.3.1/test-classes ... +2023.10.13 14:54:30 INFO time: compiled scala3-presentation-compiler in 0.54s +2023.10.13 14:54:30 INFO compiling scala3-presentation-compiler-test +2023.10.13 14:54:30 INFO time: compiled scala3-presentation-compiler-test in 26ms +2023.10.13 14:54:30 INFO time: compiled scala3-compiler-test in 1.32s +2023.10.13 14:54:30 INFO Processing buildTarget/scalaMainClasses +2023.10.13 14:54:31 INFO compiling scala3-interfaces +2023.10.13 14:54:31 INFO compiling scala3-library +2023.10.13 14:54:31 INFO time: compiled scala3-interfaces in 2ms +2023.10.13 14:54:31 INFO time: compiled scala3-library in 3ms +2023.10.13 14:54:31 INFO compiling tasty-core +2023.10.13 14:54:31 INFO time: compiled tasty-core in 2ms +2023.10.13 14:54:31 INFO compiling scala3-compiler +2023.10.13 14:54:31 INFO time: compiled scala3-compiler in 14ms +2023.10.13 14:54:31 INFO compiling scala3-sbt-bridge +2023.10.13 14:54:31 INFO compiling scala3-presentation-compiler +2023.10.13 14:54:31 INFO compiling scala3-compiler-test +2023.10.13 14:54:31 INFO compiling 1 Java source to /Users/odersky/workspace/dotty/sbt-bridge/src/target/classes ... +2023.10.13 14:54:31 INFO time: compiled scala3-compiler-test in 9ms +2023.10.13 14:54:31 INFO time: compiled scala3-presentation-compiler in 14ms +2023.10.13 14:54:31 INFO compiling scala3-presentation-compiler-test +2023.10.13 14:54:31 INFO time: compiled scala3-presentation-compiler-test in 7ms +2023.10.13 14:54:32 INFO time: compiled scala3-sbt-bridge in 1.06s +2023.10.13 14:54:33 INFO compiling scala3-library-bootstrapped +2023.10.13 14:54:33 INFO compiling 2 Scala sources to /Users/odersky/workspace/dotty/out/bootstrap/scala3-library-bootstrapped/scala-3.4.0-RC1-bin-SNAPSHOT-nonbootstrapped/classes ... +2023.10.13 14:54:35 INFO Processing buildTarget/scalaTestClasses +2023.10.13 14:54:35 INFO compiling scala3-library +2023.10.13 14:54:35 INFO time: compiled scala3-library in 1ms +2023.10.13 14:54:35 INFO compiling scala3-interfaces +2023.10.13 14:54:35 INFO compiling tasty-core +2023.10.13 14:54:35 INFO time: compiled scala3-interfaces in 1ms +2023.10.13 14:54:35 INFO time: compiled tasty-core in 1ms +2023.10.13 14:54:35 INFO compiling scala3-compiler +2023.10.13 14:54:35 INFO time: compiled scala3-compiler in 15ms +2023.10.13 14:54:35 INFO compiling scala3-presentation-compiler +2023.10.13 14:54:35 INFO compiling scala3-compiler-test +2023.10.13 14:54:35 INFO time: compiled scala3-presentation-compiler in 5ms +2023.10.13 14:54:35 INFO time: compiled scala3-compiler-test in 6ms +2023.10.13 14:54:35 INFO compiling scala3-presentation-compiler-test +2023.10.13 14:54:35 INFO time: compiled scala3-presentation-compiler-test in 3ms +2023.10.13 14:54:35 INFO Processing buildTarget/jvmRunEnvironment +2023.10.13 14:54:35 INFO compiling scala3-interfaces +2023.10.13 14:54:35 INFO compiling scala3-library +2023.10.13 14:54:35 INFO time: compiled scala3-interfaces in 3ms +2023.10.13 14:54:35 INFO time: compiled scala3-library in 4ms +2023.10.13 14:54:35 INFO compiling tasty-core +2023.10.13 14:54:35 INFO time: compiled tasty-core in 1ms +2023.10.13 14:54:35 INFO compiling scala3-compiler +2023.10.13 14:54:35 INFO time: compiled scala3-compiler in 13ms +2023.10.13 14:54:35 INFO compiling scala3-sbt-bridge +2023.10.13 14:54:35 INFO compiling scala3-compiler-test +2023.10.13 14:54:35 INFO compiling scala3-presentation-compiler +2023.10.13 14:54:35 INFO time: compiled scala3-sbt-bridge in 8ms +2023.10.13 14:54:35 INFO time: compiled scala3-presentation-compiler in 8ms +2023.10.13 14:54:35 INFO time: compiled scala3-compiler-test in 13ms +2023.10.13 14:54:35 INFO compiling scala3-presentation-compiler-test +2023.10.13 14:54:35 INFO time: compiled scala3-presentation-compiler-test in 3ms +2023.10.13 14:54:35 INFO compiling scala3-library-bootstrapped +2023.10.13 14:54:41 WARN sbt does not support `buildTarget/inverseSources`, unable to fetch targets owning source. +2023.10.13 14:54:41 WARN no build target for: /Users/odersky/workspace/dotty/tests/neg/i18588.scala +2023.10.13 14:54:41 WARN sbt does not support `buildTarget/inverseSources`, unable to fetch targets owning source. +2023.10.13 14:54:41 WARN no build target for: /Users/odersky/workspace/dotty/tests/neg/i18588.scala +2023.10.13 14:54:48 WARN sbt does not support `buildTarget/inverseSources`, unable to fetch targets owning source. +2023.10.13 14:54:48 WARN no build target for: /Users/odersky/workspace/dotty/tests/neg/i18588.scala +2023.10.13 14:55:05 INFO Processing workspace/buildTargets +2023.10.13 14:55:13 WARN sbt does not support `buildTarget/inverseSources`, unable to fetch targets owning source. +2023.10.13 14:55:13 WARN no build target for: /Users/odersky/workspace/dotty/tests/neg/i18588.scala +2023.10.13 14:56:06 INFO Processing workspace/buildTargets +2023.10.13 14:56:08 INFO time: code lens generation in 1.36s +2023.10.13 14:56:16 INFO time: code lens generation in 1.24s +2023.10.13 14:56:30 WARN sbt does not support `buildTarget/inverseSources`, unable to fetch targets owning source. +2023.10.13 14:56:32 WARN no build target for: /Users/odersky/workspace/dotty/tests/neg/i18588.scala +2023.10.13 14:56:41 INFO time: code lens generation in 24s +2023.10.13 14:56:53 INFO BSP server: [warn] ./backend/jvm/ClassNode1.java:1:1: Using directives detected in multiple files. It is recommended to keep them centralized in the /Users/odersky/workspace/dotty/tests/pos-with-compiler-cc/project.scala file. +2023.10.13 14:56:53 INFO BSP server: [warn] ./backend/jvm/LabelNode1.java:1:1: Using directives detected in multiple files. It is recommended to keep them centralized in the /Users/odersky/workspace/dotty/tests/pos-with-compiler-cc/project.scala file. +2023.10.13 14:56:53 INFO BSP server: [warn] ./backend/jvm/MethodNode1.java:1:1: Using directives detected in multiple files. It is recommended to keep them centralized in the /Users/odersky/workspace/dotty/tests/pos-with-compiler-cc/project.scala file. +2023.10.13 14:56:53 INFO BSP server: [warn] ./dotc/profile/ExtendedThreadMxBean.java:1:1: Using directives detected in multiple files. It is recommended to keep them centralized in the /Users/odersky/workspace/dotty/tests/pos-with-compiler-cc/project.scala file. +2023.10.13 14:56:53 INFO BSP server: [warn] package dotty.tools.dotc.profile; +2023.10.13 14:56:53 INFO BSP server: [warn] ^ +2023.10.13 14:56:53 INFO BSP server: [warn] ./dotc/profile/ExternalToolHook.java:1:1: Using directives detected in multiple files. It is recommended to keep them centralized in the /Users/odersky/workspace/dotty/tests/pos-with-compiler-cc/project.scala file. +2023.10.13 14:56:53 INFO BSP server: [warn] package dotty.tools.dotc.profile; +2023.10.13 14:56:53 INFO BSP server: [warn] ^ +2023.10.13 14:56:59 INFO time: code lens generation in 35s +2023.10.13 14:56:59 INFO time: code lens generation in 37s +2023.10.13 14:57:06 INFO Processing workspace/buildTargets +2023.10.13 14:58:32 INFO Processing workspace/buildTargets +2023.10.13 14:59:13 INFO Processing workspace/buildTargets +[Error - 15:01:20] Request textDocument/codeAction failed. + Message: Internal error. + Code: -32603 +java.lang.RuntimeException: java.lang.reflect.InvocationTargetException + at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.lambda$null$0(GenericEndpoint.java:67) + at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.request(GenericEndpoint.java:120) + at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.handleRequest(RemoteEndpoint.java:261) + at org.eclipse.lsp4j.jsonrpc.RemoteEndpoint.consume(RemoteEndpoint.java:190) + at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.handleMessage(StreamMessageProducer.java:194) + at org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer.listen(StreamMessageProducer.java:94) + at org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor.run(ConcurrentMessageProcessor.java:113) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) + at java.base/java.lang.Thread.run(Thread.java:833) +Caused by: java.lang.reflect.InvocationTargetException + at jdk.internal.reflect.GeneratedMethodAccessor11.invoke(Unknown Source) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint.lambda$null$0(GenericEndpoint.java:65) + ... 11 more +Caused by: java.lang.OutOfMemoryError: Java heap space + +2023.10.13 15:01:21 INFO time: code lens generation in 1.23s diff --git a/tests/pos-custom-args/captures/colltest.scala b/tests/pos-custom-args/captures/colltest.scala index af4ae6f03c56..ab3ac437db9f 100644 --- a/tests/pos-custom-args/captures/colltest.scala +++ b/tests/pos-custom-args/captures/colltest.scala @@ -31,5 +31,4 @@ object CollectionStrawMan5 { 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/concat-iterator.scala b/tests/pos-custom-args/captures/concat-iterator.scala new file mode 100644 index 000000000000..828b8b3b2657 --- /dev/null +++ b/tests/pos-custom-args/captures/concat-iterator.scala @@ -0,0 +1,17 @@ +package test + +trait IOnce[A]: + self: I[A]^ => + +trait I[+A]: + self: I[A]^ => + + def concat[B >: A](xs: => IOnce[B]^): I[B]^{this, xs} = new I.ConcatI[B](self).concat(xs) + +object I: + private final class ConcatI[+A](val from: I[A]^) extends I[A]: + override def concat[B >: A](that: => IOnce[B]^): I[B]^{this, that} = ??? + + + + diff --git a/tests/pos-custom-args/captures/function-combinators.scala b/tests/pos-custom-args/captures/function-combinators.scala index 4354af4c7636..7369f8399f2f 100644 --- a/tests/pos-custom-args/captures/function-combinators.scala +++ b/tests/pos-custom-args/captures/function-combinators.scala @@ -2,7 +2,7 @@ class ContextClass type Context = ContextClass^ def Test(using ctx1: Context, ctx2: Context) = - val f: Int => Int = identity + val f: Int ->{cap[Test]} Int = identity val g1: Int ->{ctx1} Int = identity val g2: Int ->{ctx2} Int = identity val h: Int -> Int = identity @@ -19,7 +19,7 @@ def Test(using ctx1: Context, ctx2: Context) = val c3 = h.andThen(g2); val _: Int ->{g2} Int = c3 val c4 = h.andThen(h); val _: Int -> Int = c4 - val f2: (Int, Int) => Int = _ + _ + val f2: (Int, Int) ->{cap[Test]} Int = _ + _ val f2c = f2.curried; val _: Int -> Int ->{f2} Int = f2c val f2t = f2.tupled; val _: ((Int, Int)) ->{f2} Int = f2t diff --git a/tests/pos-custom-args/captures/i15749a.scala b/tests/pos-custom-args/captures/i15749a.scala index 0ac65f7f4150..fe5f4d75dae1 100644 --- a/tests/pos-custom-args/captures/i15749a.scala +++ b/tests/pos-custom-args/captures/i15749a.scala @@ -1,4 +1,3 @@ -// TODO: Adapt to levels class Unit object u extends Unit @@ -11,12 +10,12 @@ def test = def wrapper[T](x: T): Wrapper[T] = [X] => (op: T ->{cap} X) => op(x) - def strictMap[A <: Top, B <: Top](mx: Wrapper[A])(f: A ->{cap} B): Wrapper[B] = + def strictMap[A <: Top, sealed 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[A](mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = + def forceWrapper[sealed 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/iterators.scala b/tests/pos-custom-args/captures/iterators.scala index 10a7f57cd68f..9a3ce7569b09 100644 --- a/tests/pos-custom-args/captures/iterators.scala +++ b/tests/pos-custom-args/captures/iterators.scala @@ -1,5 +1,9 @@ package cctest +trait IterableOnce[A]: + this: IterableOnce[A]^ => + def iterator: Iterator[A]^{this} + abstract class Iterator[T]: thisIterator: Iterator[T]^ => @@ -10,6 +14,9 @@ abstract class Iterator[T]: def next = f(thisIterator.next) end Iterator +private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head.iterator + class C type Cap = C^ diff --git a/tests/pos-custom-args/captures/lazylists-exceptions.scala b/tests/pos-custom-args/captures/lazylists-exceptions.scala index 8f1fba2bf2dc..afc6616108bc 100644 --- a/tests/pos-custom-args/captures/lazylists-exceptions.scala +++ b/tests/pos-custom-args/captures/lazylists-exceptions.scala @@ -1,5 +1,6 @@ import language.experimental.saferExceptions import scala.compiletime.uninitialized +import scala.annotation.unchecked.uncheckedCaptures trait LzyList[+A]: def isEmpty: Boolean @@ -13,7 +14,7 @@ object LzyNil extends LzyList[Nothing]: final class LzyCons[+A](hd: A, tl: () => LzyList[A]^) extends LzyList[A]: private var forced = false - private var cache: LzyList[A]^{this} = uninitialized + private var cache: LzyList[A @uncheckedCaptures]^{this} = uninitialized private def force = if !forced then { cache = tl(); forced = true } cache diff --git a/tests/pos-custom-args/captures/lists.scala b/tests/pos-custom-args/captures/lists.scala index 56473e68d49f..c3a604128942 100644 --- a/tests/pos-custom-args/captures/lists.scala +++ b/tests/pos-custom-args/captures/lists.scala @@ -30,7 +30,7 @@ def test(c: Cap, d: Cap, e: Cap) = CONS(z, ys) val zsc: LIST[Cap ->{d, y} Unit] = zs val z1 = zs.head - val z1c: Cap ->{y, d} Unit = z1 + val z1c: Cap^{cap[test]} ->{y, d} Unit = z1 val ys1 = zs.tail val y1 = ys1.head diff --git a/tests/pos-custom-args/captures/nested-classes-2.scala b/tests/pos-custom-args/captures/nested-classes-2.scala new file mode 100644 index 000000000000..e9e74dedb7c1 --- /dev/null +++ b/tests/pos-custom-args/captures/nested-classes-2.scala @@ -0,0 +1,24 @@ + +def f(x: (() => Unit)): (() => Unit) => (() => Unit) = + def g(y: (() => Unit)): (() => Unit) = x + g + +def test1(x: (() => Unit)): Unit = + def test2(y: (() => Unit)) = + val a: (() => Unit) => (() => Unit) = f(y) + a(x) // OK, but should be error + test2(() => ()) + +def test2(x1: (() => Unit), x2: (() => Unit) => Unit) = + class C1(x1: (() => Unit), xx2: (() => Unit) => Unit): + def c2(y1: (() => Unit), y2: (() => Unit) => Unit): C2^{cap} = C2(y1, y2) + class C2(y1: (() => Unit), y2: (() => Unit) => Unit): + val a: (() => Unit) => (() => Unit) = f(y1) + a(x1) //OK, but should be error + C2(() => (), x => ()) + + def test3(y1: (() => Unit), y2: (() => Unit) => Unit) = + val cc1/*: C1^{cap[test3]}*/ = C1(y1, y2) // error (but should be OK) + val cc2 = cc1.c2(x1, x2) // error (but should be OK) + //val cc3: cc1.C2^{cap[test2]} = cc2 + diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index 78991e4377c0..e15a76970c29 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -1,4 +1,5 @@ - +//class CC +//type Cap = CC^ @annotation.capability class Cap object Generic: @@ -12,42 +13,6 @@ object Generic: 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 x1c: Cap^ ->{c} Unit = x1 val y1 = p.snd - val y1c: Cap ->{d} Unit = y1 - -object Monomorphic: - - class Pair(x: Cap => Unit, y: Cap => Unit): - 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 () - 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 - -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 - + val y1c: Cap^ ->{d} Unit = y1 diff --git a/tests/pos-custom-args/captures/test.scala b/tests/pos-custom-args/captures/test.scala index cf532bbdf34a..0664b2172198 100644 --- a/tests/pos-custom-args/captures/test.scala +++ b/tests/pos-custom-args/captures/test.scala @@ -1,9 +1,16 @@ class C type Cap = C^ -class Foo(x: Cap): - this: Foo^{x} => +type Proc = () => Unit + +class Ref[sealed T](p: T): + private var x: T = p + def set(x: T): Unit = this.x = x + def get: T = x + +def test(c: () => Unit) = + val p: () => Unit = ??? + val r = Ref(p) + val x = r.get + r.set(x) -def test(c: Cap) = - val x = Foo(c) - () diff --git a/tests/pos-custom-args/captures/vars.scala b/tests/pos-custom-args/captures/vars.scala index ccf2cd587eb1..a335be96fed1 100644 --- a/tests/pos-custom-args/captures/vars.scala +++ b/tests/pos-custom-args/captures/vars.scala @@ -14,4 +14,4 @@ def test(cap1: Cap, cap2: Cap) = val r = Ref() r.elem = f - val fc: String ->{cap1} String = r.elem + val fc: String ->{cap1} String = r.elem \ No newline at end of file diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala index 8e3253bcd22d..451b8988364f 100644 --- a/tests/pos-custom-args/captures/vars1.scala +++ b/tests/pos-custom-args/captures/vars1.scala @@ -1,19 +1,26 @@ import caps.unsafe.* +import annotation.unchecked.uncheckedCaptures object Test: type ErrorHandler = (Int, String) => Unit + @uncheckedCaptures var defaultIncompleteHandler: ErrorHandler = ??? + @uncheckedCaptures var incompleteHandler: ErrorHandler = defaultIncompleteHandler private val x = incompleteHandler.unsafeUnbox val _ : ErrorHandler = x val _ = x(1, "a") - def defaultIncompleteHandler1(): ErrorHandler = ??? + def defaultIncompleteHandler1(): (Int, String) => Unit = ??? 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 diff --git a/tests/pos-special/stdlib/Test1.scala b/tests/pos-special/stdlib/Test1.scala index 786e3aaa2bf1..9ee4e7cfa6a1 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[T](op: (lcap: caps.Cap) ?-> FileOutputStream^ => T): T = + def usingLogFile[sealed T](op: FileOutputStream^ => T): T = val logFile = FileOutputStream("log") val result = op(logFile) logFile.close() @@ -20,7 +20,7 @@ object Test0: object Test1: def test(it: Iterator[Int]^, v: View[Int]^) = - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[test]} Boolean = _ % 2 == 0 val it2 = it.filter(isEven) val _: Iterator[Int]^{it, isEven} = it2 val it2c: Iterator[Int]^{it2} = it2 diff --git a/tests/pos-special/stdlib/Test2.scala b/tests/pos-special/stdlib/Test2.scala index cab9440c17db..a59da522b183 100644 --- a/tests/pos-special/stdlib/Test2.scala +++ b/tests/pos-special/stdlib/Test2.scala @@ -64,7 +64,7 @@ object Test { def iterOps(xs: => Iterator[Int]^) = val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[iterOps]} Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => x :: -x :: Nil val x1 = xs.foldLeft("")(strPlusInt) @@ -116,7 +116,7 @@ object Test { def viewOps(xs: View[Int]^) = { val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[viewOps]} Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => x :: -x :: Nil val x1 = xs.foldLeft("")(strPlusInt) diff --git a/tests/pos-special/stdlib/collection/IterableOnce.scala b/tests/pos-special/stdlib/collection/IterableOnce.scala index f75eab54a89b..6836a3bac39a 100644 --- a/tests/pos-special/stdlib/collection/IterableOnce.scala +++ b/tests/pos-special/stdlib/collection/IterableOnce.scala @@ -14,7 +14,7 @@ package scala package collection import scala.annotation.tailrec -import scala.annotation.unchecked.uncheckedVariance +import scala.annotation.unchecked.{uncheckedVariance, uncheckedCaptures} import scala.collection.mutable.StringBuilder import scala.language.implicitConversions import scala.math.{Numeric, Ordering} @@ -1340,8 +1340,8 @@ object IterableOnceOps: // Moved out of trait IterableOnceOps to here, since universal traits cannot // have nested classes in Scala 3 private class Maximized[X, B](descriptor: String)(f: X -> B)(cmp: (B, B) -> Boolean) extends AbstractFunction2[Maximized[X, B], X, Maximized[X, B]] { - var maxElem: X = null.asInstanceOf[X] - var maxF: B = null.asInstanceOf[B] + var maxElem: X @uncheckedCaptures = null.asInstanceOf[X] + var maxF: B @uncheckedCaptures = null.asInstanceOf[B] var nonEmpty = false def toOption: Option[X] = if (nonEmpty) Some(maxElem) else None def result: X = if (nonEmpty) maxElem else throw new UnsupportedOperationException(s"empty.$descriptor") diff --git a/tests/pos-special/stdlib/collection/Iterator.scala b/tests/pos-special/stdlib/collection/Iterator.scala index 6e8d15ea99a4..ecd8d985bbf0 100644 --- a/tests/pos-special/stdlib/collection/Iterator.scala +++ b/tests/pos-special/stdlib/collection/Iterator.scala @@ -14,7 +14,7 @@ package scala.collection import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ImmutableBuilder} import scala.annotation.tailrec -import scala.annotation.unchecked.uncheckedVariance +import scala.annotation.unchecked.{uncheckedVariance, uncheckedCaptures} import scala.runtime.Statics import language.experimental.captureChecking import caps.unsafe.unsafeAssumePure @@ -161,12 +161,12 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") - private[this] var buffer: Array[B] = null // current result - private[this] var prev: Array[B] = null // if sliding, overlap from previous result + private[this] var buffer: Array[B @uncheckedCaptures] = null // current result + private[this] var prev: Array[B @uncheckedCaptures] = null // if sliding, overlap from previous result private[this] var first = true // if !first, advancing may skip ahead private[this] var filled = false // whether the buffer is "hot" private[this] var partial = true // whether to emit partial sequence - private[this] var padding: () -> B = null // what to pad short sequences with + private[this] var padding: () -> B @uncheckedCaptures = null // what to pad short sequences with private[this] def pad = padding != null // irrespective of partial flag private[this] def newBuilder = { val b = ArrayBuilder.make[Any] @@ -1143,11 +1143,11 @@ object Iterator extends IterableFactory[Iterator] { * Nested ConcatIterators are merged to avoid blowing the stack. */ private final class ConcatIterator[+A](val from: Iterator[A]^) extends AbstractIterator[A] { - private var current: Iterator[A] = from.unsafeAssumePure + private var current: Iterator[A @uncheckedCaptures] = from.unsafeAssumePure // This should be Iteratpr[A]^, but fails since mutable variables can't capture cap. // To do better we'd need to track nesting levels for universal capabiltities. - private var tail: ConcatIteratorCell[A @uncheckedVariance] = null - private var last: ConcatIteratorCell[A @uncheckedVariance] = null + private var tail: ConcatIteratorCell[A @uncheckedVariance @uncheckedCaptures] = null + private var last: ConcatIteratorCell[A @uncheckedVariance @uncheckedCaptures] = null private var currentHasNextChecked = false def hasNext = @@ -1216,7 +1216,7 @@ object Iterator extends IterableFactory[Iterator] { } } - private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A]^, var tail: ConcatIteratorCell[A]) { + private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A]^, var tail: ConcatIteratorCell[A @uncheckedCaptures]) { def headIterator: Iterator[A]^{this} = head.iterator // CC todo: can't use {head} as capture set, gives "cannot establish a reference" } @@ -1277,8 +1277,8 @@ object Iterator extends IterableFactory[Iterator] { * type `A` and update an internal state of type `S`. */ private final class UnfoldIterator[A, S](init: S)(f: S => Option[(A, S)])extends AbstractIterator[A] { - private[this] var state: S = init - private[this] var nextResult: Option[(A, S)] = null + private[this] var state: S @uncheckedCaptures = init + private[this] var nextResult: Option[(A, S)] @uncheckedCaptures = null override def hasNext: Boolean = { if (nextResult eq null) { diff --git a/tests/pos-special/stdlib/collection/View.scala b/tests/pos-special/stdlib/collection/View.scala index 8e2ee3ad9e32..85910311a4c3 100644 --- a/tests/pos-special/stdlib/collection/View.scala +++ b/tests/pos-special/stdlib/collection/View.scala @@ -15,6 +15,7 @@ package scala.collection import scala.annotation.{nowarn, tailrec} import scala.collection.mutable.{ArrayBuffer, Builder} import scala.collection.immutable.LazyList +import scala.annotation.unchecked.uncheckedCaptures import language.experimental.captureChecking /** Views are collections whose transformation operations are non strict: the resulting elements @@ -448,7 +449,7 @@ object View extends IterableFactory[View] { } private final class TakeRightIterator[A](underlying: Iterator[A]^, maxlen: Int) extends AbstractIterator[A] { - private[this] var current: Iterator[A]^{underlying} = underlying + private[this] var current: Iterator[A @uncheckedCaptures]^{underlying} = underlying private[this] var len: Int = -1 private[this] var pos: Int = 0 private[this] var buf: ArrayBuffer[AnyRef] = _ diff --git a/tests/pos-special/stdlib/collection/immutable/List.scala b/tests/pos-special/stdlib/collection/immutable/List.scala index 6245c5001776..6a305f4ebdec 100644 --- a/tests/pos-special/stdlib/collection/immutable/List.scala +++ b/tests/pos-special/stdlib/collection/immutable/List.scala @@ -14,7 +14,7 @@ package scala package collection package immutable -import scala.annotation.unchecked.uncheckedVariance +import scala.annotation.unchecked.{uncheckedVariance, uncheckedCaptures} import scala.annotation.tailrec import mutable.{Builder, ListBuffer} import scala.collection.generic.DefaultSerializable @@ -215,7 +215,7 @@ sealed abstract class List[+A] // dropRight is inherited from LinearSeq override def splitAt(n: Int): (List[A], List[A]) = { - val b = new ListBuffer[A] + val b = new ListBuffer[A @uncheckedCaptures] var i = 0 var these = this while (!these.isEmpty && i < n) { @@ -307,7 +307,7 @@ sealed abstract class List[+A] } @inline final override def takeWhile(p: A => Boolean): List[A] = { - val b = new ListBuffer[A] + val b = new ListBuffer[A @uncheckedCaptures] var these = this while (!these.isEmpty && p(these.head)) { b += these.head @@ -317,7 +317,7 @@ sealed abstract class List[+A] } @inline final override def span(p: A => Boolean): (List[A], List[A]) = { - val b = new ListBuffer[A] + val b = new ListBuffer[A @uncheckedCaptures] var these = this while (!these.isEmpty && p(these.head)) { b += these.head @@ -652,7 +652,7 @@ sealed abstract class List[+A] // Internal code that mutates `next` _must_ call `Statics.releaseFence()` if either immediately, or // before a newly-allocated, thread-local :: instance is aliased (e.g. in ListBuffer.toList) -final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally +final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance @uncheckedCaptures]) // sound because `next` is used only locally extends List[A] { releaseFence() override def headOption: Some[A] = Some(head) @@ -684,7 +684,7 @@ object List extends StrictOptimizedSeqFactory[List] { def from[B](coll: collection.IterableOnce[B]^): List[B] = Nil.prependedAll(coll) - def newBuilder[A]: Builder[A, List[A]] = new ListBuffer() + def newBuilder[A]: Builder[A, List[A]] = new ListBuffer[A @uncheckedCaptures]() def empty[A]: List[A] = Nil diff --git a/tests/pos-special/stdlib/collection/mutable/ListBuffer.scala b/tests/pos-special/stdlib/collection/mutable/ListBuffer.scala index 570c815644ee..4f607c770130 100644 --- a/tests/pos-special/stdlib/collection/mutable/ListBuffer.scala +++ b/tests/pos-special/stdlib/collection/mutable/ListBuffer.scala @@ -19,6 +19,7 @@ import java.lang.{IllegalArgumentException, IndexOutOfBoundsException} import scala.collection.generic.DefaultSerializable import scala.runtime.Statics.releaseFence +import scala.annotation.unchecked.uncheckedCaptures import language.experimental.captureChecking /** A `Buffer` implementation backed by a list. It provides constant time @@ -37,7 +38,7 @@ import language.experimental.captureChecking * @define willNotTerminateInf */ @SerialVersionUID(-8428291952499836345L) -class ListBuffer[A] +class ListBuffer[sealed A] extends AbstractBuffer[A] with SeqOps[A, ListBuffer, ListBuffer[A]] with StrictOptimizedSeqOps[A, ListBuffer, ListBuffer[A]] @@ -396,9 +397,9 @@ class ListBuffer[A] @SerialVersionUID(3L) object ListBuffer extends StrictOptimizedSeqFactory[ListBuffer] { - def from[A](coll: collection.IterableOnce[A]^): ListBuffer[A] = new ListBuffer[A].freshFrom(coll) + def from[sealed A](coll: collection.IterableOnce[A]^): ListBuffer[A] = new ListBuffer[A].freshFrom(coll) - def newBuilder[A]: Builder[A, ListBuffer[A]] = new GrowableBuilder(empty[A]) + def newBuilder[sealed A]: Builder[A, ListBuffer[A]] = new GrowableBuilder(empty[A]) - def empty[A]: ListBuffer[A] = new ListBuffer[A] + def empty[A]: ListBuffer[A] = new ListBuffer[A @uncheckedCaptures] } diff --git a/tests/pos-with-compiler-cc/dotc/Run.scala b/tests/pos-with-compiler-cc/dotc/Run.scala index 16a955afca1a..96f8c6a7b06f 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 caps.unsafe.unsafeUnbox +import annotation.unchecked.uncheckedCaptures /** A compiler run. Exports various methods to compile source files */ class Run(comp: Compiler, @constructorOnly ictx0: Context) extends ImplicitRunInfo with ConstraintRunInfo { @@ -165,6 +165,7 @@ 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 @@ -275,7 +276,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).unsafeUnbox + val action = finalizeActions.remove(0) 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 0e8edd55ed08..f5a108a13c19 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Scopes.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Scopes.scala @@ -1,8 +1,3 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - package dotty.tools package dotc package core @@ -17,6 +12,7 @@ import Denotations._ import printing.Texts._ import printing.Printer import SymDenotations.NoDenotation +import annotation.unchecked.uncheckedCaptures import collection.mutable @@ -220,6 +216,7 @@ 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 67b9f063e9d0..0e1fc277865a 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 caps.unsafe.* +import annotation.unchecked.uncheckedCaptures /** Provides methods to compare types. */ @@ -33,17 +33,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling import TypeComparer._ Stats.record("TypeComparer") - private var myContext: Context = initctx.unsafeBox - def comparerContext: Context = myContext.unsafeUnbox + @uncheckedCaptures + private var myContext: Context = initctx + def comparerContext: Context = myContext - protected given [DummySoItsADef]: Context = myContext.unsafeUnbox + protected given [DummySoItsADef]: Context = myContext 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.unsafeBox + myContext = c 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 b87cde4a6ad1..fcc449af3632 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 caps.unsafe.{unsafeUnbox, unsafeBox} +import annotation.unchecked.uncheckedCaptures /** 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] - var curCtx = ctx.unsafeBox + @uncheckedCaptures var curCtx = ctx while currentAddr.index < end.index do - val stat = readIndexedStat(exprOwner)(using curCtx.unsafeUnbox) + val stat = readIndexedStat(exprOwner)(using curCtx) buf += stat stat match - case stat: Import => curCtx = curCtx.unsafeUnbox.importContext(stat, stat.symbol).unsafeBox + case stat: Import => curCtx = curCtx.importContext(stat, stat.symbol) case _ => assert(currentAddr.index == end.index) - k(buf.toList, curCtx.unsafeUnbox) + k(buf.toList, curCtx) 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 548f645a23d9..8487192b9d8a 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,6 +930,8 @@ 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 aa286446a334..0baae1730f4a 100644 --- a/tests/pos-with-compiler-cc/dotc/typer/Typer.scala +++ b/tests/pos-with-compiler-cc/dotc/typer/Typer.scala @@ -54,7 +54,8 @@ import config.Config import language.experimental.pureFunctions import scala.annotation.constructorOnly -import caps.unsafe.{unsafeBox, unsafeUnbox} +import annotation.unchecked.uncheckedCaptures + object Typer { @@ -1673,11 +1674,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 = { - var caseCtx = ctx.unsafeBox + @uncheckedCaptures var caseCtx = ctx val cases1 = tree.cases.zip(pt.cases) .map { case (cas, tpe) => - val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx.unsafeUnbox) - caseCtx = Nullables.afterPatternContext(sel, case1.pat).unsafeBox + val case1 = typedCase(cas, sel, wideSelType, tpe)(using caseCtx) + caseCtx = Nullables.afterPatternContext(sel, case1.pat) case1 } .asInstanceOf[List[CaseDef]] @@ -1692,10 +1693,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] = - var caseCtx = ctx.unsafeBox + @uncheckedCaptures var caseCtx = ctx cases.mapconserve { cas => - val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx.unsafeUnbox) - caseCtx = Nullables.afterPatternContext(sel, case1.pat).unsafeBox + val case1 = typedCase(cas, sel, wideSelType, pt)(using caseCtx) + caseCtx = Nullables.afterPatternContext(sel, case1.pat) case1 } diff --git a/tests/pos/functorial-functors.scala b/tests/pos/functorial-functors.scala new file mode 100644 index 000000000000..5e810fa989a6 --- /dev/null +++ b/tests/pos/functorial-functors.scala @@ -0,0 +1,14 @@ +class Common: + + trait Functor: + type F[X] + extension [A](x: F[A]) def map[B](f: A => B): F[B] + + trait Monad extends Functor: + extension [A](x: F[A]) + def flatMap[B](f: A => F[B]): F[B] + def map[B](f: A => B) = x.flatMap(f `andThen` pure) + + def pure[A](x: A): F[A] +end Common + diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index 63aee49f8454..efd331604fbe 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -3,7 +3,7 @@ package strawman.collections import Predef.{augmentString as _, wrapString as _, *} import scala.reflect.ClassTag -import annotation.unchecked.uncheckedVariance +import annotation.unchecked.{uncheckedVariance, uncheckedCaptures} import annotation.tailrec /** A strawman architecture for new collections. It contains some @@ -215,7 +215,7 @@ object CollectionStrawMan5 { } def length: Int = if (isEmpty) 0 else 1 + tail.length - protected[this] def newBuilder = new ListBuffer[A] @uncheckedVariance + protected[this] def newBuilder = new ListBuffer[A @uncheckedVariance @uncheckedCaptures] def ++:[B >: A](prefix: List[B]): List[B] = if (prefix.isEmpty) this else Cons(prefix.head, prefix.tail ++: this) @@ -227,7 +227,7 @@ object CollectionStrawMan5 { if (n > 0) tail.drop(n - 1) else this } - case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance @uncheckedCaptures]) // sound because `next` is used only locally extends List[A] { override def isEmpty = false override def head = x @@ -244,17 +244,17 @@ object CollectionStrawMan5 { type C[X] = List[X] def fromIterable[B](coll: Iterable[B]^): List[B] = coll match { case coll: List[B] => coll - case _ => ListBuffer.fromIterable(coll).result + case _ => ListBuffer.fromIterable[B @uncheckedCaptures](coll).result } } /** Concrete collection type: ListBuffer */ - class ListBuffer[A] extends Seq[A] with SeqLike[A] with Builder[A, List[A]] { + class ListBuffer[sealed A] extends Seq[A] with SeqLike[A] with Builder[A, List[A]] { type C[X] = ListBuffer[X] private var first, last: List[A] = Nil private var aliased = false def iterator = first.iterator - def fromIterable[B](coll: Iterable[B]^): ListBuffer[B] = ListBuffer.fromIterable(coll) + def fromIterable[sealed B](coll: Iterable[B]^): ListBuffer[B] = ListBuffer.fromIterable(coll) def apply(i: Int) = first.apply(i) def length = first.length @@ -288,7 +288,7 @@ object CollectionStrawMan5 { object ListBuffer extends SeqFactory { type C[X] = ListBuffer[X] - def fromIterable[B](coll: Iterable[B]^): ListBuffer[B] = new ListBuffer[B] ++= coll + def fromIterable[sealed B](coll: Iterable[B]^): ListBuffer[B] = new ListBuffer[B] ++= coll } /** Concrete collection type: ArrayBuffer */ diff --git a/tests/run-custom-args/captures/colltest5/Test_2.scala b/tests/run-custom-args/captures/colltest5/Test_2.scala index fbb22039c327..5ea259b965ed 100644 --- a/tests/run-custom-args/captures/colltest5/Test_2.scala +++ b/tests/run-custom-args/captures/colltest5/Test_2.scala @@ -64,7 +64,7 @@ object Test { def viewOps(xs: View[Int]^{cap}) = { val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[viewOps]} Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => Cons(x, Cons(-x, Nil)) val x1 = xs.foldLeft("")(strPlusInt)