From fce355eefea0991b67cb24e69a5bb68ccfc5c437 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Sep 2023 09:14:04 +0200 Subject: [PATCH 001/117] Refine "cannot have inferred type" test --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b9904db4ea41..b0dcf4250a93 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1238,7 +1238,7 @@ class CheckCaptures extends Recheck, SymTransformer: if !canUseInferred then val inferred = t.tpt.knownType def checkPure(tp: Type) = tp match - case CapturingType(_, refs) + case CapturingType(_, refs: CaptureSet.Var) if !refs.elems.filter(isNotPureThis).isEmpty => val resultStr = if t.isInstanceOf[DefDef] then " result" else "" report.error( From 1f94a6841f252c3e1c7117ed5dbb4d3bfebd39d3 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Sep 2023 18:19:33 +0200 Subject: [PATCH 002/117] Run Setup code one phase before CheckCaptures This is needed if we want to do setup transforms as part of denotation transformers. --- compiler/src/dotty/tools/dotc/Compiler.scala | 5 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 158 ++-- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 7 +- .../dotty/tools/dotc/cc/CapturingType.scala | 58 +- .../dotty/tools/dotc/cc/CheckCaptures.scala | 118 ++- .../dotty/tools/dotc/cc/RetainingType.scala | 34 + compiler/src/dotty/tools/dotc/cc/Setup.scala | 778 ++++++++++-------- .../src/dotty/tools/dotc/cc/Synthetics.scala | 2 +- .../dotty/tools/dotc/config/Printers.scala | 7 +- .../tools/dotc/config/ScalaSettings.scala | 3 + .../dotty/tools/dotc/core/Definitions.scala | 12 +- .../src/dotty/tools/dotc/core/Flags.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 6 +- .../src/dotty/tools/dotc/core/Types.scala | 16 +- .../tools/dotc/printing/PlainPrinter.scala | 27 +- .../dotty/tools/dotc/transform/Recheck.scala | 28 +- .../captures/box-adapt-boxing.scala | 4 +- tests/neg-custom-args/captures/lazylist.check | 4 +- .../neg-custom-args/captures/lazylists2.check | 6 +- tests/neg-custom-args/captures/real-try.check | 2 +- .../captures/usingLogFile.check | 2 +- tests/neg-custom-args/captures/vars.check | 2 +- tests/pos-custom-args/captures/pairs.scala | 3 +- 24 files changed, 729 insertions(+), 557 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/cc/RetainingType.scala 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/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index dfaa3c701576..b82bf795338e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -7,7 +7,7 @@ import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* import ast.{tpd, untpd} import Decorators.*, NameOps.* import config.SourceVersion -import config.Printers.capt +import config.Printers.{capt, ccSetup} import util.Property.Key import tpd.* import StdNames.nme @@ -28,23 +28,22 @@ private val adaptUnpickledFunctionTypes = false */ private val constrainRootsWhenMapping = true -/** The arguments of a @retains or @retainsByName annotation */ -private[cc] def retainedElems(tree: Tree)(using Context): List[Tree] = tree match - case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems - case _ => Nil - 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 -/** Capture checking state, which is stored in a context property */ +/** Capture checking state, which is known to other capture checking components */ class CCState: + /** Temporary set indicating closures that are the rhs of a val or def. + * An entry gets removed when we check isLevelOwner on it. + */ val rhsClosure: mutable.HashSet[Symbol] = new mutable.HashSet - val levelOwners: mutable.HashSet[Symbol] = new mutable.HashSet + /** Cache for level ownership */ + val isLevelOwner: mutable.HashMap[Symbol, Boolean] = new mutable.HashMap /** Associates certain symbols (the nesting level owners) with their ccNestingLevel */ val nestingLevels: mutable.HashMap[Symbol, Int] = new mutable.HashMap @@ -61,13 +60,12 @@ class CCState: * 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() +end CCState /** 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 @@ -98,6 +96,8 @@ class mapRoots(from: CaptureRoot, to: CaptureRoot)(using Context) extends BiType && CaptureRoot.isEnclosingRoot(t, from) => to case from: CaptureRoot.Var if from.followAlias eq t => to case _ => t + case t @ Setup.Box(t1) => + t.derivedBox(this(t1)) case _ => mapOverFollowingAliases(t) @@ -123,11 +123,16 @@ 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. */ @@ -176,8 +181,10 @@ extension (tp: Type) 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) + def boxedUnlessFun(tycon: Type)(using Context) = // TODO: drop + if ctx.phase != Phases.checkCapturesPhase + && ctx.phase != Phases.checkCapturesPhase.prev + || defn.isFunctionSymbol(tycon.typeSymbol) then tp else tp.boxed @@ -247,16 +254,6 @@ extension (tp: Type) 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 +273,20 @@ extension (tp: Type) case _ => false -extension (cls: ClassSymbol) + def isCapabilityClassRef(using Context) = tp match + case _: TypeRef | _: AppliedType => tp.typeSymbol.hasAnnotation(defn.CapabilityAnnot) + case _ => false + +extension (cls: Symbol) def pureBaseClass(using Context): Option[Symbol] = - cls.baseClasses.find(bc => + if cls.isClass then cls.asClass.baseClasses.find: bc => defn.pureBaseClasses.contains(bc) - || { - val selfType = bc.givenSelfType - selfType.exists && selfType.captureSet.isAlwaysEmpty - }) + || bc.givenSelfType.dealiasKeepAnnots.match + case CapturingType(_, refs) => refs.isAlwaysEmpty + case RetainingType(_, refs) => refs.isEmpty // TODO: Better: test at phase cc instead? + case selfType => selfType.exists && selfType.captureSet.isAlwaysEmpty + else None extension (sym: Symbol) @@ -330,7 +332,53 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox - def isLevelOwner(using Context): Boolean = ccState.levelOwners.contains(sym) + // Not yet needed + 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 _ => annot.tree.retainedElems.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 + + def isLevelOwner(using Context): Boolean = + val symd = sym.denot + def isCaseClassSynthetic = // TODO drop + symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty + def compute = + if symd.isClass then + symd.is(CaptureChecked) || symd.isRoot + else + symd.is(Method) + && (!symd.owner.isClass || symd.owner.is(CaptureChecked)) + && !Synthetics.isExcluded(sym) + && !isCaseClassSynthetic + && !symd.isConstructor + && (!symd.isAnonymousFunction + || ccState.rhsClosure.remove(sym) + || sym.definedLocalRoot.exists // TODO drop + ) + ccState.isLevelOwner.getOrElseUpdate(sym, compute) /** The owner of the current level. Qualifying owners are * - methods other than constructors and anonymous functions @@ -344,6 +392,24 @@ extension (sym: Symbol) else if sym.isLevelOwner then sym else sym.owner.levelOwner + /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. + * If name refers to a val that has a closure as rhs, we return the closure as level + * owner. + */ + def levelOwnerNamed(name: String)(using Context): Symbol = + def recur(owner: Symbol, prev: Symbol): Symbol = + if owner.name.toString == name then + if owner.isLevelOwner then owner + else if owner.isTerm && !owner.isOneOf(Method | Module) && prev.exists then prev + else NoSymbol + else if owner == defn.RootClass then + NoSymbol + else + val prev1 = if owner.isAnonymousFunction && owner.isLevelOwner then owner else NoSymbol + recur(owner.owner, prev1) + recur(sym, NoSymbol) + .showing(i"find outer $sym [ $name ] = $result", capt) + /** The nesting level of `sym` for the purposes of `cc`, * -1 for NoSymbol */ @@ -358,7 +424,9 @@ extension (sym: Symbol) * a capture checker is running. */ def ccNestingLevelOpt(using Context): Option[Int] = - if ctx.property(ccStateKey).isDefined then Some(ccNestingLevel) else None + if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev + then Some(ccNestingLevel) + else None /** The parameter with type caps.Cap in the leading term parameter section, * or NoSymbol, if none exists. @@ -379,24 +447,6 @@ extension (sym: Symbol) else newRoot ccState.localRoots.getOrElseUpdate(owner, lclRoot) - /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. - * If name refers to a val that has a closure as rhs, we return the closure as level - * owner. - */ - def levelOwnerNamed(name: String)(using Context): Symbol = - def recur(owner: Symbol, prev: Symbol): Symbol = - if owner.name.toString == name then - if owner.isLevelOwner then owner - else if owner.isTerm && !owner.isOneOf(Method | Module) && prev.exists then prev - else NoSymbol - else if owner == defn.RootClass then - NoSymbol - else - val prev1 = if owner.isAnonymousFunction && owner.isLevelOwner then owner else NoSymbol - recur(owner.owner, prev1) - recur(sym, NoSymbol) - .showing(i"find outer $sym [ $name ] = $result", capt) - def maxNested(other: Symbol)(using Context): Symbol = if sym.ccNestingLevel < other.ccNestingLevel then other else sym /* does not work yet, we do mix sets with different levels, for instance in cc-this.scala. @@ -425,8 +475,10 @@ 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) + def boxedUnlessFun(tycon: Type)(using Context) = // TODO drop + if ctx.phase != Phases.checkCapturesPhase + && ctx.phase != Phases.checkCapturesPhase.prev + || defn.isFunctionClass(tycon.typeSymbol) then ts else ts.mapconserve(_.boxed) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 5fdbea2a1bc6..026f02872086 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -200,7 +200,7 @@ sealed abstract class CaptureSet extends Showable: case Nil => addDependent(that) recur(elems.toList) - .showing(i"subcaptures $this <:< $that = ${result.show}", capt) + //.showing(i"subcaptures $this <:< $that = ${result.show}", capt) /** Two capture sets are considered =:= equal if they mutually subcapture each other * in a frozen state. @@ -1035,10 +1035,7 @@ object CaptureSet: def levelErrors: Addenda = new Addenda: override def toAdd(using Context) = - for - state <- ctx.property(ccStateKey).toList - (ref, cs) <- state.levelError - yield + for (ref, cs) <- ccState.levelError.toList yield val levelStr = ref match case ref: (TermRef | ThisType) => i", defined at level ${ref.ccNestingLevel}" case _ => "" diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index a7c283f4cc3b..95ca10cf3674 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -25,28 +25,44 @@ 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 capability class ecpansion if they are refined with another capturing type + * - fuses compatible capturiong 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 ctx.phaseId <= Phases.checkCapturesPhase.id => + Some((parent, ann.refs)) + case AnnotatedType(parent, ann) + if ann.symbol == defn.RetainsAnnot && ctx.phase == Phases.checkCapturesPhase => + 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 @@ -55,28 +71,10 @@ object CapturingType: * capture sets may be thrown away in the computed base type. */ def isUncachable(tp: Type)(using Context): Boolean = - ctx.phase == Phases.checkCapturesPhase && - (Setup.isDuringSetup || ctx.mode.is(Mode.IgnoreCaptures) && tp.isEventuallyCapturingType) + ctx.phase == Phases.checkCapturesPhase + && (Setup.isDuringSetup + || 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 b0dcf4250a93..4374cd1e3f27 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -27,26 +27,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, @@ -140,7 +120,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 @@ -152,36 +132,43 @@ object CheckCaptures: 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. + * 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. */ - def warnIfRedundantCaptureSet(ann: Tree, tpt: Tree)(using Context): Unit = - var retained = retainedElems(ann).toArray + def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + val normCap = new TypeMap: + def apply(t: Type): Type = t match + case t: TermRef if t.isGenericRootCapability => ctx.owner.localRoot.termRef + case _ => t + + var retained = ann.retainedElems.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 + + 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 = + val remaining = others.map(normCap) + if remaining.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) + check(parent.captureSet, parent) + + 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) + check(remaining, remaining) + end for + end checkWellformedPost /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -198,13 +185,11 @@ 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.* @@ -1050,20 +1035,24 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) - private var setup: Setup = compiletime.uninitialized + private var setup: SetupAPI = compiletime.uninitialized 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 = thisPhase.prev.asInstanceOf[Setup] + setup.setupUnit(ctx.compilationUnit.tpdTree, recheckDef) + + 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 @@ -1207,13 +1196,10 @@ class CheckCaptures extends Recheck, SymTransformer: 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 _ => - } + tree.tpe.foreachPart: + case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => + checkWellformedPost(parent, annot.tree, tree) + case _ => case t: ValOrDefDef if t.tpt.isInstanceOf[InferredTypeTree] && !Synthetics.isExcluded(t.symbol) => val sym = t.symbol 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..5af59b193aa3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala @@ -0,0 +1,34 @@ +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 => + new Error(i"bad retains $tp").printStackTrace + assert(false) + 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..393616814cf8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -8,12 +8,53 @@ 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 config.Printers.{capt, ccSetup} +import ast.tpd, tpd.* +import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.IdentityCaptRefMap import Synthetics.isExcluded import util.Property +import printing.{Printer, Texts}, Texts.{Text, Str} + +/** Operations accessed from CheckCaptures */ +trait SetupAPI: + type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Unit + def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + def decorate(tp: Type, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type + +object Setup: + def enabled(using Context) = true // ctx.settings.YccNew.value // if new impl is conditional + + private val IsDuringSetupKey = new Property.Key[Unit] + + def isDuringSetup(using Context): Boolean = + ctx.property(IsDuringSetupKey).isDefined + //|| ctx.phase == Phases.checkCapturesPhase.prev + + case class Box(t: Type) extends UncachedGroundType, TermType: + override def fallbackToText(printer: Printer): Text = + Str("Box(") ~ printer.toText(t) ~ ")" + def derivedBox(t1: Type): Type = + if t1 eq t then this else Box(t1) + + trait DepFunSpecializedTypeMap(using Context) extends TypeMap: + override def mapOver(tp: Type) = tp match + case defn.RefinedFunctionOf(rinfo: MethodOrPoly) => + val rinfo1 = this(rinfo) + if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true) + else tp + case _ => + super.mapOver(tp) + + /** 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,12 +68,49 @@ 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.* +class Setup extends PreRecheck, SymTransformer, SetupAPI: + thisPhase => + + override def isRunnable(using Context) = + super.isRunnable && Feature.ccEnabledSomewhere + + private def toCC(sym: Symbol)(using Context) = new FollowAliases: + def apply(t: Type): Type = + mapOverFollowingAliases(t) + + 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 + + /** - 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(symd: SymDenotation)(using Context): SymDenotation = + def needsTransform = symd.owner.isTerm || symd.owner.is(CaptureChecked) + def needsInfoTransform = symd.isGetter + if !pastRecheck && Feature.ccEnabledSomewhere then + val sym = symd.symbol + atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed + // if sym is class && level owner: add a capture root + // translate cap/capIn to local roots + // what are local roots? should we add them in CCState? + // create local roots if necessary + if Synthetics.needsTransform(symd) then + Synthetics.transform(symd) + else + val newFlags = newFlagsFor(symd) + val newInfo = + if needsInfoTransform then transformExplicitType(sym.info, mapRoots = false) + else /*toCC(sym)*/(sym.info) + + if newFlags != symd.flags || (newInfo ne sym.info) + then symd.copySymDenotation(initFlags = newFlags, info = newInfo) + else symd + else symd /** Create dependent function with underlying function class `tycon` and given * arguments `argTypes` and result `resType`. @@ -102,15 +180,13 @@ extends tpd.TreeTraverser: 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. + if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) => cls.paramGetters.foldLeft(tp) { (core, getter) => - if getter.termRef.isTracked then + if atPhase(thisPhase.next)(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) + .showing(i"add capture refinement $tp --> $result", ccSetup) else core } @@ -122,7 +198,8 @@ extends tpd.TreeTraverser: private def mapNested(ts: List[Type]): List[Type] = val saved = isTopLevel isTopLevel = false - try ts.mapConserve(this) finally isTopLevel = saved + try ts.mapConserve(this) + finally isTopLevel = saved def apply(tp: Type) = val tp1 = tp match @@ -144,13 +221,13 @@ extends tpd.TreeTraverser: val res1 = this(res0) if isTopLevel then depFun(tycon1, args1, res1) - .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", capt) + .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", ccSetup) 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))) + 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) @@ -161,103 +238,134 @@ extends tpd.TreeTraverser: resType = this(tp.resType)) case tp: TypeLambda => // Don't recurse into parameter bounds, just cleanup any stray retains annotations + // !!! TODO we should also map roots to rootvars here tp.derivedLambdaType( paramInfos = tp.paramInfos.mapConserve(cleanup(_).bounds), resType = this(tp.resType)) + case Box(tp1) => + box(this(tp1)) case _ => mapOver(tp) - addVar(addCaptureRefinements(tp1), ctx.owner, mapRoots) + addVar(addCaptureRefinements(normalizeCaptures(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 + private def transformInferredType(tp: Type, mapRoots: Boolean)(using Context): Type = + mapInferred(mapRoots)(tp) + + private def transformExplicitType(tp: Type, mapRoots: Boolean)(using Context): Type = + val expandAliases = new TypeMap: + 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(tp: Type, encl: List[MethodType] = Nil): 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 + + /** 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) = + 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: TypeRef => + if t.symbol.isClass then t + else t.info match + case TypeAlias(alias) => + val transformed = + if t.symbol.isStatic then this(alias) + else transformExplicitType(alias, mapRoots)( + using ctx.withOwner(t.symbol.owner)) + if transformed ne alias then transformed else t + //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") + case _ => + recur(t) + case t @ AppliedType(tycon: TypeProxy, args) if true => + tycon.underlying match + case TypeAlias(aliasTycon) => + val args1 = + if defn.isFunctionClass(t.dealias.typeSymbol) then args + else args.map(Box(_)) + val alias = aliasTycon.applyIfParameterized(args1) + val transformed = this(alias) + if transformed ne alias then + //println(i"APP ALIAS $t / $alias / $transformed") + transformed + else + //println(i"NO ALIAS $t / $alias / $transformed") + recur(t) + case _ => + recur(t) + case t @ CapturingType(parent, refs) => + checkQualifiedRoots(t.annot.tree) // TODO: NEEDED? + t.derivedCapturingType(this(parent), refs) + case t @ AnnotatedType(parent, ann) => + if ann.symbol == defn.RetainsAnnot then + checkQualifiedRoots(ann.tree) + CapturingType(this(parent), ann.tree.toCaptureSet) + else + t.derivedAnnotatedType(this(parent), ann) + case Box(t1) => + box(this(t1)) + case t: LazyRef => + val t1 = this(t.ref) + if t1 ne t.ref then t1 else t + case t: TypeVar => + this(t.underlying) + case _ => + recur(t) + end expandAliases - /** 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 + if tp2 ne tp then ccSetup.println(i"expanded: $tp --> $tp1 --> $tp2") + tp2 + 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 = if !tree.hasRememberedType then + val tp = if boxed then Box(tree.tpe) else tree.tpe tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tree.tpe, boxed, mapRoots) - else transformExplicitType(tree.tpe, boxed, mapRoots)) + then transformInferredType(tp, mapRoots) + else transformExplicitType(tp, mapRoots)) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -299,272 +407,244 @@ extends tpd.TreeTraverser: /** 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 = + def newOwnerFor(sym: Symbol)(using Context): Symbol = var octx = ctx while octx.owner == sym do octx = octx.outer - if octx.owner.name == nme.TRY_BLOCK then octx.owner else null + if octx.owner.name == nme.TRY_BLOCK then octx.owner else sym.maybeOwner /** Update info of `sym` for CheckCaptures phase only */ private def updateInfo(sym: Symbol, info: Type)(using Context) = - sym.updateInfo(preRecheckPhase, info, newOwnerFor(sym)) + sym.updateInfo(thisPhase, info, newFlagsFor(sym), newOwnerFor(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) + updateInfo(sym, sym.info) + + extension (sym: Symbol) def nextInfo(using Context): Type = + atPhase(thisPhase.next)(sym.info) + + def setupTraverser(recheckDef: DefRecheck) = new TreeTraverser: + + 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) + 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(transformExplicitType(tree.tpe, mapRoots = false)) + case _ => false + + val sym = tree.symbol + val mapRoots = + // need to run before cc so that rhsClosure is defined without transforming + // symbols to cc, since rhsClosure is used in that transformation. + 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 + ) + ccSetup.println(i"mapped $tree = ${tpt.knownType}") 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 + 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 => + inContext(ctx.withOwner(tree.symbol.owner)): + traverseChildren(tree) + case tree: Try if Feature.enabled(Feature.saferExceptions) => + val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType)) + ccState.isLevelOwner(tryOwner) = true + 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 + ) + case tree: ValOrDefDef => 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 + + /** 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 _ => - 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) + info - 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 - ) - case tree: ValOrDefDef => - val sym = tree.symbol - - /** The return type of a constructor instantiated with local type and value - * parameters. Constructors have `unit` result type, that's why we can't - * get this type by reading the result type tree, and have to construct it - * explicitly. - */ - def constrReturnType(info: Type, psymss: List[List[Symbol]]): Type = info match - case info: MethodOrPoly => - constrReturnType(info.instantiate(psymss.head.map(_.namedType)), psymss.tail) - case _ => - info - - /** The local result type, which is the known type of the result type tree, - * with special treatment for constructors. - */ - def localReturnType = - if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) - else tree.tpt.knownType - - // Replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the - // capture sets of the types of the method's parameter symbols and result type. - def integrateRT( - info: Type, // symbol info to replace - psymss: List[List[Symbol]], // the local (type and term) parameter symbols corresponding to `info` - prevPsymss: List[List[Symbol]], // the local parameter symbols seen previously in reverse order - prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order - ): Type = - info match - case mt: MethodOrPoly => - val psyms = psymss.head - val mapr = - if sym.isLevelOwner then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) - else identity[Type] - mt.companion(mt.paramNames)( - mt1 => - if !psyms.exists(_.isUpdatedAfter(preRecheckPhase)) && !mt.isParamDependent && prevLambdas.isEmpty then - mt.paramInfos - else - val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => mapr(subst(psym.info)).asInstanceOf[mt.PInfo]), - mt1 => - mapr(integrateRT(mt.resType, psymss.tail, psyms :: prevPsymss, mt1 :: prevLambdas)) - ) - case info: ExprType => - info.derivedExprType(resType = - integrateRT(info.resType, psymss, prevPsymss, prevLambdas)) - case info => - if prevLambdas.isEmpty then localReturnType - else SubstParams(prevPsymss, prevLambdas)(localReturnType) - - def signatureChanges = - tree.tpt.hasRememberedType && !sym.isConstructor - || tree.match + /** 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.termParamss.nestedExists(_.tpt.hasRememberedType) case _ => false - if sym.exists && signatureChanges then - val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) - .showing(i"update info $sym: ${sym.info} = $result", capt) - if newInfo ne sym.info then - updateInfo(sym, - if sym.isAnonymousFunction then - // closures are handled specially; the newInfo is constrained from - // the expected type and only afterwards we recheck the definition - newInfo - else new LazyType: - def complete(denot: SymDenotation)(using Context) = - // infos of other methods are determined from their definitions which - // are checked on demand - denot.info = newInfo - recheckDef(tree, sym)) - else updateOwner(sym) - else if !sym.is(Module) then updateOwner(sym) // Modules are updated with their module classes - - case tree: Bind => - val sym = tree.symbol - updateInfo(sym, transformInferredType(sym.info, boxed = false, mapRoots = true)) - case tree: TypeDef => - tree.symbol match - case cls: ClassSymbol => - val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - val newSelfType = - if (selfInfo eq NoType) || cls.is(ModuleClass) && !cls.isStatic then - // add capture set to self type of nested classes if no self type is given explicitly. - // 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 + 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` + 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 !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then + mt.paramInfos + else + val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) + psyms.map(psym => mapr(subst(psym.nextInfo)).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) + + if sym.exists && signatureChanges then + val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) + .showing(i"update info $sym: ${sym.info} = $result", ccSetup) + if newInfo ne sym.info then + updateInfo(sym, + if sym.isAnonymousFunction || sym.is(Param) || sym.is(ParamAccessor) 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(!isDuringSetup, i"$sym") + assert(ctx.phase == thisPhase.next, i"$sym") + ccSetup.println(i"FORCING $sym") + 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, mapRoots = true)) + case tree: TypeDef => + tree.symbol match + case cls: ClassSymbol => + val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo + val newSelfType = + if (selfInfo eq NoType) || cls.is(ModuleClass) && !cls.isStatic then + // add capture set to self type of nested classes if no self type is given explicitly. + // 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, mapRoots = true) + case _ => + NoType + if newSelfType.exists then + ccSetup.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 = tree.symbol.info + val newInfo = transformExplicitType(info, mapRoots = !ctx.owner.isStaticOwner) + updateInfo(tree.symbol, newInfo) + if newInfo ne info then + ccSetup.println(i"update info of ${tree.symbol} from $info to $newInfo") + 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 @@ -581,7 +661,7 @@ extends tpd.TreeTraverser: superTypeIsImpure(tp.tp1) && superTypeIsImpure(tp.tp2) case _ => false - }.showing(i"super type is impure $tp = $result", capt) + }.showing(i"super type is impure $tp = $result", ccSetup) /** Should a capture set variable be added on type `tp`? */ def needsVariable(tp: Type)(using Context): Boolean = { @@ -591,7 +671,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) => @@ -632,6 +712,8 @@ 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: LazyRef => normalizeCaptures(tp.ref) case _ => @@ -648,12 +730,12 @@ 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 = transformExplicitType(dealiased, mapRoots) + // !!! TODO: We should map roots to root vars here + 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`. @@ -664,12 +746,8 @@ extends tpd.TreeTraverser: 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).withProperty(IsDuringSetupKey, Some(()))) -object Setup: - val IsDuringSetupKey = new Property.Key[Unit] - - def isDuringSetup(using Context): Boolean = - ctx.property(IsDuringSetupKey).isDefined end Setup \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 1509fd838265..f59ac80ef1b5 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -99,7 +99,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 RetainingType(parent, _) => transformDefaultGetterCaptures(parent, owner, idx) case info @ AnnotatedType(parent, annot) => info.derivedAnnotatedType(transformDefaultGetterCaptures(parent, owner, idx), annot) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 63d616e1ce3d..526c98eb20b8 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 { @@ -13,6 +15,9 @@ object Printers { val default = new Printer val capt = noPrinter + val ccSetupOn = new Printer + def ccSetup(using Context): Printer = if ctx.settings.YccTest.value then ccSetupOn else noPrinter + 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..ca5fac207f3f 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, debug info for captured references") + val YccTest: Setting[Boolean] = BooleanSetting("-Ycc-test", "Used in conjunction with captureChecking language import, debug info for captured references") + val YccPrintSetup: Setting[Boolean] = BooleanSetting("-Ycc-print-setup", "") /** 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/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index edd054375b05..ae1e2eb2b4e5 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,7 @@ 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 expandedUniversalSet: CaptureSet = CaptureSet(captureRoot.termRef) @tu lazy val PureClass: Symbol = requiredClass("scala.Pure") @@ -1249,8 +1251,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/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8df809dc9ee6..97277eeb6600 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -641,7 +641,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def compareRefined: Boolean = val tp1w = tp1.widen - if ctx.phase == Phases.checkCapturesPhase then + if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then // A relaxed version of subtyping for dependent functions where method types // are treated as contravariant. @@ -2096,7 +2096,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling ExprType(info1.resType) case info1 => info1 - if ctx.phase == Phases.checkCapturesPhase then + if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then // When comparing against a RefiningVar refinement, map the // localRoot of the corresponding class in `tp1` to the owner of the // refining capture set. @@ -2220,7 +2220,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 ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then // allow to constrain capture set variables isSubType(formal2a, formal1) else diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 0f1aed020c6d..a2bae7275c2b 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, boxedUnlessFun, ccNestingLevel} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -2933,12 +2933,10 @@ object Types { 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 + val owner = symbol.maybeOwner + def normOwner = if owner.isLocalDummy then owner.owner else owner + if name == nme.LOCAL_CAPTURE_ROOT then normOwner + else if info.isRef(defn.Caps_Cap) && owner.isTerm then normOwner else NoSymbol override def normalizedRef(using Context): CaptureRef = @@ -5124,7 +5122,9 @@ object Types { else if (clsd.is(Module)) givenSelf else if (ctx.erasedTypes) appliedRef else givenSelf.dealiasKeepAnnots match - case givenSelf1 @ EventuallyCapturingType(tp, _) => + case givenSelf1 @ CapturingType(tp, _) => + givenSelf1.derivedAnnotatedType(tp & appliedRef, givenSelf1.annot) + case givenSelf1 @ RetainingType(tp, _) => givenSelf1.derivedAnnotatedType(tp & appliedRef, givenSelf1.annot) case _ => AndType(givenSelf, appliedRef) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 83ff03e05592..17bdee3dbc1a 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, CaptureRoot, isBoxed, ccNestingLevel, levelOwner, retainedElems} class PlainPrinter(_ctx: Context) extends Printer { @@ -161,8 +161,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. */ @@ -230,7 +242,7 @@ 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 showAsCap = rootsInRefs match @@ -249,6 +261,11 @@ class PlainPrinter(_ctx: Context) extends Printer { 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 +288,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)) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 306ca2b0eb9c..47b277d06321 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -17,7 +17,7 @@ import typer.TypeAssigner.seqLitType import typer.ConstFold import typer.ErrorReporting.{Addenda, NothingToAdd} import NamerOps.methodType -import config.Printers.recheckr +import config.Printers.{recheckr, ccSetup, noPrinter} import util.Property import StdNames.nme import reporting.trace @@ -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) @@ -526,12 +522,14 @@ abstract class Recheck extends Phase, SymTransformer: tpe def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = - trace(i"rechecking $tree with pt = $pt", recheckr, show = true) { + def op = try recheckFinish(recheckStart(tree, pt), tree, pt) catch case ex: Exception => println(i"error while rechecking $tree") throw ex - } + if ccSetup eq noPrinter then op + else trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): + op /** Typing and previous transforms sometimes leaves skolem types in prefixes of * NamedTypes in `expected` that do not match the `actual` Type. -Ycheck does @@ -582,7 +580,7 @@ abstract class Recheck extends Phase, SymTransformer: err.typeMismatch(tree.withType(actual), expected, addenda) else if debugSuccesses then tree match - case _: Ident => + case closureDef(_) => println(i"SUCCESS $tree:\n${TypeComparer.explained(_.isSubType(actual, expected))}") case _ => end checkConformsExpr diff --git a/tests/neg-custom-args/captures/box-adapt-boxing.scala b/tests/neg-custom-args/captures/box-adapt-boxing.scala index 4b631472bad4..b6eb59aef593 100644 --- a/tests/neg-custom-args/captures/box-adapt-boxing.scala +++ b/tests/neg-custom-args/captures/box-adapt-boxing.scala @@ -17,7 +17,7 @@ def main(io: Cap^, fs: Cap^): Unit = { type Op0[X] = Box[X] -> Unit type Op1[X] = Unit -> Box[X] val f: Unit -> (Cap^{io}) -> Unit = ??? - val test: Op1[Op0[Cap^{io}]^{io}]^{} = f + val test: Op1[Op0[Cap^{io}]^{io}]^{} = f // error ??? not sure this is correct // expected: {} Unit -> box {io} (box {io} Cap) -> Unit // actual: Unit -> ({io} Cap) -> Unit // @@ -33,6 +33,6 @@ def main(io: Cap^, fs: Cap^): Unit = { type Op[X] = Unit -> Box[X] val f: Unit -> (Cap^{io}) -> Unit = ??? val g: Op[Id[Cap^{io}]^{fs}] = f // error - val h: Op[Id[Cap^{io}]^{io}] = f + val h: Op[Id[Cap^{io}]^{io}] = f // error ??? not sure this is correct } } diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index aeb410f07d65..ba5080d808f8 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 ------------------------------------- diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index 5038ab1bea93..70a94edcd31a 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 @@ -33,7 +33,7 @@ -- [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/real-try.check b/tests/neg-custom-args/captures/real-try.check index f57aef60745b..ba58b000c8b8 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -28,7 +28,7 @@ 31 | Cell(() => foo(1))// // error | ^^^^^^^^^^^^^^^^^^ | Found: Cell[box () => Unit]^? - | Required: Cell[() ->? Unit]^? + | Required: Cell[box () ->? 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 diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 9550b5864586..f4de75efc493 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -7,7 +7,7 @@ 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 ------------------------------------------------------ diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index f8abe22e9d53..f589883f6aab 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -21,7 +21,7 @@ 26 | b = List(g) // error | ^^^^^^^ | Found: List[box (x$0: String) ->{cap3} String] - | Required: List[String ->{cap[test]} String] + | Required: List[box String ->{cap[test]} String] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:30:10 ----------------------------------------- diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index 78991e4377c0..e722e2fa74bd 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: From 5c6a7fd855d9a1da34a214b3351f788e9644e607 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Sep 2023 18:27:57 +0200 Subject: [PATCH 003/117] Don't shorten local roots when printing --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 5 +++-- tests/neg-custom-args/captures/byname.check | 2 +- tests/neg-custom-args/captures/capt1.check | 8 ++++---- tests/neg-custom-args/captures/cc-this.check | 4 ++-- tests/neg-custom-args/captures/cc-this2.check | 2 +- tests/neg-custom-args/captures/cc-this3.check | 2 +- tests/neg-custom-args/captures/eta.check | 4 ++-- .../captures/exception-definitions.check | 4 ++-- tests/neg-custom-args/captures/i15772.check | 12 ++++++------ .../captures/lazylists-exceptions.check | 4 ++-- tests/neg-custom-args/captures/lazyref.check | 6 +++--- tests/neg-custom-args/captures/leaked-curried.check | 2 +- tests/neg-custom-args/captures/levels.check | 2 +- tests/neg-custom-args/captures/real-try.check | 12 ++++++------ tests/neg-custom-args/captures/try.check | 2 +- tests/neg-custom-args/captures/vars.check | 4 ++-- 16 files changed, 38 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 17bdee3dbc1a..c2b9bb677a8c 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -49,9 +49,10 @@ class PlainPrinter(_ctx: Context) extends Printer { 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. + * For now it is better to turn this off, so that we get the best info for diagnosis. + * TODO: we should consider dropping this switch once we implemented disambiguation of capture roots. */ - private val shortenCap = true + private val shortenCap = false def homogenize(tp: Type): Type = if (homogenizedView) diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 61b83fc24688..781b26b6944d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ - | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} + | reference (cap2 : Cap^{cap[test2]}) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ?->{cap1} I diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 124bd61a0109..93b263aacba6 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:4:11 -------------------------------------------------------------- 4 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | (x : C^{cap[f]}) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C -- Error: tests/neg-custom-args/captures/capt1.scala:7:11 -------------------------------------------------------------- 7 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | (x : C^{cap[g]}) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:14:2 ----------------------------------------- 14 | def f(y: Int) = if x == null then y else y // error @@ -36,5 +36,5 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:32:30 ------------------------------------------------------------- 32 | val z2 = h[() -> Cap](() => x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ + | (x : C^{cap[foo]}) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^{cap[foo]} diff --git a/tests/neg-custom-args/captures/cc-this.check b/tests/neg-custom-args/captures/cc-this.check index 335302c5c259..798dd81bf6e1 100644 --- a/tests/neg-custom-args/captures/cc-this.check +++ b/tests/neg-custom-args/captures/cc-this.check @@ -8,8 +8,8 @@ -- Error: tests/neg-custom-args/captures/cc-this.scala:10:15 ----------------------------------------------------------- 10 | class C2(val x: () => Int): // error | ^ - | reference (C2.this.x : () => Int) is not included in the allowed capture set {} of the self type of class C2 + |reference (C2.this.x : () ->{cap[C2]} Int) is not included in the allowed capture set {} of the self type of class C2 -- Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ------------------------------------------------------------ 17 | class C4(val f: () => Int) extends C3 // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | reference (C4.this.f : () => Int) is not included in the allowed capture set {} of pure base class class C3 + |reference (C4.this.f : () ->{cap[C4]} Int) is not included in the allowed capture set {} of pure base class class C3 diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index fbf7d5f4d831..14c4c76370a6 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 (cap[D] : 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-this3.check b/tests/neg-custom-args/captures/cc-this3.check index d57471c6872e..2ab46bb677a0 100644 --- a/tests/neg-custom-args/captures/cc-this3.check +++ b/tests/neg-custom-args/captures/cc-this3.check @@ -1,7 +1,7 @@ -- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:8:6 --------------------------------------- 8 |class B extends A: // error | ^ - | illegal inheritance: self type B^ of class B does not conform to self type A^{} + | illegal inheritance: self type B^{cap[B]} of class B does not conform to self type A^{} | of parent class A | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index 382ee177aa91..b7103511cb3a 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 {} - | of an enclosing function literal with expected type () -> box () ->? Unit + | (f : () ->{cap[foo]} Unit) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box () ->? Unit diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 4533cf2c083d..6b676bc59539 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,7 +1,7 @@ -- 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 (cap[Err] : 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 @@ -10,4 +10,4 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:8 ----------------------------------------------- 8 | class Err3(c: Any^) extends Exception // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | reference (Err3.this.c : Any^) is not included in the allowed capture set {} of pure base class class Throwable + |reference (Err3.this.c : Any^{cap[Err3]}) is not included in the allowed capture set {} of pure base class class Throwable diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index cb6b40361add..3b1628b77af1 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,25 +1,25 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ 19 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int + | (x : C^{cap[main1]}) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error | ^^^^^^^ | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[..main1](from instantiating box1), c} Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Required: (C^{cap[main1]} ->{cap[main1]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ 26 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int + | (x : C^{cap[main2]}) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[..main2](from instantiating box2), c} Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Required: (C^{cap[main2]} ->{cap[main2]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 74318b6bb254..57e5e65cd374 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,10 +1,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:37:4 -------------------------- 37 | tabulate(10) { i => // error | ^ - | Found: LazyList[Int]^ + | Found: LazyList[Int]^{cap[]} | Required: LazyList[Int]^? | - | Note that reference (cap : caps.Cap), defined at level 2 + | Note that reference (cap[] : caps.Cap), defined at level 2 | cannot be included in outer capture set ?, defined at level 1 in method problem 38 | if i > 9 then throw Ex1() 39 | i * i diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index 8c91ec13b5d8..a3cf34067b30 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -8,21 +8,21 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:21:35 -------------------------------------- 21 | val ref2c: LazyRef[Int]^{cap2} = ref2 // error | ^^^^ - | Found: (ref2 : LazyRef[Int]{val elem: () => Int}^{cap2, ref1}) + | Found: (ref2 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, ref1}) | Required: LazyRef[Int]^{cap2} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:23:35 -------------------------------------- 23 | val ref3c: LazyRef[Int]^{ref1} = ref3 // error | ^^^^ - | Found: (ref3 : LazyRef[Int]{val elem: () => Int}^{cap2, ref1}) + | Found: (ref3 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, ref1}) | Required: LazyRef[Int]^{ref1} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:25:35 -------------------------------------- 25 | val ref4c: LazyRef[Int]^{cap1} = ref4 // error | ^^^^ - | Found: (ref4 : LazyRef[Int]{val elem: () => Int}^{cap2, cap1}) + | Found: (ref4 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, cap1}) | Required: LazyRef[Int]^{cap1} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 509069797def..1918fecbc496 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -6,6 +6,6 @@ 12 | val get: () ->{} () ->{io} Cap^ = // error | ^ | error overriding value get in trait Box of type () -> () ->{cap[Box]} Cap^{cap[Box]}; - | value get of type () -> () ->{io} Cap^ has incompatible type + | value get of type () -> () ->{io} Cap^{cap[Foo]} has incompatible type | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 8d11c196b10f..2a72dbd354d1 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -4,7 +4,7 @@ | Found: box (x: String) ->{cap3} String | Required: box (x$0: String) ->? String | - | Note that reference (cap3 : CC^), defined at level 2 + | Note that reference (cap3 : CC^{cap[scope]}), defined at level 2 | cannot be included in outer capture set ?, defined at level 1 in method test | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index ba58b000c8b8..c81a30d82918 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -7,30 +7,30 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:19:4 -------------------------------------- 19 | () => foo(1) // error | ^^^^^^^^^^^^ - | Found: () => Unit + | Found: () ->{cap[]} Unit | Required: () ->? Unit | - | Note that reference (cap : caps.Cap), defined at level 2 + | 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]^? + | Found: () ->{cap[]} Cell[Unit]^? | Required: () ->? Cell[Unit]^? | - | Note that reference (cap : caps.Cap), defined at level 2 + | 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]^? + | Found: Cell[box () ->{cap[]} Unit]^? | Required: Cell[box () ->? Unit]^? | - | Note that reference (cap : caps.Cap), defined at level 2 + | Note that reference (cap[] : caps.Cap), defined at level 2 | cannot be included in outer capture set ?, defined at level 1 in method test | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 5994e3901179..feb16d8674ea 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -2,7 +2,7 @@ 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]^ + |Required: (lcap: caps.Cap) ?-> CT[Exception]^{lcap} ->{'cap[..test](from instantiating handle)} box CT[Exception]^{cap[test]} 24 | (x: CanThrow[Exception]) => x 25 | }{ | diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index f589883f6aab..fc01f5cce675 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -8,7 +8,7 @@ -- Error: tests/neg-custom-args/captures/vars.scala:23:14 -------------------------------------------------------------- 23 | a = x => g(x) // error | ^^^^ - | reference (cap3 : CC^) is not included in the allowed capture set {cap[test]} + | reference (cap3 : CC^{cap[scope]}) 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 @@ -38,7 +38,7 @@ 31 | val sc: String => String = scope // error (but should also be OK) | ^^^^^ | Found: (x$0: String) ->{cap[scope]} String - | Required: String => String + | Required: String ->{cap[test]} 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 From 434f455e44d90c4057102bbef00a5d0f11b54bda Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Sep 2023 20:34:51 +0200 Subject: [PATCH 004/117] Drop rhsClosure from CCState --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 12 ++---------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 3 ++- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b82bf795338e..b872309810e6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -37,11 +37,6 @@ class IllegalCaptureRef(tpe: Type) extends Exception /** Capture checking state, which is known to other capture checking components */ class CCState: - /** Temporary set indicating closures that are the rhs of a val or def. - * An entry gets removed when we check isLevelOwner on it. - */ - val rhsClosure: mutable.HashSet[Symbol] = new mutable.HashSet - /** Cache for level ownership */ val isLevelOwner: mutable.HashMap[Symbol, Boolean] = new mutable.HashMap @@ -369,15 +364,12 @@ extension (sym: Symbol) if symd.isClass then symd.is(CaptureChecked) || symd.isRoot else - symd.is(Method) + symd.is(Method, butNot = Accessor) && (!symd.owner.isClass || symd.owner.is(CaptureChecked)) && !Synthetics.isExcluded(sym) && !isCaseClassSynthetic && !symd.isConstructor - && (!symd.isAnonymousFunction - || ccState.rhsClosure.remove(sym) - || sym.definedLocalRoot.exists // TODO drop - ) + && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) ccState.isLevelOwner.getOrElseUpdate(sym, compute) /** The owner of the current level. Qualifying owners are diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 393616814cf8..1e542c31c7d1 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -463,7 +463,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.rhs match case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) - ccState.rhsClosure += ddef.symbol + //ccState.isLevelOwner(sym) = true + ccState.isLevelOwner(ddef.symbol) = true // 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 From 1164d558d7a76758f55d58dfabb92fcfe9e3fbe9 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 08:53:22 +0200 Subject: [PATCH 005/117] Fix computation of level owners 1. Update symbols owner before recursing to children, so that new owners taking account of try block owners are installed. 2. Query owner chaines at phase checkCpatures when computing enclosing level owners. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 24 +++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 86 +++++++++++-------- tests/neg-custom-args/captures/i15772.check | 20 ++--- tests/neg-custom-args/captures/lazylist.check | 2 +- tests/neg-custom-args/captures/vars.check | 20 ----- tests/neg-custom-args/captures/vars.scala | 4 +- 6 files changed, 70 insertions(+), 86 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b872309810e6..03ac6d5b330d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -380,26 +380,28 @@ 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 + def recur(sym: Symbol)(using Context): Symbol = + if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass + else if sym.isLevelOwner then sym + else recur(sym.owner) + recur(sym)(using ctx.withPhase(Phases.checkCapturesPhase)) /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. * If name refers to a val that has a closure as rhs, we return the closure as level * owner. */ def levelOwnerNamed(name: String)(using Context): Symbol = - def recur(owner: Symbol, prev: Symbol): Symbol = - if owner.name.toString == name then - if owner.isLevelOwner then owner - else if owner.isTerm && !owner.isOneOf(Method | Module) && prev.exists then prev + def recur(sym: Symbol, prev: Symbol)(using Context): Symbol = + if sym.name.toString == name then + if sym.isLevelOwner then sym + else if sym.isTerm && !sym.isOneOf(Method | Module) && prev.exists then prev else NoSymbol - else if owner == defn.RootClass then + else if sym == defn.RootClass then NoSymbol else - val prev1 = if owner.isAnonymousFunction && owner.isLevelOwner then owner else NoSymbol - recur(owner.owner, prev1) - recur(sym, NoSymbol) + val prev1 = if sym.isAnonymousFunction && sym.isLevelOwner then sym else NoSymbol + recur(sym.owner, prev1) + recur(sym, NoSymbol)(using ctx.withPhase(Phases.checkCapturesPhase)) .showing(i"find outer $sym [ $name ] = $result", capt) /** The nesting level of `sym` for the purposes of `cc`, diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 1e542c31c7d1..12eac183befd 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -354,7 +354,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: then cc.mapRoots(defn.captureRoot.termRef, ctx.owner.localRoot.termRef)(tp1) .showing(i"map roots $tp1, ${tp1.getClass} == $result", capt) else tp1 - if tp2 ne tp then ccSetup.println(i"expanded: $tp --> $tp1 --> $tp2") + if tp2 ne tp then ccSetup.println(i"expanded in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 end transformExplicitType @@ -419,8 +419,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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. + /** Update the owner of `sym` to `newOwnerFor(sym)`. + * This can happen because of inserted try block owners. */ private def updateOwner(sym: Symbol)(using Context) = updateInfo(sym, sym.info) @@ -437,6 +437,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if isExcluded(meth) then return + updateOwner(meth) inContext(ctx.withOwner(meth)): paramss.foreach(traverse) transformTT(tpt, boxed = false, @@ -457,45 +458,56 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => false val sym = tree.symbol - val mapRoots = - // need to run before cc so that rhsClosure is defined without transforming - // symbols to cc, since rhsClosure is used in that transformation. - tree.rhs match - case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => - //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) - //ccState.isLevelOwner(sym) = true - ccState.isLevelOwner(ddef.symbol) = true - // 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 - ) - ccSetup.println(i"mapped $tree = ${tpt.knownType}") - traverse(tree.rhs) + val newOwner = + if sym.isOneOf(TermParamOrAccessor) then ctx.owner + else + updateOwner(sym) + sym + inContext(ctx.withOwner(newOwner)): + val mapRoots = + // need to run before cc so that rhsClosure is defined without transforming + // symbols to cc, since rhsClosure is used in that transformation. + tree.rhs match + case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => + //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) + //ccState.isLevelOwner(sym) = true + ccState.isLevelOwner(ddef.symbol) = true + // 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. + true && !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 + ) + ccSetup.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 => - inContext(ctx.withOwner(tree.symbol.owner)): + case tree: TypeDef if tree.symbol.isClass => + val cls = tree.symbol + updateOwner(cls) + if cls.is(ModuleClass) then updateOwner(cls.sourceModule) + inContext(ctx.withOwner(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.isLevelOwner(tryOwner) = true ccState.tryBlockOwner(tree) = tryOwner inContext(ctx.withOwner(tryOwner)): traverseChildren(tree) + case _ => traverseChildren(tree) postProcess(tree) @@ -551,12 +563,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 = + val mapr = + if sym.isLevelOwner then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) + else identity[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 !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then @@ -569,10 +581,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: ) case info: ExprType => info.derivedExprType(resType = - integrateRT(info.resType, psymss, prevPsymss, prevLambdas)) + mapr(integrateRT(info.resType, psymss, prevPsymss, prevLambdas))) case info => - if prevLambdas.isEmpty then localReturnType - else SubstParams(prevPsymss, prevLambdas)(localReturnType) + mapr( + if prevLambdas.isEmpty then localReturnType + else SubstParams(prevPsymss, prevLambdas)(localReturnType)) if sym.exists && signatureChanges then val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) @@ -592,9 +605,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: ccSetup.println(i"FORCING $sym") 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 diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 3b1628b77af1..7aed49d4d574 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -22,25 +22,20 @@ | Required: (C^{cap[main2]} ->{cap[main2]} 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 + | ^^^^^^^ + |Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating box2)} ->{'cap[..main3](from instantiating box2)} Unit) ->? + | Unit + |Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit | | 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]} + | Found: C^ | Required: C^{'cap[..main3](from instantiating unsafe)} | - | Note that reference (cap[c] : caps.Cap), defined at level 2 - | cannot be included in outer capture set ?, defined at level 1 in method main3 - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- 44 | x: (() -> Unit) // error @@ -48,7 +43,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/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index ba5080d808f8..4b7611fc3fb7 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -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/vars.check b/tests/neg-custom-args/captures/vars.check index fc01f5cce675..74000c79a212 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -24,26 +24,6 @@ | Required: List[box 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 ->{cap[test]} String - | - | Note that reference (cap[scope] : caps.Cap), defined at level 2 - | cannot be included in outer capture set ?, defined at level 1 in method test - | - | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/vars.scala:35:2 --------------------------------------------------------------- 35 | local { root => cap3 => // error | ^^^^^ diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 9b280a42a2f2..81a68ce6a005 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -27,8 +27,8 @@ 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 + val sc: String => String = scope def local[T](op: (local: caps.Cap) -> CC^{local} -> T): T = op(caps.cap)(CC()) From 2397cd71ffda6cae0652958a8e2c31b3397205ac Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 10:50:02 +0200 Subject: [PATCH 006/117] Setup newScheme mechanism --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 12eac183befd..f37324e0a02f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -23,7 +23,7 @@ trait SetupAPI: def decorate(tp: Type, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type object Setup: - def enabled(using Context) = true // ctx.settings.YccNew.value // if new impl is conditional + def newScheme(using Context) = ctx.settings.YccNew.value // if new impl is conditional private val IsDuringSetupKey = new Property.Key[Unit] @@ -470,14 +470,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.rhs match case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) - //ccState.isLevelOwner(sym) = true + if newScheme then ccState.isLevelOwner(sym) = true ccState.isLevelOwner(ddef.symbol) = true // 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. - true && !tpt.isInstanceOf[InferredTypeTree] + newScheme || !tpt.isInstanceOf[InferredTypeTree] // in this case roots in inferred val type count as polymorphic case _ => true From bf9a0b0058f01c0305b0b9c2c79eaa381ce35df4 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 11:42:36 +0200 Subject: [PATCH 007/117] Introduce rootTarget for mapRoots customization --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 82 ++++++++++--------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 4374cd1e3f27..fd886e5123c1 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -358,7 +358,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => mapOver(t) if variance > 0 then t1 - else setup.decorate(t1, mapRoots = false, addedSet = Function.const(CaptureSet.Fluid)) + else setup.decorate(t1, rootTarget = NoSymbol, addedSet = Function.const(CaptureSet.Fluid)) def isPreCC(sym: Symbol): Boolean = sym.isTerm && sym.maybeOwner.isClass diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index f37324e0a02f..17b2da3f3d2f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -20,7 +20,7 @@ import printing.{Printer, Texts}, Texts.{Text, Str} trait SetupAPI: type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Unit def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit - def decorate(tp: Type, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type + def decorate(tp: Type, rootTarget: Symbol, addedSet: Type => CaptureSet)(using Context): Type object Setup: def newScheme(using Context) = ctx.settings.YccNew.value // if new impl is conditional @@ -74,10 +74,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere - private def toCC(sym: Symbol)(using Context) = new FollowAliases: - def apply(t: Type): Type = - mapOverFollowingAliases(t) - 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 @@ -90,26 +86,26 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * is CC-enabled. */ def transformSym(symd: SymDenotation)(using Context): SymDenotation = - def needsTransform = symd.owner.isTerm || symd.owner.is(CaptureChecked) - def needsInfoTransform = symd.isGetter + def needsInfoTransform = newScheme || symd.isGetter if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed // if sym is class && level owner: add a capture root // translate cap/capIn to local roots - // what are local roots? should we add them in CCState? // create local roots if necessary if Synthetics.needsTransform(symd) then Synthetics.transform(symd) - else + else if symd.owner.isTerm || symd.is(CaptureChecked) || symd.owner.is(CaptureChecked) then val newFlags = newFlagsFor(symd) val newInfo = - if needsInfoTransform then transformExplicitType(sym.info, mapRoots = false) - else /*toCC(sym)*/(sym.info) + if needsInfoTransform + then transformExplicitType(sym.info, rootTarget = if newScheme then sym else NoSymbol) + else sym.info if newFlags != symd.flags || (newInfo ne sym.info) then symd.copySymDenotation(initFlags = newFlags, info = newInfo) else symd + else symd else symd /** Create dependent function with underlying function class `tycon` and given @@ -161,7 +157,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - private def mapInferred(mapRoots: Boolean)(using Context) = new TypeMap: + private def mapInferred(rootTarget: Symbol)(using Context) = new TypeMap: override def toString = "map inferred" /** Drop @retains annotations everywhere */ @@ -246,14 +242,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: box(this(tp1)) case _ => mapOver(tp) - addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner, mapRoots) + addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner, rootTarget) end apply end mapInferred - private def transformInferredType(tp: Type, mapRoots: Boolean)(using Context): Type = - mapInferred(mapRoots)(tp) + private def transformInferredType(tp: Type, rootTarget: Symbol)(using Context): Type = + mapInferred(rootTarget)(tp) - private def transformExplicitType(tp: Type, mapRoots: Boolean)(using Context): Type = + private def transformExplicitType(tp: Type, rootTarget: Symbol)(using Context): Type = val expandAliases = new TypeMap: override def toString = "expand aliases" @@ -301,13 +297,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if t2 ne t then return t2 t match case t: TypeRef => - if t.symbol.isClass then t + val sym = t.symbol + if sym.isClass then t else t.info match case TypeAlias(alias) => val transformed = - if t.symbol.isStatic then this(alias) - else transformExplicitType(alias, mapRoots)( - using ctx.withOwner(t.symbol.owner)) + if sym.isStatic then this(alias) + else transformExplicitType(alias, + if rootTarget.exists then sym else NoSymbol)( + using ctx.withOwner(sym.owner)) if transformed ne alias then transformed else t //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") case _ => @@ -350,8 +348,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val tp1 = expandAliases(tp) val tp2 = - if mapRoots - then cc.mapRoots(defn.captureRoot.termRef, ctx.owner.localRoot.termRef)(tp1) + if rootTarget.exists + then mapRoots(defn.captureRoot.termRef, rootTarget.localRoot.termRef)(tp1) .showing(i"map roots $tp1, ${tp1.getClass} == $result", capt) else tp1 if tp2 ne tp then ccSetup.println(i"expanded in ${ctx.owner}: $tp --> $tp1 --> $tp2") @@ -359,13 +357,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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, rootTarget: Symbol)(using Context): Unit = if !tree.hasRememberedType then val tp = if boxed then Box(tree.tpe) else tree.tpe tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tp, mapRoots) - else transformExplicitType(tp, mapRoots)) + then transformInferredType(tp, rootTarget) + else transformExplicitType(tp, rootTarget)) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -442,7 +440,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: paramss.foreach(traverse) transformTT(tpt, boxed = false, exact = tree.symbol.allOverriddenSymbols.hasNext, - mapRoots = true) + rootTarget = ctx.owner) traverse(tree.rhs) //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") @@ -454,7 +452,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case Apply(fn, _) => mentionsCap(fn) case TypeApply(fn, args) => args.exists(mentionsCap) case _: InferredTypeTree => false - case _: TypeTree => containsCap(transformExplicitType(tree.tpe, mapRoots = false)) + case _: TypeTree => containsCap(transformExplicitType(tree.tpe, rootTarget = NoSymbol)) case _ => false val sym = tree.symbol @@ -464,7 +462,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: updateOwner(sym) sym inContext(ctx.withOwner(newOwner)): - val mapRoots = + val rootsNeedMap = // need to run before cc so that rhsClosure is defined without transforming // symbols to cc, since rhsClosure is used in that transformation. tree.rhs match @@ -484,7 +482,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 + rootTarget = if rootsNeedMap then ctx.owner else NoSymbol ) ccSetup.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) @@ -492,7 +490,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 + transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed case tree: TypeDef if tree.symbol.isClass => val cls = tree.symbol @@ -521,10 +519,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: traverse(rest) case Nil => + extension (sym: Symbol)(using Context) def unlessStatic = + val owner = sym.levelOwner + if sym.isStaticOwner then NoSymbol else owner + def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => + val lowner = transformTT(tree, boxed = false, exact = false, - mapRoots = !ctx.owner.levelOwner.isStaticOwner // other types in static locations are not boxed + rootTarget = ctx.owner.unlessStatic // other types in static locations are not boxed ) case tree: ValOrDefDef => val sym = tree.symbol @@ -564,7 +567,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order ): Type = val mapr = - if sym.isLevelOwner then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) + if !newScheme && sym.isLevelOwner + then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) else identity[Type] info match case mt: MethodOrPoly => @@ -608,7 +612,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: Bind => val sym = tree.symbol - updateInfo(sym, transformInferredType(sym.info, mapRoots = true)) + updateInfo(sym, transformInferredType(sym.info, rootTarget = ctx.owner)) case tree: TypeDef => tree.symbol match case cls: ClassSymbol => @@ -624,7 +628,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else selfInfo match case selfInfo: Type => inContext(ctx.withOwner(cls)): - transformExplicitType(selfInfo, mapRoots = true) + transformExplicitType(selfInfo, rootTarget = ctx.owner) case _ => NoType if newSelfType.exists then @@ -642,7 +646,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if cls.is(ModuleClass) then updateOwner(cls.sourceModule) case _ => val info = tree.symbol.info - val newInfo = transformExplicitType(info, mapRoots = !ctx.owner.isStaticOwner) + val newInfo = transformExplicitType(info, rootTarget = ctx.owner.unlessStatic) updateInfo(tree.symbol, newInfo) if newInfo ne info then ccSetup.println(i"update info of ${tree.symbol} from $info to $newInfo") @@ -733,7 +737,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Add a capture set variable to `tp` if necessary, or maybe pull out * an embedded capture set variable from a part of `tp`. */ - def decorate(tp: Type, mapRoots: Boolean, addedSet: Type => CaptureSet)(using Context): Type = + def decorate(tp: Type, rootTarget: Symbol, 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 @@ -743,7 +747,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else fallback val dealiased = tp.dealiasKeepAnnots if dealiased ne tp then - val transformed = transformExplicitType(dealiased, mapRoots) + val transformed = transformExplicitType(dealiased, rootTarget) // !!! TODO: We should map roots to root vars here maybeAdd(transformed, if transformed ne dealiased then transformed else tp) else maybeAdd(tp, tp) @@ -751,8 +755,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Add a capture set variable to `tp` if necessary, or maybe pull out * an embedded capture set variable from a part of `tp`. */ - def addVar(tp: Type, owner: Symbol, mapRoots: Boolean)(using Context): Type = - decorate(tp, mapRoots, + def addVar(tp: Type, owner: Symbol, rootTarget: Symbol)(using Context): Type = + decorate(tp, rootTarget, addedSet = _.dealias.match case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems) case _ => CaptureSet.Var(owner)) From 62120fbfa4c40cdf2120d7e8e5dd0dbe72466711 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 13:16:14 +0200 Subject: [PATCH 008/117] Change result type inference of vals and defs --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 17b2da3f3d2f..b999552939cd 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -563,6 +563,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 = @@ -581,18 +582,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) psyms.map(psym => mapr(subst(psym.nextInfo)).asInstanceOf[mt.PInfo]), mt1 => - mapr(integrateRT(mt.resType, psymss.tail, psyms :: prevPsymss, mt1 :: prevLambdas)) + mapr(integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas)) ) case info: ExprType => info.derivedExprType(resType = - mapr(integrateRT(info.resType, psymss, prevPsymss, prevLambdas))) + mapr(integrateRT(info.resType, psymss, resType, prevPsymss, prevLambdas))) case info => mapr( - if prevLambdas.isEmpty then localReturnType - else SubstParams(prevPsymss, prevLambdas)(localReturnType)) + if prevLambdas.isEmpty then resType + else SubstParams(prevPsymss, prevLambdas)(resType)) if sym.exists && signatureChanges then - val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) + val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) .showing(i"update info $sym: ${sym.info} = $result", ccSetup) if newInfo ne sym.info then updateInfo(sym, From f550272d1a0d7f1479b183d6b318eb5855d6df5d Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 14:20:07 +0200 Subject: [PATCH 009/117] Make RecheckDef return a type --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 11 ++++++----- compiler/src/dotty/tools/dotc/cc/Setup.scala | 6 ++++-- .../dotty/tools/dotc/transform/Recheck.scala | 19 +++++++++++-------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index fd886e5123c1..f3135a74b800 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -601,10 +601,10 @@ class CheckCaptures extends Recheck, SymTransformer: 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 super.recheckValDef(tree, sym) finally if !sym.is(Param) then // Parameters with inferred types belong to anonymous methods. We need to wait @@ -613,8 +613,9 @@ 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 diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b999552939cd..1b0a63acf25f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -18,7 +18,7 @@ import printing.{Printer, Texts}, Texts.{Text, Str} /** Operations accessed from CheckCaptures */ trait SetupAPI: - type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Unit + type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit def decorate(tp: Type, rootTarget: Symbol, addedSet: Type => CaptureSet)(using Context): Type @@ -593,8 +593,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else SubstParams(prevPsymss, prevLambdas)(resType)) if sym.exists && signatureChanges then - val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) + def absInfo(resType: Type): Type = + integrateRT(sym.info, sym.paramSymss, resType, Nil, Nil) .showing(i"update info $sym: ${sym.info} = $result", ccSetup) + val newInfo = absInfo(localReturnType) if newInfo ne sym.info then updateInfo(sym, if sym.isAnonymousFunction || sym.is(Param) || sym.is(ParamAccessor) then diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 47b277d06321..c3ed979b9761 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -246,13 +246,16 @@ 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 = + if tree.rhs.isEmpty then sym.info + else recheck(tree.rhs, sym.info) - 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) @@ -421,7 +424,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 @@ -447,7 +450,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) From aa295591e8bb45968fc082178acf72629599ea37 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Sep 2023 22:49:46 +0200 Subject: [PATCH 010/117] Use a separate phase to add try owners I tried several other strategies, but nothing worked. - update after Setup: This means we cannot to levelOwner during setup. - running levelOwner after Setup causes CyclicErrors when transforming symbols in Setup - having a seperate notion of ccOwner maintained in a CCState table does not work since there are too many hidden uses of owner everywhere that would ignore that table. --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + compiler/src/dotty/tools/dotc/ast/tpd.scala | 15 ++++++ .../dotty/tools/dotc/cc/AddTryOwners.scala | 45 +++++++++++++++++ .../src/dotty/tools/dotc/cc/CaptureOps.scala | 8 +-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 1 + compiler/src/dotty/tools/dotc/cc/Setup.scala | 50 ++----------------- .../dotty/tools/dotc/transform/Recheck.scala | 14 ++++-- tests/neg-custom-args/captures/real-try.check | 14 +++--- 8 files changed, 88 insertions(+), 60 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index c195c85c6b57..b6efedce1ffd 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -83,6 +83,7 @@ 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 cc.AddTryOwners) :: // Add symbols as owners of try blocks, 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 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/AddTryOwners.scala b/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala new file mode 100644 index 000000000000..2e9f7ecfccd2 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc +package cc + +import core.* +import DenotTransformers.IdentityDenotTransformer +import Phases.Phase +import Contexts.*, Symbols.*, Flags.*, Types.* +import config.Feature +import ast.tpd +import StdNames.nme +import Decorators.i + +object AddTryOwners: + val name: String = "addTryOwners" + val description: String = "add symbols for try blocks in preparation of capture checking" + +class AddTryOwners extends Phase, IdentityDenotTransformer: + thisPhase => + + import tpd.* + + override def phaseName: String = AddTryOwners.name + override def description: String = AddTryOwners.description + + override def isRunnable(using Context) = + super.isRunnable && Feature.ccEnabledSomewhere + + override def isCheckable = false + + def run(using Context): Unit = + val addTryOwners = new TreeTraverserWithPreciseImportContexts: + def traverse(tree: Tree)(using Context): Unit = tree match + case tree @ Try(expr, cases, finalizer) if Feature.enabled(Feature.saferExceptions) => + val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType)) + ccState.tryBlockOwner(tree) = tryOwner + expr.changeOwnerAfter(ctx.owner, tryOwner, thisPhase) + inContext(ctx.withOwner(tryOwner)): + traverse(expr) + traverse(cases) + traverse(finalizer) + case _ => + traverseChildren(tree) + addTryOwners.traverse(ctx.compilationUnit.tpdTree) +end AddTryOwners + diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 03ac6d5b330d..c308209300db 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -380,18 +380,18 @@ extension (sym: Symbol) * - _root_ */ def levelOwner(using Context): Symbol = - def recur(sym: Symbol)(using Context): Symbol = + def recur(sym: Symbol): Symbol = if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass else if sym.isLevelOwner then sym else recur(sym.owner) - recur(sym)(using ctx.withPhase(Phases.checkCapturesPhase)) + recur(sym) /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. * If name refers to a val that has a closure as rhs, we return the closure as level * owner. */ def levelOwnerNamed(name: String)(using Context): Symbol = - def recur(sym: Symbol, prev: Symbol)(using Context): Symbol = + def recur(sym: Symbol, prev: Symbol): Symbol = if sym.name.toString == name then if sym.isLevelOwner then sym else if sym.isTerm && !sym.isOneOf(Method | Module) && prev.exists then prev @@ -401,7 +401,7 @@ extension (sym: Symbol) else val prev1 = if sym.isAnonymousFunction && sym.isLevelOwner then sym else NoSymbol recur(sym.owner, prev1) - recur(sym, NoSymbol)(using ctx.withPhase(Phases.checkCapturesPhase)) + recur(sym, NoSymbol) .showing(i"find outer $sym [ $name ] = $result", capt) /** The nesting level of `sym` for the purposes of `cc`, diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index f3135a74b800..c9553c10efe3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -182,6 +182,7 @@ class CheckCaptures extends Recheck, SymTransformer: def phaseName: String = "cc" override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere + override def firstPrepPhase = preRecheckPhase.prev.asInstanceOf[AddTryOwners] def newRechecker()(using Context) = CaptureChecker(ctx) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 1b0a63acf25f..374615ea23e5 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -402,31 +402,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def inverse = thisMap end SubstParams - /** If the outer context directly enclosing the definition of `sym` - * has a owner, that owner, otherwise null. - */ - def newOwnerFor(sym: Symbol)(using Context): Symbol = - var octx = ctx - while octx.owner == sym do octx = octx.outer - if octx.owner.name == nme.TRY_BLOCK then octx.owner else sym.maybeOwner - /** Update info of `sym` for CheckCaptures phase only */ private def updateInfo(sym: Symbol, info: Type)(using Context) = - sym.updateInfo(thisPhase, info, newFlagsFor(sym), newOwnerFor(sym)) + sym.updateInfo(thisPhase, info, newFlagsFor(sym)) sym.namedType match case ref: CaptureRef => ref.invalidateCaches() // TODO: needed? case _ => - /** Update the owner of `sym` to `newOwnerFor(sym)`. - * This can happen because of inserted try block owners. - */ - private def updateOwner(sym: Symbol)(using Context) = - updateInfo(sym, sym.info) - extension (sym: Symbol) def nextInfo(using Context): Type = atPhase(thisPhase.next)(sym.info) - def setupTraverser(recheckDef: DefRecheck) = new TreeTraverser: + def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: def traverse(tree: Tree)(using Context): Unit = tree match @@ -435,7 +421,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if isExcluded(meth) then return - updateOwner(meth) inContext(ctx.withOwner(meth)): paramss.foreach(traverse) transformTT(tpt, boxed = false, @@ -456,12 +441,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => false val sym = tree.symbol - val newOwner = - if sym.isOneOf(TermParamOrAccessor) then ctx.owner - else - updateOwner(sym) - sym - inContext(ctx.withOwner(newOwner)): + val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) + inContext(defCtx): val rootsNeedMap = // need to run before cc so that rhsClosure is defined without transforming // symbols to cc, since rhsClosure is used in that transformation. @@ -493,17 +474,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed case tree: TypeDef if tree.symbol.isClass => - val cls = tree.symbol - updateOwner(cls) - if cls.is(ModuleClass) then updateOwner(cls.sourceModule) - inContext(ctx.withOwner(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.isLevelOwner(tryOwner) = true - ccState.tryBlockOwner(tree) = tryOwner - inContext(ctx.withOwner(tryOwner)): + inContext(ctx.withOwner(tree.symbol)): traverseChildren(tree) case _ => @@ -511,14 +482,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 => - extension (sym: Symbol)(using Context) def unlessStatic = val owner = sym.levelOwner if sym.isStaticOwner then NoSymbol else owner @@ -644,9 +607,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 = tree.symbol.info val newInfo = transformExplicitType(info, rootTarget = ctx.owner.unlessStatic) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index c3ed979b9761..b0e226d60c09 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -138,19 +138,27 @@ 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 = diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index c81a30d82918..eee1b8c456cc 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -24,13 +24,11 @@ | 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 -------------------------------------- +-- Error: tests/neg-custom-args/captures/real-try.scala:31:21 ---------------------------------------------------------- 31 | Cell(() => foo(1))// // error - | ^^^^^^^^^^^^^^^^^^ - | Found: Cell[box () ->{cap[]} Unit]^? - | Required: Cell[box () ->? Unit]^? + | ^ + |(canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}) cannot be referenced here; it is not included in the allowed capture set ? + |of an enclosing function literal with expected type box () ->? 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` + |Note that reference (canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}), defined at level 2 + |cannot be included in outer capture set ?, defined at level 1 in method test From 1a651142dc37afc5e821ade341f81da0913f2f1e Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Sep 2023 12:27:29 +0200 Subject: [PATCH 011/117] Transform infos of all symbols defined in current run in SymTransformer (except if they are updated). This is the first step towards converting to capturing types in SymTransformer, where we have the context. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index c308209300db..3ac0c419e975 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -32,7 +32,7 @@ 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 +class IllegalCaptureRef(tpe: Type) extends Exception(tpe.toString) /** Capture checking state, which is known to other capture checking components */ class CCState: diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 374615ea23e5..255cf54fad6d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -15,6 +15,7 @@ import CaptureSet.IdentityCaptRefMap import Synthetics.isExcluded import util.Property import printing.{Printer, Texts}, Texts.{Text, Str} +import collection.mutable /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -74,7 +75,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere - def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = + 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 @@ -89,7 +92,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def needsInfoTransform = newScheme || symd.isGetter if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol - atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed + val needsInfoTransform = sym.isDefinedInCurrentRun && !toBeUpdated.contains(sym) // if sym is class && level owner: add a capture root // translate cap/capIn to local roots // create local roots if necessary @@ -98,8 +101,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else if symd.owner.isTerm || symd.is(CaptureChecked) || symd.owner.is(CaptureChecked) then val newFlags = newFlagsFor(symd) val newInfo = - if needsInfoTransform - then transformExplicitType(sym.info, rootTarget = if newScheme then sym else NoSymbol) + if needsInfoTransform then + atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed + transformExplicitType(sym.info, rootTarget = if newScheme then sym else NoSymbol) else sym.info if newFlags != symd.flags || (newInfo ne sym.info) @@ -107,6 +111,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd else symd else symd + end transformSym /** Create dependent function with underlying function class `tycon` and given * arguments `argTypes` and result `resType`. @@ -404,7 +409,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Update info of `sym` for CheckCaptures phase only */ private def updateInfo(sym: Symbol, info: Type)(using Context) = + toBeUpdated += sym sym.updateInfo(thisPhase, info, newFlagsFor(sym)) + toBeUpdated -= sym sym.namedType match case ref: CaptureRef => ref.invalidateCaches() // TODO: needed? case _ => @@ -514,7 +521,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else tree.tpt.knownType def paramSignatureChanges = tree.match - case tree: DefDef => tree.termParamss.nestedExists(_.tpt.hasRememberedType) + case tree: DefDef => tree.paramss.nestedExists: + case param: ValDef => param.tpt.hasRememberedType + case param: TypeDef => param.rhs.hasRememberedType case _ => false def signatureChanges = From f9d8b74443e44185fedd2749d83b1ec7b5832276 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Sep 2023 12:28:20 +0200 Subject: [PATCH 012/117] Fix bug when restoring denotations in Recheck. Need to copy the denotation, since denotations come with next pointers which would get scrambled otherwise. --- compiler/src/dotty/tools/dotc/transform/Recheck.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index b0e226d60c09..e21e66550cec 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -158,7 +158,8 @@ abstract class Recheck extends Phase, SymTransformer: val sym = symd.symbol def updatedAfter(p: Phase): Boolean = sym.isUpdatedAfter(p) || p != preRecheckPhase && updatedAfter(p.next) - if updatedAfter(firstPrepPhase) then atPhase(firstPrepPhase)(sym.denot.copySymDenotation()) + if updatedAfter(firstPrepPhase) + then atPhase(firstPrepPhase)(sym.denot.copySymDenotation()) else symd def run(using Context): Unit = From eb8457b66d258d5e06aafc991d734884a456a851 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Sep 2023 12:28:51 +0200 Subject: [PATCH 013/117] Do fluidify when transforming symbols instead of on access --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 41 ++----------------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 30 ++++++++++++-- .../dotty/tools/dotc/typer/RefChecks.scala | 37 +++++++++-------- 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c9553c10efe3..b3cddc514a31 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -342,38 +342,6 @@ 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, rootTarget = NoSymbol, 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 = if tree.symbol.is(Method) then if tree.symbol.info.isParameterless then @@ -381,7 +349,7 @@ class CheckCaptures extends Recheck, SymTransformer: includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - handleBackwardsCompat(super.recheckIdent(tree), tree.symbol) + super.recheckIdent(tree) /** A specialized implementation of the selection rule. * @@ -411,7 +379,7 @@ 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) + selType else val qualCs = qualType.captureSet capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") @@ -1025,9 +993,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) = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 255cf54fad6d..dfe60bb2fa1a 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -11,7 +11,7 @@ import config.Feature import config.Printers.{capt, ccSetup} import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* -import CaptureSet.IdentityCaptRefMap +import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded import util.Property import printing.{Printer, Texts}, Texts.{Text, Str} @@ -21,7 +21,7 @@ import collection.mutable trait SetupAPI: type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit - def decorate(tp: Type, rootTarget: Symbol, addedSet: Type => CaptureSet)(using Context): Type + def isPreCC(sym: Symbol)(using Context): Boolean object Setup: def newScheme(using Context) = ctx.settings.YccNew.value // if new impl is conditional @@ -82,6 +82,29 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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, rootTarget = NoSymbol, 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. @@ -89,7 +112,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * is CC-enabled. */ def transformSym(symd: SymDenotation)(using Context): SymDenotation = - def needsInfoTransform = newScheme || symd.isGetter if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol val needsInfoTransform = sym.isDefinedInCurrentRun && !toBeUpdated.contains(sym) @@ -98,6 +120,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // create local roots if necessary if Synthetics.needsTransform(symd) then Synthetics.transform(symd) + 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 = diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 061d759e9ca4..c13b3854a7dd 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -223,36 +223,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 @@ -371,8 +374,8 @@ 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)}") From 174ccb4ae6d3063971ae1d81b255dfe8f989258b Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Sep 2023 22:09:19 +0200 Subject: [PATCH 014/117] Align rechecking ValDefs and DefDefs Makes things more regular and allows for a non-identity mapping between declared types and infos of vals. --- compiler/src/dotty/tools/dotc/transform/Recheck.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index e21e66550cec..cef6ddabd468 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -256,8 +256,9 @@ abstract class Recheck extends Phase, SymTransformer: bindType.symbol.info def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = - if tree.rhs.isEmpty then sym.info - else recheck(tree.rhs, sym.info) + val resType = recheck(tree.tpt) + if tree.rhs.isEmpty then resType + else recheck(tree.rhs, resType) def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = inContext(linkConstructorParams(sym).withOwner(sym)): From e6c43c1e1b5638db1454f0fa40bdfb67f1099c18 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Sep 2023 22:15:11 +0200 Subject: [PATCH 015/117] Revise inferred type checking The new check is that a publicly visible inferred type after capture checking must conform to the type before capture checking (which is also the type seen by other separately compiled units). --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 10 +++ .../dotty/tools/dotc/cc/CheckCaptures.scala | 77 +++++++++--------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 18 ++--- tests/neg-custom-args/captures/i15116.check | 80 +++++++++++++------ .../captures/usingLogFile.check | 34 ++++++-- 5 files changed, 134 insertions(+), 85 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3ac0c419e975..abe4fa4bde77 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -272,6 +272,16 @@ extension (tp: Type) 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: Symbol) def pureBaseClass(using Context): Option[Symbol] = diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b3cddc514a31..39b0510b8760 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -573,7 +573,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try if sym.is(Module) then sym.info // Modules are checked by checking the module class - else super.recheckValDef(tree, sym) + else checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then // Parameters with inferred types belong to anonymous methods. We need to wait @@ -589,11 +589,46 @@ class CheckCaptures extends Recheck, SymTransformer: 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) curEnv = saved + 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 + 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. @@ -1148,9 +1183,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`. */ @@ -1169,41 +1201,6 @@ class CheckCaptures extends Recheck, SymTransformer: case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => checkWellformedPost(parent, 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: CaptureSet.Var) - 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 => diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index dfe60bb2fa1a..979c93775ab1 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -127,7 +127,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val newInfo = if needsInfoTransform then atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed - transformExplicitType(sym.info, rootTarget = if newScheme then sym else NoSymbol) + transformExplicitType(sym.info, rootTarget = if newScheme && false then sym else NoSymbol) else sym.info if newFlags != symd.flags || (newInfo ne sym.info) @@ -189,14 +189,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def mapInferred(rootTarget: Symbol)(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) - /** 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. @@ -265,7 +257,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // Don't recurse into parameter bounds, just cleanup any stray retains annotations // !!! TODO we should also map roots to rootvars here tp.derivedLambdaType( - paramInfos = tp.paramInfos.mapConserve(cleanup(_).bounds), + paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), resType = this(tp.resType)) case Box(tp1) => box(this(tp1)) @@ -480,14 +472,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.rhs match case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) - if newScheme then ccState.isLevelOwner(sym) = true + if newScheme && false then ccState.isLevelOwner(sym) = true ccState.isLevelOwner(ddef.symbol) = true // 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. - newScheme || !tpt.isInstanceOf[InferredTypeTree] + (newScheme && false) || !tpt.isInstanceOf[InferredTypeTree] // in this case roots in inferred val type count as polymorphic case _ => true @@ -564,7 +556,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order ): Type = val mapr = - if !newScheme && sym.isLevelOwner + if !(newScheme && false) && sym.isLevelOwner then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) else identity[Type] info match diff --git a/tests/neg-custom-args/captures/i15116.check b/tests/neg-custom-args/captures/i15116.check index 765477df7466..3a41fdae73ec 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^{cap[Bar]})}^{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^{cap[Bar]})}^{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^{cap[Bar1]})}^{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^{cap[Bar1]})}^{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/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index f4de75efc493..5fd9b417e260 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,3 +1,16 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:23:27 --------------------------------- +23 | val later = usingLogFile { f => () => f.write(0) } // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: () ->{x$0, local} Unit + | Required: () -> Unit + | + | Note that the expected type () -> Unit + | is the previously inferred type of value later + | which is also the type seen in separately compiled sources. + | The new inferred type box () ->{x$0, local} Unit + | must conform to this type. + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/usingLogFile.scala:32:37 ------------------------------------------------------ 32 | usingLogFile { f => later3 = () => f.write(0) } // error | ^ @@ -10,18 +23,23 @@ | 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 +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:47:27 --------------------------------- +47 | val later = usingLogFile { f => () => f.write(0) } // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: () ->{x$0, local} Unit + | Required: () -> Unit + | + | Note that the expected type () -> Unit + | is the previously inferred type of value later + | which is also the type seen in separately compiled sources. + | The new inferred type box (() ->{x$0, local} Unit)^? + | must conform to this type. + | + | longer explanation available when compiling with `-explain` -- 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 | ^^^^^^^^^ From ba1e919fd473265ee0ff74f638121b6921bf92e2 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Sep 2023 10:14:19 +0200 Subject: [PATCH 016/117] Take singleton types into account when instantiating local roots Widen singleton types when instantiating local roots in checkConforms --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 5 +++-- tests/neg-custom-args/captures/i15772.check | 7 ------- tests/neg-custom-args/captures/i15772.scala | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 39b0510b8760..c0089bd99f9d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -761,8 +761,9 @@ class CheckCaptures extends Recheck, SymTransformer: // 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 actualw = actual.widen + val actual1 = mapRoots(defn.captureRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner))(actualw) + (actual1 ne actualw) && { val res = super.isCompatible(actual1, expected) if !res && ctx.settings.YccDebug.value then println(i"Failure under mapped roots:") diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 7aed49d4d574..22c95f8a8ed5 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -30,13 +30,6 @@ |Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit | | 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^ - | Required: C^{'cap[..main3](from instantiating unsafe)} - | - | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- 44 | x: (() -> Unit) // error | ^ 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: From 622c73c73e2a7317b23edd8fca633b920f16ed5e Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Sep 2023 16:56:20 +0200 Subject: [PATCH 017/117] Replace some uses of CaptureSet universal to account for local roots --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 23 ++++++++++++++----- compiler/src/dotty/tools/dotc/cc/Setup.scala | 4 ++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 026f02872086..3cde5cae981a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -61,6 +61,10 @@ sealed abstract class CaptureSet extends Showable: */ def owner: Symbol + def rootSet(using Context): CaptureSet = + if Setup.newScheme then owner.localRoot.termRef.singletonCaptureSet + else universal + /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -83,6 +87,12 @@ sealed abstract class CaptureSet extends Showable: case _ => false } + /** Does this capture set contain the root reference `cap` as element? */ + final def containsRoot(using Context) = + elems.exists: + case ref: TermRef => ref.isRootCapability + case _ => false + /** 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. @@ -539,13 +549,14 @@ object CaptureSet: /** Roughly: the intersection of all constant known supersets of this set. * The aim is to find an as-good-as-possible constant set that is a superset - * of this set. The universal set {cap} is a sound fallback. + * of this set. The associated root set {cap[owner]} is a sound fallback. */ final def upperApprox(origin: CaptureSet)(using Context): 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 + rootSet else computingApprox = true try computeApprox(origin).ensuring(_.isConst) @@ -553,7 +564,7 @@ object CaptureSet: /** The intersection of all upper approximations of dependent sets */ protected def computeApprox(origin: CaptureSet)(using Context): CaptureSet = - (universal /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } + (rootSet /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } /** Widen the variable's elements to its upper approximation and * mark it as constant from now on. This is used for contra-variant type variables @@ -694,7 +705,7 @@ object CaptureSet: if source eq origin then // it's a mapping of origin, so not a superset of `origin`, // therefore don't contribute to the intersection. - universal + rootSet else source.upperApprox(this).map(tm) @@ -758,7 +769,7 @@ object CaptureSet: if source eq origin then // it's a filter of origin, so not a superset of `origin`, // therefore don't contribute to the intersection. - universal + rootSet else source.upperApprox(this).filter(p) @@ -789,7 +800,7 @@ object CaptureSet: if (origin eq cs1) || (origin eq cs2) then // it's a combination of origin with some other set, so not a superset of `origin`, // therefore don't contribute to the intersection. - universal + rootSet else CaptureSet(elemIntersection(cs1.upperApprox(this), cs2.upperApprox(this))) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 979c93775ab1..5b8043fdf45d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -685,8 +685,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 AnnotatedType(parent, _) => needsVariable(parent) case _ => From baf530fda53b570a8765e8cfa0b083de08194711 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Sep 2023 16:59:41 +0200 Subject: [PATCH 018/117] Less forcing of info when printing Don't force info when checking whether something is a root capability while printing --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index c2b9bb677a8c..1ed02e8b6e7d 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -186,6 +186,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 => @@ -193,7 +198,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" @@ -245,7 +250,7 @@ class PlainPrinter(_ctx: Context) extends Printer { }.close 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 From 8a332f7a043bb5259dbb70732b91b6b1b330a47b Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 23 Sep 2023 10:05:07 +0200 Subject: [PATCH 019/117] Also set CaptureChecked status for unpickled roots --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + 1 file changed, 1 insertion(+) 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 From d8f8c5a0dd47ab2ac6ab1739b50281e308333f50 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 23 Sep 2023 10:51:36 +0200 Subject: [PATCH 020/117] Let vals be lever owners Refactor everything so that now vals as well as defs can be level owners --- .../tools/dotc/cc/CaptureAnnotation.scala | 13 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 32 +++- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 21 ++- .../dotty/tools/dotc/cc/CheckCaptures.scala | 137 ++++++++++-------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 72 +++------ .../src/dotty/tools/dotc/cc/Synthetics.scala | 38 ++--- .../dotty/tools/dotc/core/TypeComparer.scala | 14 +- .../dotty/tools/dotc/transform/Recheck.scala | 5 +- tests/neg-custom-args/captures/byname.check | 2 +- .../captures/heal-tparam-cs.scala | 2 +- tests/neg-custom-args/captures/i15772.check | 3 +- tests/neg-custom-args/captures/lazylist.check | 2 +- .../captures/leaked-curried.check | 7 - .../captures/leaked-curried.scala | 2 +- .../captures/usingLogFile.check | 41 ++---- .../captures/usingLogFile.scala | 4 +- tests/neg-custom-args/captures/vars.check | 2 +- tests/pos-custom-args/captures/capt2.scala | 2 +- tests/pos-custom-args/captures/colltest.scala | 1 - .../captures/function-combinators.scala | 4 +- tests/pos-special/stdlib/Test1.scala | 2 +- tests/pos-special/stdlib/Test2.scala | 6 +- .../captures/colltest5/Test_2.scala | 4 +- 23 files changed, 220 insertions(+), 196 deletions(-) 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 abe4fa4bde77..452da11efa34 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -31,6 +31,13 @@ private val constrainRootsWhenMapping = true def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) +/** 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)(using Context): Type = + MethodType.companion(isContextual = isContextual)(args, resultType) + .toFunctionType(alwaysDependent = true) + /** An exception thrown if a @retains argument is not syntactically a CaptureRef */ class IllegalCaptureRef(tpe: Type) extends Exception(tpe.toString) @@ -152,6 +159,7 @@ extension (tp: Type) if (parent eq p) && (refs eq r) then tp else CapturingType(parent, refs, tp.isBoxed) + // TODO Move boxed/unboxed to CaapturingType? /** 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. @@ -160,9 +168,9 @@ extension (tp: Type) case tp @ CapturingType(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty => tp.annot match case ann: CaptureAnnotation => - ann.boxedType(tp) + AnnotatedType(parent, ann.boxedAnnot) case ann => - ann.tree.getAttachment(BoxedType) match + ann.tree.getAttachment(BoxedType) match // TODO drop case None => ann.tree.putAttachment(BoxedType, BoxedTypeCache()) case _ => ann.tree.attachment(BoxedType)(tp) @@ -171,6 +179,18 @@ extension (tp: Type) case _ => tp + /** 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 + /** 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 @@ -366,6 +386,7 @@ extension (sym: Symbol) case _ => false + // TODO Also include vals (right now they are manually entered in levelOwners by Setup) def isLevelOwner(using Context): Boolean = val symd = sym.denot def isCaseClassSynthetic = // TODO drop @@ -375,8 +396,11 @@ extension (sym: Symbol) symd.is(CaptureChecked) || symd.isRoot else symd.is(Method, butNot = Accessor) - && (!symd.owner.isClass || symd.owner.is(CaptureChecked)) - && !Synthetics.isExcluded(sym) + && (!symd.owner.isClass + || symd.owner.is(CaptureChecked) + || Synthetics.needsTransform(symd) + ) + //&& !Synthetics.isExcluded(sym) && !isCaseClassSynthetic && !symd.isConstructor && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 3cde5cae981a..e93521fe0072 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, ccSetup} import Annotations.Annotation import annotation.threadUnsafe import annotation.constructorOnly @@ -62,8 +62,7 @@ sealed abstract class CaptureSet extends Showable: def owner: Symbol def rootSet(using Context): CaptureSet = - if Setup.newScheme then owner.localRoot.termRef.singletonCaptureSet - else universal + owner.localRoot.termRef.singletonCaptureSet /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -142,7 +141,7 @@ sealed abstract class CaptureSet extends Showable: case y: TermRef => (y.prefix eq x) || x.isRootIncluding(y) case y: CaptureRoot.Var => x.isRootIncluding(y) case _ => false - || (x.isGenericRootCapability || y.isGenericRootCapability && x.isRootCapability) + || (x.isGenericRootCapability || y.isRootCapability && x.isRootCapability) && ctx.property(LooseRootChecking).isDefined private def isRootIncluding(y: CaptureRoot) = @@ -504,9 +503,11 @@ object CaptureSet: private def recordLevelError()(using Context): Unit = for elem <- triedElem do + capt.println(i"level error for $elem, $this") ccState.levelError = Some((elem, this)) private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match + case elem: TermRef if elem.symbol.isLevelOwner => elem.ccNestingLevel - 1 <= ownLevel case elem: (TermRef | ThisType) => elem.ccNestingLevel <= ownLevel case elem: CaptureRoot.Var => CaptureRoot.isEnclosingRoot(elem, owner.localRoot.termRef) case _ => true @@ -965,10 +966,16 @@ 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) + case ref: TermRef if ref.isRootCapability => + ref.singletonCaptureSet + case ref: TermRef if ref.symbol.isLevelOwner => + ofType(ref.underlying, followResult = true).filter( + ref.symbol.localRoot.termRef != _) + // TODO: Can replace filter with - ref.symbol.localRoot.termRef when we drop level nesting + case _ => + ofType(ref.underlying, followResult = true) /** Capture set of a type */ def ofType(tp: Type, followResult: Boolean)(using Context): CaptureSet = diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c0089bd99f9d..a5bbf0555dfc 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -6,13 +6,14 @@ 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, ccSetup} 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 import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property} import transform.SymUtils.* import transform.{Recheck, PreRecheck} @@ -193,7 +194,6 @@ class CheckCaptures extends Recheck, SymTransformer: val ccState = new CCState class CaptureChecker(ictx: Context) extends Rechecker(ictx): - import ast.tpd.* override def keepType(tree: Tree) = super.keepType(tree) @@ -273,13 +273,19 @@ class CheckCaptures extends Recheck, SymTransformer: 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) = 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.skipConstructor.owner) else CaptureSet.empty) /** For all nested environments up to `limit` or a closed environment perform `op`, @@ -342,14 +348,15 @@ class CheckCaptures extends Recheck, SymTransformer: def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) - 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) - super.recheckIdent(tree) + instantiateLocalRoots(tree.symbol, pt): + super.recheckIdent(tree, pt) /** A specialized implementation of the selection rule. * @@ -378,30 +385,38 @@ 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 - selType - else - val qualCs = qualType.captureSet - capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") - if qualCs.mightSubcapture(selCs) - && !selCs.mightSubcapture(qualCs) - && !pt.stripCapturing.isInstanceOf[SingletonType] - then - selType.widen.stripCapturing.capturing(qualCs) - .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) - else + instantiateLocalRoots(tree.symbol, pt): + if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then selType + else + val qualCs = qualType.captureSet + capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") + if qualCs.mightSubcapture(selCs) + && !selCs.mightSubcapture(qualCs) + && !pt.stripCapturing.isInstanceOf[SingletonType] + then + selType.widen.stripCapturing.capturing(qualCs) + .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) + else + 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] + /** Instantiate local roots of `sym` in type `tp` to root variables, provided + * - `sym` is a level owner, and + * - `tp` is the type of a function that gets applied, either as a method + * or as a function value that gets applied. + */ + def instantiateLocalRoots(sym: Symbol, pt: Type)(tp: Type)(using Context): Type = + def canInstantiate = + sym.is(Method, butNot = Accessor) + || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto + if sym.skipConstructor.isLevelOwner && canInstantiate then + val tpw = tp.widen + val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner, sym))(tpw) + .showing(i"INST $sym: $tp, ${sym.localRoot} = $result", ccSetup) + if tpw eq tp1 then tp else tp1 + else + tp /** A specialized implementation of the apply rule. * @@ -431,7 +446,7 @@ class CheckCaptures extends Recheck, SymTransformer: 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)) @@ -591,7 +606,10 @@ class CheckCaptures extends Recheck, SymTransformer: curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) 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 def checkInferredResult(tp: Type, tree: ValOrDefDef)(using Context): Type = @@ -624,7 +642,7 @@ class CheckCaptures extends Recheck, SymTransformer: tree.tpt match case tpt: InferredTypeTree if !canUseInferred => val expected = tpt.tpe.dropAllRetains - checkConformsExpr(tp, expected, tree.rhs, addenda(expected)) + todoAtPostCheck += (() => checkConformsExpr(tp, expected, tree.rhs, addenda(expected))) case _ => tp end checkInferredResult @@ -755,33 +773,33 @@ 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 actualw = actual.widen - val actual1 = mapRoots(defn.captureRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner))(actualw) - (actual1 ne actualw) && { - val res = super.isCompatible(actual1, expected) - if !res && ctx.settings.YccDebug.value then - println(i"Failure under mapped roots:") - println(i"${TypeComparer.explained(_.isSubType(actual, expected))}") - res - } - } - - /** Massage `actual` and `expected` types using the methods below before checking conformance */ + /** 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): Unit = val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) - val actual1 = adaptBoxed(actual, expected1, tree.srcPos) - //println(i"check conforms $actual1 <<< $expected1") - super.checkConformsExpr(actual1, expected1, tree, 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") + var ok = isCompatible(actualBoxed, expected1) + if !ok then stripTyped(tree) match + case tree: RefTree if tree.symbol.isLevelOwner => + // 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 actualWide = actual.widen + val actualInst = mapRoots(tree.symbol.localRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner))(actualWide) + capt.println(i"fallBack from $actualWide to $actualInst to match $expected1") + ok = (actualInst ne actualWide) + && isCompatible(adaptBoxed(actualInst, expected1, tree.srcPos), expected1) + case _ => + if !ok then + capt.println(i"conforms failed for ${tree}: $actual vs $expected") + err.typeMismatch(tree.withType(actualBoxed), expected, addenda ++ CaptureSet.levelErrors) + end checkConformsExpr /** Turn `expected` into a dependent function when `actual` is dependent. */ private def alignDependentFunction(expected: Type, actual: Type)(using Context): Type = @@ -792,7 +810,7 @@ class CheckCaptures extends Recheck, SymTransformer: 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) + val expected1 = depFun(args, resultType, isContextual) expected1 case _ => expected @@ -1036,7 +1054,8 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case t: Template => - checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) + checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t))( + using ctx.withProperty(LooseRootChecking, Some(()))) case _ => traverseChildren(t) @@ -1217,6 +1236,8 @@ class CheckCaptures extends Recheck, SymTransformer: end check end checker checker.traverse(unit)(using ctx.withOwner(defn.RootClass)) + for chk <- todoAtPostCheck do chk() + if !ctx.reporter.errorsReported then //inContext(ctx.withProperty(LooseRootChecking, Some(()))): // We dont report errors here if previous errors were reported, because other diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 5b8043fdf45d..b76f1abbce84 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -114,22 +114,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformSym(symd: SymDenotation)(using Context): SymDenotation = if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol - val needsInfoTransform = sym.isDefinedInCurrentRun && !toBeUpdated.contains(sym) - // if sym is class && level owner: add a capture root - // translate cap/capIn to local roots - // create local roots if necessary + def mappedInfo = + if toBeUpdated.contains(sym) then symd.info + else transformExplicitType(symd.info, rootTarget = sym) + // TODO if sym is class && level owner: add a capture root if Synthetics.needsTransform(symd) then - Synthetics.transform(symd) + 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 = - if needsInfoTransform then - atPhase(thisPhase.next)(symd.maybeOwner.info) // ensure owner is completed - transformExplicitType(sym.info, rootTarget = if newScheme && false then sym else NoSymbol) - else sym.info - + val newInfo = mappedInfo if newFlags != symd.flags || (newInfo ne sym.info) then symd.copySymDenotation(initFlags = newFlags, info = newInfo) else symd @@ -137,15 +132,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd end transformSym - /** Create dependent function with underlying function class `tycon` and given - * arguments `argTypes` and result `resType`. - */ - 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) - /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, * convert it to be boxed. */ @@ -237,7 +223,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val args1 = mapNested(args0) val res1 = this(res0) if isTopLevel then - depFun(tycon1, args1, res1) + depFun(args1, res1, + isContextual = defn.isContextFunctionClass(tycon1.classSymbol)) .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", ccSetup) else if (tycon1 eq tycon) && (args1 eq args0) && (res1 eq res0) then tp @@ -466,27 +453,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val sym = tree.symbol val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): - val rootsNeedMap = - // need to run before cc so that rhsClosure is defined without transforming - // symbols to cc, since rhsClosure is used in that transformation. - tree.rhs match - case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => - //ddef.symbol.setNestingLevel(ctx.owner.nestingLevel + 1) - if newScheme && false then ccState.isLevelOwner(sym) = true - ccState.isLevelOwner(ddef.symbol) = true - // 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. - (newScheme && false) || !tpt.isInstanceOf[InferredTypeTree] - // in this case roots in inferred val type count as polymorphic - case _ => - true + tree.rhs match + case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => + if !sym.is(Mutable) then ccState.isLevelOwner(sym) = true + // TODO Drop the possibleyTypedClosureDef condition anc replace by the + // condition that the val takes a cap parameter + case _ => 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 - rootTarget = if rootsNeedMap then ctx.owner else NoSymbol + rootTarget = ctx.owner ) ccSetup.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) @@ -555,10 +531,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 = - val mapr = - if !(newScheme && false) && sym.isLevelOwner - then mapRoots(sym.localRoot.termRef, defn.captureRoot.termRef) - else identity[Type] info match case mt: MethodOrPoly => val psyms = psymss.head @@ -568,17 +540,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: mt.paramInfos else val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => mapr(subst(psym.nextInfo)).asInstanceOf[mt.PInfo]), + psyms.map(psym => subst(psym.nextInfo).asInstanceOf[mt.PInfo]), mt1 => - mapr(integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas)) + integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) ) case info: ExprType => info.derivedExprType(resType = - mapr(integrateRT(info.resType, psymss, resType, prevPsymss, prevLambdas))) + integrateRT(info.resType, psymss, resType, prevPsymss, prevLambdas)) case info => - mapr( - if prevLambdas.isEmpty then resType - else SubstParams(prevPsymss, prevLambdas)(resType)) + if prevLambdas.isEmpty then resType + else SubstParams(prevPsymss, prevLambdas)(resType) if sym.exists && signatureChanges then def absInfo(resType: Type): Type = @@ -597,7 +568,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // are checked on demand assert(!isDuringSetup, i"$sym") assert(ctx.phase == thisPhase.next, i"$sym") - ccSetup.println(i"FORCING $sym") + ccSetup.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)) diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index f59ac80ef1b5..7932a29fea1b 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -59,9 +59,11 @@ 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 = + + def localRootSet = symd.symbol.localRoot.termRef.singletonCaptureSet /** 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: @@ -99,7 +101,7 @@ object Synthetics: info.derivedLambdaType(resType = transformDefaultGetterCaptures(info.resType, owner, idx)) case info: ExprType => info.derivedExprType(transformDefaultGetterCaptures(info.resType, owner, idx)) - case RetainingType(parent, _) => + case CapturingType(parent, _) => transformDefaultGetterCaptures(parent, owner, idx) case info @ AnnotatedType(parent, annot) => info.derivedAnnotatedType(transformDefaultGetterCaptures(parent, owner, idx), annot) @@ -117,7 +119,7 @@ object Synthetics: def transformUnapplyCaptures(info: Type)(using Context): Type = info match case info: MethodType => val paramInfo :: Nil = info.paramInfos: @unchecked - val newParamInfo = CapturingType(paramInfo, CaptureSet.universal) + val newParamInfo = CapturingType(paramInfo, localRootSet) val trackedParam = info.paramRefs.head def newResult(tp: Type): Type = tp match case tp: MethodOrPoly => @@ -129,17 +131,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 => mt.paramInfos.map(_.capturing(localRootSet)), 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 @@ -149,19 +151,19 @@ object Synthetics: ExprType(mapFinalResult(et.resType, CapturingType(_, CaptureSet(enclThis)))) def transformCompareCaptures = - MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType) + MethodType(defn.ObjectType.capturing(localRootSet) :: 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/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 97277eeb6600..84167fbeb7f2 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. @@ -542,7 +542,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling 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) + then + val tp2a = + if tp1.isBoxedCapturing && !parent1.isBoxedCapturing + then tp2.unboxed + else tp2 + recur(parent1, tp2a) else thirdTry case tp1: AnnotatedType if !tp1.isRefining => recur(tp1.parent, tp2) @@ -995,7 +1000,10 @@ 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 (ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev) + && tp1.isTracked + => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index cef6ddabd468..8fbfddf3581c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -207,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 = @@ -295,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 = @@ -476,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 => diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 781b26b6944d..67f42a4acc69 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -2,7 +2,7 @@ 10 | h(f2()) // error | ^^^^ | Found: (x$0: Int) ->{cap1} Int - | Required: (x$0: Int) ->{cap2} Int + | Required: Int ->{cap2} Int | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index f72325a0be8a..d362f9681d6e 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -11,7 +11,7 @@ def main(io: Cap^{cap}, net: Cap^{cap}): Unit = { } val test2: (c: Cap^{cap}) -> () ->{cap} Unit = - localCap { c => // error, was: should work + localCap { c => // should work (was error) (c1: Cap^{cap}) => () => { c1.use() } } diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 22c95f8a8ed5..0ec4fef1c46d 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -25,8 +25,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - |Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating box2)} ->{'cap[..main3](from instantiating box2)} Unit) ->? - | Unit + |Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating c)} ->{'cap[..main3](from instantiating box2)} Unit) ->? Unit |Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 4b7611fc3fb7..ba5080d808f8 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -37,6 +37,6 @@ 22 | def tail: LazyList[Nothing]^ = ??? // error overriding | ^ | error overriding method tail in class LazyList of type -> lazylists.LazyList[Nothing]; - | method tail of type -> lazylists.LazyList[Nothing]^ has incompatible type + | method tail of type -> lazylists.LazyList[Nothing]^{cap[tail]} has incompatible type | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 1918fecbc496..a3665b50b6f6 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -2,10 +2,3 @@ 13 | () => () => io // error | ^^ |(io : Cap^{cap[main]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure --- [E164] Declaration Error: tests/neg-custom-args/captures/leaked-curried.scala:12:10 --------------------------------- -12 | val get: () ->{} () ->{io} Cap^ = // error - | ^ - | error overriding value get in trait Box of type () -> () ->{cap[Box]} Cap^{cap[Box]}; - | value get of type () -> () ->{io} Cap^{cap[Foo]} has incompatible type - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.scala b/tests/neg-custom-args/captures/leaked-curried.scala index a566999d7c39..30ec4f7dbab5 100644 --- a/tests/neg-custom-args/captures/leaked-curried.scala +++ b/tests/neg-custom-args/captures/leaked-curried.scala @@ -9,7 +9,7 @@ trait Box: def main(): Unit = val leaked = withCap: (io: Cap^) => class Foo extends Box, Pure: - val get: () ->{} () ->{io} Cap^ = // error + val get: () ->{} () ->{io} Cap^ = () => () => io // error new Foo val bad = leaked.get()().use() // using a leaked capability diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 5fd9b417e260..eb24f76d748c 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,16 +1,3 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:23:27 --------------------------------- -23 | val later = usingLogFile { f => () => f.write(0) } // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: () ->{x$0, local} Unit - | Required: () -> Unit - | - | Note that the expected type () -> Unit - | is the previously inferred type of value later - | which is also the type seen in separately compiled sources. - | The new inferred type box () ->{x$0, local} Unit - | must conform to this type. - | - | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/usingLogFile.scala:32:37 ------------------------------------------------------ 32 | usingLogFile { f => later3 = () => f.write(0) } // error | ^ @@ -23,24 +10,26 @@ | Required: Test2.Cell[box () ->{cap[]} Unit] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:47:27 --------------------------------- -47 | val later = usingLogFile { f => () => f.write(0) } // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: () ->{x$0, local} Unit - | Required: () -> Unit - | - | Note that the expected type () -> Unit - | is the previously inferred type of value later - | which is also the type seen in separately compiled sources. - | The new inferred type box (() ->{x$0, local} Unit)^? - | must conform to this type. - | - | 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 -- 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 | ^^^^^^^^^ | escaping local reference local.type +-- Error: tests/neg-custom-args/captures/usingLogFile.scala:70:16 ------------------------------------------------------ +70 | val later = usingFile("logfile", // error !!! but should be ok, since we can widen `l` to `file` instead of to `cap` + | ^^^^^^^^^ + | reference (_$1 : java.io.OutputStream^{local}) is not included in the allowed capture set {x$0, local} + | + | Note that reference (_$1 : java.io.OutputStream^{local}), defined at level 2 + | cannot be included in outer capture set {x$0, local}, defined at level 1 in method test diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index b87b81d0eda8..a983231ff51a 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -67,6 +67,6 @@ object Test4: 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.check b/tests/neg-custom-args/captures/vars.check index 74000c79a212..4452a3b2da6b 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -14,7 +14,7 @@ 24 | a = g // error | ^ | Found: box (x: String) ->{cap3} String - | Required: box (x$0: String) ->{cap[test]} String + | Required: box String ->{cap[test]} String | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:26:12 ----------------------------------------- diff --git a/tests/pos-custom-args/captures/capt2.scala b/tests/pos-custom-args/captures/capt2.scala index 45381bf602ed..1df2ea69b119 100644 --- a/tests/pos-custom-args/captures/capt2.scala +++ b/tests/pos-custom-args/captures/capt2.scala @@ -13,7 +13,7 @@ def test2() = z: (() -> Unit) @retains(x) def z2: (() -> Unit) @retains(y) = y z2: (() -> Unit) @retains(y) - val p: () => String = () => "abc" + val p: () ->{cap[test2]} String = () => "abc" val q: C^{p} = ??? val _ = p: (() ->{p} String) 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/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-special/stdlib/Test1.scala b/tests/pos-special/stdlib/Test1.scala index 786e3aaa2bf1..d5f26060651c 100644 --- a/tests/pos-special/stdlib/Test1.scala +++ b/tests/pos-special/stdlib/Test1.scala @@ -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..26b2faee1165 100644 --- a/tests/pos-special/stdlib/Test2.scala +++ b/tests/pos-special/stdlib/Test2.scala @@ -8,7 +8,7 @@ object Test { def seqOps(xs: Seq[Int]) = { // try with Seq[Int]^{cap} val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[seqOps]} Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => x :: -x :: Nil val x1 = xs.foldLeft("")(strPlusInt) @@ -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/run-custom-args/captures/colltest5/Test_2.scala b/tests/run-custom-args/captures/colltest5/Test_2.scala index fbb22039c327..64e8d4a88cf3 100644 --- a/tests/run-custom-args/captures/colltest5/Test_2.scala +++ b/tests/run-custom-args/captures/colltest5/Test_2.scala @@ -8,7 +8,7 @@ object Test { def seqOps(xs: Seq[Int]) = { // try with Seq[Int]^{cap} val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int => Boolean = _ % 2 == 0 + val isEven: Int ->{cap[seqOps]} Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => Cons(x, Cons(-x, Nil)) val x1 = xs.foldLeft("")(strPlusInt) @@ -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) From 4323b5b7ca47c94a2c453e8e2feaf2ce57aecb93 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 23 Sep 2023 13:39:12 +0200 Subject: [PATCH 021/117] Refactor wellformed checks Innstead of traversing old types at postCheck, note what needs to be done when these types are first transformed at Setup. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 49 +------------ compiler/src/dotty/tools/dotc/cc/Setup.scala | 69 +++++++++++++++++-- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a5bbf0555dfc..6b22e062725a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -132,45 +132,6 @@ 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. - * Note: We need to perform the check on the original annotation rather than its - * capture set since the conversion to a capture set already eliminates redundant elements. - */ - def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = - val normCap = new TypeMap: - def apply(t: Type): Type = t match - case t: TermRef if t.isGenericRootCapability => ctx.owner.localRoot.termRef - case _ => t - - 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 = - val remaining = others.map(normCap) - if remaining.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) - 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 - /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -1059,10 +1020,9 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) - private var setup: SetupAPI = compiletime.uninitialized + private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = - setup = thisPhase.prev.asInstanceOf[Setup] setup.setupUnit(ctx.compilationUnit.tpdTree, recheckDef) if ctx.settings.YccPrintSetup.value then @@ -1215,12 +1175,6 @@ 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.tpe.foreachPart: - case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot => - checkWellformedPost(parent, annot.tree, tree) - case _ => case t @ TypeApply(fun, args) => fun.knownType.widen match case tl: PolyType => @@ -1237,6 +1191,7 @@ class CheckCaptures extends Recheck, SymTransformer: 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(()))): diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b76f1abbce84..bab0f281655d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -22,15 +22,14 @@ 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: def newScheme(using Context) = ctx.settings.YccNew.value // if new impl is conditional private val IsDuringSetupKey = new Property.Key[Unit] - def isDuringSetup(using Context): Boolean = - ctx.property(IsDuringSetupKey).isDefined - //|| ctx.phase == Phases.checkCapturesPhase.prev + def isDuringSetup(using Context): Boolean = ctx.property(IsDuringSetupKey).isDefined case class Box(t: Type) extends UncachedGroundType, TermType: override def fallbackToText(printer: Printer): Text = @@ -257,7 +256,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def transformInferredType(tp: Type, rootTarget: Symbol)(using Context): Type = mapInferred(rootTarget)(tp) - private def transformExplicitType(tp: Type, rootTarget: Symbol)(using Context): Type = + private def transformExplicitType(tp: Type, rootTarget: Symbol, tptToCheck: Option[Tree] = None)(using Context): Type = val expandAliases = new TypeMap: override def toString = "expand aliases" @@ -339,8 +338,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: t.derivedCapturingType(this(parent), refs) case t @ AnnotatedType(parent, ann) => if ann.symbol == defn.RetainsAnnot then - checkQualifiedRoots(ann.tree) - CapturingType(this(parent), ann.tree.toCaptureSet) + val parent1 = this(parent) + for tpt <- tptToCheck do + checkQualifiedRoots(ann.tree) + checkWellformedLater(parent1, ann.tree, tpt) + CapturingType(parent1, ann.tree.toCaptureSet) else t.derivedAnnotatedType(this(parent), ann) case Box(t1) => @@ -371,7 +373,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact then transformInferredType(tp, rootTarget) - else transformExplicitType(tp, rootTarget)) + else transformExplicitType(tp, rootTarget, tptToCheck = Some(tree))) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -725,4 +727,57 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: setupTraverser(recheckDef).traverse(tree)( using ctx.withPhase(thisPhase).withProperty(IsDuringSetupKey, Some(()))) + // ------ Checks to run after main capture checking -------------------------- + + /** 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") + val normCap = new TypeMap: + def apply(t: Type): Type = t match + case t: TermRef if t.isGenericRootCapability => ctx.owner.localRoot.termRef + case _ => t + + 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 = + val remaining = others.map(normCap) + if remaining.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) + 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 From 99e3fe328aa39a8f114ffa6948f70a5b3f3cf241 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 24 Sep 2023 00:02:41 +0200 Subject: [PATCH 022/117] Drop boxedUnlessFun and boxedIfTypeParam operations With the new way to construct capturing types at Setup, we can add the correct boxing annotations there, so the previous unmodular hack can be dropped. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 24 ------------------- .../dotty/tools/dotc/cc/CapturingType.scala | 12 ++++++++++ .../dotty/tools/dotc/cc/RetainingType.scala | 7 +++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 16 +++++++++---- .../tools/dotc/core/TypeApplications.scala | 5 ++-- .../dotty/tools/dotc/core/TypeComparer.scala | 8 +++---- .../src/dotty/tools/dotc/core/Types.scala | 10 ++++---- tests/neg-custom-args/captures/vars.check | 8 +++---- 8 files changed, 43 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 452da11efa34..3e44f5fe4781 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -191,18 +191,6 @@ extension (tp: Type) 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) = // TODO: drop - if ctx.phase != Phases.checkCapturesPhase - && ctx.phase != Phases.checkCapturesPhase.prev - || defn.isFunctionSymbol(tycon.typeSymbol) - then tp - else tp.boxed - /** 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 * intersections, and type proxies other than abstract types. @@ -498,15 +486,3 @@ extension (tp: AnnotatedType) 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) = // TODO drop - if ctx.phase != Phases.checkCapturesPhase - && ctx.phase != Phases.checkCapturesPhase.prev - || defn.isFunctionClass(tycon.typeSymbol) - then ts - else ts.mapconserve(_.boxed) - diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 95ca10cf3674..ae7838662c1d 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. @@ -59,6 +60,17 @@ object CapturingType: Some((parent, ann.refs)) case AnnotatedType(parent, ann) if ann.symbol == defn.RetainsAnnot && ctx.phase == Phases.checkCapturesPhase => + // 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 _ => diff --git a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala index 5af59b193aa3..7902b03445fb 100644 --- a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala @@ -26,9 +26,10 @@ object RetainingType: if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then tp.annot match case _: CaptureAnnotation => - new Error(i"bad retains $tp").printStackTrace - assert(false) - case ann => Some((tp.parent, ann.tree.retainedElems)) + 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 bab0f281655d..3f450d0ab5e2 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -257,7 +257,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: mapInferred(rootTarget)(tp) private def transformExplicitType(tp: Type, rootTarget: Symbol, tptToCheck: Option[Tree] = None)(using Context): Type = - val expandAliases = new TypeMap: + val expandAliases = new DeepTypeMap: override def toString = "expand aliases" /** Expand $throws aliases. This is hard-coded here since $throws aliases in stdlib @@ -478,6 +478,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: inContext(ctx.withOwner(tree.symbol)): traverseChildren(tree) + case tree @ SeqLiteral(elems, tpt: TypeTree) => + traverse(elems) + transformTT(tpt, boxed = true, exact = false, rootTarget = ctx.owner) + case _ => traverseChildren(tree) postProcess(tree) @@ -491,7 +495,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: TypeTree => val lowner = transformTT(tree, boxed = false, exact = false, - rootTarget = ctx.owner.unlessStatic // other types in static locations are not boxed + rootTarget = ctx.owner.unlessStatic // roots of other types in static locations are not mapped ) case tree: ValOrDefDef => val sym = tree.symbol @@ -592,13 +596,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(cinfo.selfType, CaptureSet.Var(cls)) else selfInfo match case selfInfo: Type => - inContext(ctx.withOwner(cls)): + val selfInfo1 = inContext(ctx.withOwner(cls)): transformExplicitType(selfInfo, rootTarget = ctx.owner) + if selfInfo1 eq selfInfo then NoType else selfInfo1 case _ => NoType if newSelfType.exists then ccSetup.println(i"mapped self type for $cls: $newSelfType, was $selfInfo") - val newInfo = ClassInfo(prefix, cls, ps, decls, newSelfType) + val ps1 = ps.mapConserve(transformExplicitType(_, rootTarget = ctx.owner)) + val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) updateInfo(cls, newInfo) cls.thisType.asInstanceOf[ThisType].invalidateCaches() if cls.is(ModuleClass) then @@ -691,6 +697,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 _ => 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 84167fbeb7f2..62559a037703 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -579,7 +579,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 @@ -913,7 +913,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) @@ -947,7 +947,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 _ => @@ -1800,7 +1800,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) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index a2bae7275c2b..4fb2e11d5b6d 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, RetainingType, boxedUnlessFun, ccNestingLevel} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, ccNestingLevel} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -1456,12 +1456,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 @@ -2714,7 +2714,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 diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 4452a3b2da6b..98991fdf9857 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -17,11 +17,11 @@ | Required: box String ->{cap[test]} String | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:26:12 ----------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:26:13 ----------------------------------------- 26 | b = List(g) // error - | ^^^^^^^ - | Found: List[box (x$0: String) ->{cap3} String] - | Required: List[box String ->{cap[test]} String] + | ^ + | Found: Seq[(x: String) ->{cap3} String] + | Required: Seq[box (x$0: String) ->{cap3} String] | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/vars.scala:35:2 --------------------------------------------------------------- From 3544db423fe37ba27e7c82059feebe938d2a66af Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 25 Sep 2023 21:34:54 +0200 Subject: [PATCH 023/117] Fix followAlias --- compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala | 2 +- tests/neg-custom-args/captures/i15772.check | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 4e1241518963..0651d5699892 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -33,7 +33,7 @@ object CaptureRoot: def followAlias: CaptureRoot = alias match case alias: Var if alias ne this => alias.followAlias - case _ => this + case _ => alias def locallyConsistent = lowerLevel <= upperLevel diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 0ec4fef1c46d..c62dae2f7627 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^{cap[main1]} ->{cap[main1]} Unit) -> Unit + | Found: (C{val arg: C^{cap[C]}}^{c} ->{cap[main1], c} Unit) ->{c} Unit + | Required: (C^{cap[main1]} ->{cap[main1]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ @@ -18,15 +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^{cap[main2]} ->{cap[main2]} Unit) -> Unit + | Found: (C{val arg: C^{cap[C]}}^{c} ->{cap[main2], c} Unit) ->{c} Unit + | Required: (C^{cap[main2]} ->{cap[main2]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - |Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating c)} ->{'cap[..main3](from instantiating box2)} Unit) ->? Unit - |Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit + | Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating c)} ->{cap[main3]} Unit) ->? Unit + | Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- From c1f198e78d832403bc6a3c98f2a97bead4dae399 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Sep 2023 15:06:47 +0200 Subject: [PATCH 024/117] Also interpolate root variables in non-variable capture sets. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 40 ++++++++----------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 21 +++++----- tests/pos-custom-args/captures/vars.scala | 2 +- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 6b22e062725a..6adbca05e4f7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -165,30 +165,24 @@ 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) + override def traverse(t: Type) = t match + case t @ CapturingType(parent, refs) => + refs match + case refs: CaptureSet.Var if variance < 0 => refs.solve() + case _ => + for ref <- refs.elems do + ref match + case ref: CaptureRoot.Var => + ref.followAlias match + case rv: CaptureRoot.Var if rv.upperBound == ctx.owner.levelOwner => + rv.setAlias(ctx.owner.localRoot.termRef) case _ => - case _ => - traverse(parent) - case t @ defn.RefinedFunctionOf(rinfo) => - traverse(rinfo) - case tp: TypeVar => - case tp: TypeRef => - traverse(tp.prefix) - case _ => - traverseChildren(t) + 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. diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 3f450d0ab5e2..d17447221b3f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -426,6 +426,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + def transformResultType(tpt: TypeTree, sym: Symbol)(using Context) = + transformTT(tpt, + boxed = sym.is(Mutable, butNot = Method), // types of mutable variables are boxed + exact = sym.allOverriddenSymbols.hasNext, // types of symbols that override a parent don't get a capture set TODO drop + rootTarget = ctx.owner) + def traverse(tree: Tree)(using Context): Unit = tree match case tree @ DefDef(_, paramss, tpt: TypeTree, _) => @@ -435,9 +441,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: inContext(ctx.withOwner(meth)): paramss.foreach(traverse) - transformTT(tpt, boxed = false, - exact = tree.symbol.allOverriddenSymbols.hasNext, - rootTarget = ctx.owner) + transformResultType(tpt, meth) traverse(tree.rhs) //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") @@ -461,11 +465,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // TODO Drop the possibleyTypedClosureDef condition anc replace by the // condition that the val takes a cap parameter case _ => - 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 - rootTarget = ctx.owner - ) + transformResultType(tpt, sym) ccSetup.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) @@ -563,7 +563,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: .showing(i"update info $sym: ${sym.info} = $result", ccSetup) val newInfo = absInfo(localReturnType) if newInfo ne sym.info then - updateInfo(sym, + val updatedInfo = if sym.isAnonymousFunction || sym.is(Param) || sym.is(ParamAccessor) then // closures are handled specially; the newInfo is constrained from // the expected type and only afterwards we recheck the definition @@ -577,7 +577,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: ccSetup.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)) + recheckDef(tree, sym) + updateInfo(sym, updatedInfo) case tree: Bind => val sym = tree.symbol 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 From 19a1fa720dbd6b35da4e2d703883b0cdbfcb8ee5 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Sep 2023 13:47:43 +0200 Subject: [PATCH 025/117] Refactor addNewElems Break out operation to add a single element. This makes widenCaptures redundant. --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 59 ++++++++----------- .../dotty/tools/dotc/cc/CheckCaptures.scala | 29 ++++----- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index e93521fe0072..7b1280fd905a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -101,7 +101,12 @@ sealed abstract class CaptureSet extends Showable: * @return CompareResult.OK if elements were added, or a conflicting * capture set that prevents addition otherwise. */ - protected def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult + protected def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + (CompareResult.OK /: newElems): (r, elem) => + r.andAlso(addNewElem(elem, origin)) + + /** Add a single element, with the same meaning as in `addNewElems` */ + protected def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult /** If this is a variable, add `cs` as a dependent set */ protected def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult @@ -314,8 +319,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 @@ -391,7 +396,7 @@ object CaptureSet: def isConst = true def isAlwaysEmpty = elems.isEmpty - def addNewElems(elems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = CompareResult.fail(this) def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -413,7 +418,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 addNewElem(elem: CaptureRef, origin: CaptureSet)(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 = "" @@ -452,7 +457,7 @@ object CaptureSet: var rootAddedHandler: () => Context ?=> Unit = () => () /** 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 = "" @@ -482,23 +487,26 @@ object CaptureSet: def resetDeps()(using state: VarState): Unit = deps = state.deps(this) - def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + final def addNewElem(elem: CaptureRef, origin: CaptureSet)(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() + else if !levelOK(elem) then + val saved = triedElem + triedElem = triedElem.orElse(Some(elem)) + val res = + if elem.isRootCapability then CompareResult.fail(this) + else addNewElems(elem.captureSetOfInfo.elems, origin) + if res.isOK then triedElem = saved // reset only in case of success, leave as is on error + else recordLevelError() res else //assert(id != 2, newElems) - elems ++= newElems - if isUniversal then rootAddedHandler() - newElemAddedHandler(newElems.toList) + elems += elem + if elem.isGenericRootCapability then rootAddedHandler() + newElemAddedHandler(elem) // assert(id != 5 || elems.size != 3, this) (CompareResult.OK /: deps) { (r, dep) => - r.andAlso(dep.tryInclude(newElems, this)) + r.andAlso(dep.tryInclude(elem, this)) } private def recordLevelError()(using Context): Unit = @@ -512,23 +520,6 @@ object CaptureSet: case elem: CaptureRoot.Var => CaptureRoot.isEnclosingRoot(elem, owner.localRoot.termRef) case _ => true - private def widenCaptures(elems: Refs)(using Context): Option[Refs] = - val res = optional: - (SimpleIdentitySet[CaptureRef]() /: elems): (acc, elem) => - if levelOK(elem) then acc + elem - else - val saved = triedElem - triedElem = triedElem.orElse(Some(elem)) - if elem.isRootCapability then break() - val res = acc ++ widenCaptures(elem.captureSetOfInfo.elems).? - triedElem = saved // reset only in case of success, leave as is on error - res - def resStr = res match - case Some(refs) => i"${refs.toList}" - case None => "FAIL" - capt.println(i"widen captures ${elems.toList} for $this at $owner = $resStr") - res - def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = if (cs eq this) || cs.isUniversal || isConst then CompareResult.OK @@ -542,7 +533,7 @@ object CaptureSet: 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) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 6adbca05e4f7..e5d505a6578b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1118,22 +1118,23 @@ 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) - else - val widened = ref.captureSetOfInfo - val added = widened.filter(isAllowed(_)) - capt.println(i"heal $ref in $cs by widening to $added") - checkSubset(added, cs, tree.srcPos) - recur(widened.elems.toList) - recur(elems) + def 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") + checkSubset(added, cs, tree.srcPos) + widened.elems.foreach(recur) + case _ => + recur(elem) def traverse(tp: Type) = tp match From c933a7444c853889372143b90c92cc22f0b0e7d4 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Sep 2023 18:17:30 +0200 Subject: [PATCH 026/117] Refactor level error reporting Avoid the mutable state in CapturSet.Var --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 57 +++++++++++-------- .../captures/lazylists-exceptions.check | 3 - tests/neg-custom-args/captures/real-try.check | 6 -- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3e44f5fe4781..12d344c28c12 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -56,7 +56,7 @@ 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.LevelError] = None /** Under saferExceptions: The symbol generated for a try. * Installed by Setup, removed by CheckCaptures. diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 7b1280fd905a..cc8fab8b87df 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -205,7 +205,8 @@ sealed abstract class CaptureSet extends Showable: case elem :: elems1 => var result = that.tryInclude(elem, this) if !result.isOK && !elem.isRootCapability && summon[VarState] != FrozenState then - result = elem.captureSetOfInfo.subCaptures(that) + ccState.levelError = ccState.levelError.orElse(result.levelError) + result = result.orElse(elem.captureSetOfInfo.subCaptures(that)) if result.isOK then recur(elems1) else @@ -461,8 +462,6 @@ object CaptureSet: var description: String = "" - private var triedElem: Option[CaptureRef] = None - /** Record current elements in given VarState provided it does not yet * contain an entry for this variable. */ @@ -491,14 +490,9 @@ object CaptureSet: if isConst || !recordElemsState() then CompareResult.fail(this) // fail if variable is solved or given VarState is frozen else if !levelOK(elem) then - val saved = triedElem - triedElem = triedElem.orElse(Some(elem)) - val res = - if elem.isRootCapability then CompareResult.fail(this) - else addNewElems(elem.captureSetOfInfo.elems, origin) - if res.isOK then triedElem = saved // reset only in case of success, leave as is on error - else recordLevelError() - res + val res = CompareResult.levelError(this, elem) + if elem.isRootCapability then res + else res.orElse(addNewElems(elem.captureSetOfInfo.elems, origin)) else //assert(id != 2, newElems) elems += elem @@ -509,11 +503,6 @@ object CaptureSet: r.andAlso(dep.tryInclude(elem, this)) } - private def recordLevelError()(using Context): Unit = - for elem <- triedElem do - capt.println(i"level error for $elem, $this") - ccState.levelError = Some((elem, this)) - private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match case elem: TermRef if elem.symbol.isLevelOwner => elem.ccNestingLevel - 1 <= ownLevel case elem: (TermRef | ThisType) => elem.ccNestingLevel <= ownLevel @@ -853,23 +842,44 @@ object CaptureSet: type CompareResult = CompareResult.TYPE + case class LevelError(cs: CaptureSet, elem: CaptureRef) + /** 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) + opaque type TYPE = + Unit // Success + | CaptureSet // Failure with blocking set + | LevelError // Failure due to level error + val OK: TYPE = () def fail(cs: CaptureSet): TYPE = cs + def levelError(cs: CaptureSet, ref: CaptureRef): TYPE = LevelError(cs, ref) extension (result: TYPE) /** The result is OK */ - def isOK: Boolean = result eq OK + def isOK: Boolean = result == () /** 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" + def blocking: CaptureSet = (result: @unchecked) match + case cs: CaptureSet => cs + case LevelError(cs, _) => cs + def levelError: Option[LevelError] = result match + case result: LevelError => Some(result) + case _ => None + inline def andAlso(op: Context ?=> TYPE)(using Context): TYPE = + if result.isOK then op else result + inline def orElse(op: Context ?=> TYPE)(using Context): TYPE = + if result.isOK then result + else + val alt = op + if alt.isOK then alt + else result + def show(using Context): String = result match + case () => "OK" + case cs: CaptureSet => cs.show + case LevelError(cs, ref) => i"($ref at wrong level for $cs)" end CompareResult /** A VarState serves as a snapshot mechanism that can undo @@ -1044,7 +1054,8 @@ object CaptureSet: def levelErrors: Addenda = new Addenda: override def toAdd(using Context) = - for (ref, cs) <- ccState.levelError.toList yield + for LevelError(cs, ref) <- ccState.levelError.toList yield + ccState.levelError = None val levelStr = ref match case ref: (TermRef | ThisType) => i", defined at level ${ref.ccNestingLevel}" case _ => "" diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 57e5e65cd374..702f8e6260b9 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -3,9 +3,6 @@ | ^ | Found: LazyList[Int]^{cap[]} | Required: LazyList[Int]^? - | - | Note that reference (cap[] : caps.Cap), defined at level 2 - | cannot be included in outer capture set ?, defined at level 1 in method problem 38 | if i > 9 then throw Ex1() 39 | i * i 40 | } diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index eee1b8c456cc..f969404cde73 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -10,9 +10,6 @@ | Found: () ->{cap[]} 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 @@ -20,9 +17,6 @@ | Found: () ->{cap[]} 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` -- Error: tests/neg-custom-args/captures/real-try.scala:31:21 ---------------------------------------------------------- 31 | Cell(() => foo(1))// // error From 764302d1093ecb80007bcf2ff535d192ab30ea36 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 29 Sep 2023 11:18:01 +0200 Subject: [PATCH 027/117] Make CompareResult an enum # Conflicts: # compiler/src/dotty/tools/dotc/cc/Setup.scala --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 91 +++++++++---------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 12d344c28c12..f10ccdac4113 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -56,7 +56,7 @@ 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[CaptureSet.LevelError] = None + var levelError: Option[CaptureSet.CompareResult.LevelError] = None /** Under saferExceptions: The symbol generated for a try. * Installed by Setup, removed by CheckCaptures. diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index cc8fab8b87df..6cb555ef3f88 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -398,7 +398,7 @@ object CaptureSet: def isAlwaysEmpty = elems.isEmpty def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - CompareResult.fail(this) + CompareResult.Fail(this) def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -488,9 +488,9 @@ object CaptureSet: final def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if isConst || !recordElemsState() then - CompareResult.fail(this) // fail if variable is solved or given VarState is frozen + CompareResult.Fail(this) // fail if variable is solved or given VarState is frozen else if !levelOK(elem) then - val res = CompareResult.levelError(this, elem) + val res = CompareResult.LevelError(this, elem) if elem.isRootCapability then res else res.orElse(addNewElems(elem.captureSetOfInfo.elems, origin)) else @@ -516,7 +516,7 @@ object CaptureSet: deps += cs CompareResult.OK else - CompareResult.fail(this) + CompareResult.Fail(this) override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = rootAddedHandler = handler @@ -660,7 +660,7 @@ object CaptureSet: .andAlso { if added.isConst then CompareResult.OK else if added.asVar.recordDepsState() then { addAsDependentTo(added); CompareResult.OK } - else CompareResult.fail(this) + else CompareResult.Fail(this) } .andAlso { if (origin ne source) && (origin ne initial) && mapIsIdempotent then @@ -677,7 +677,7 @@ object CaptureSet: // 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) + CompareResult.Fail(this) else CompareResult.OK } @@ -743,7 +743,7 @@ object CaptureSet: super.addNewElems(newElems, origin) .andAlso { if filtered.size == newElems.size then source.tryInclude(newElems, this) - else CompareResult.fail(this) + else CompareResult.Fail(this) } override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = @@ -840,46 +840,41 @@ object CaptureSet: /** A TypeMap that is the identity on capture references */ trait IdentityCaptRefMap extends TypeMap - type CompareResult = CompareResult.TYPE - - case class LevelError(cs: CaptureSet, elem: CaptureRef) - - /** 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 = - Unit // Success - | CaptureSet // Failure with blocking set - | LevelError // Failure due to level error - val OK: TYPE = () - def fail(cs: CaptureSet): TYPE = cs - def levelError(cs: CaptureSet, ref: CaptureRef): TYPE = LevelError(cs, ref) - - extension (result: TYPE) - /** The result is OK */ - def isOK: Boolean = result == () - /** If not isOK, the blocking capture set */ - def blocking: CaptureSet = (result: @unchecked) match - case cs: CaptureSet => cs - case LevelError(cs, _) => cs - def levelError: Option[LevelError] = result match - case result: LevelError => Some(result) - case _ => None - inline def andAlso(op: Context ?=> TYPE)(using Context): TYPE = - if result.isOK then op else result - inline def orElse(op: Context ?=> TYPE)(using Context): TYPE = - if result.isOK then result - else - val alt = op - if alt.isOK then alt - else result - def show(using Context): String = result match - case () => "OK" - case cs: CaptureSet => cs.show - case LevelError(cs, ref) => i"($ref at wrong level for $cs)" + enum CompareResult extends Showable: + case OK + case Fail(cs: CaptureSet) + case LevelError(cs: CaptureSet, elem: CaptureRef) + + override def toText(printer: Printer): Text = + inContext(printer.printerContext): + this match + case OK => Str("OK") + case Fail(blocking: CaptureSet) => blocking.show + case LevelError(cs: CaptureSet, elem: CaptureRef) => + Str(i"($elem at wrong level for $cs in ${cs.owner})") + + /** 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 + 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 end CompareResult /** A VarState serves as a snapshot mechanism that can undo @@ -1054,7 +1049,7 @@ object CaptureSet: def levelErrors: Addenda = new Addenda: override def toAdd(using Context) = - for LevelError(cs, ref) <- ccState.levelError.toList yield + for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield ccState.levelError = None val levelStr = ref match case ref: (TermRef | ThisType) => i", defined at level ${ref.ccNestingLevel}" From 70ffa249ee6d744a79959e04b26a16fd2bdd5413 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 29 Sep 2023 11:54:21 +0200 Subject: [PATCH 028/117] Add takesCappedParam criterion to isLevelOwner (Configurable) --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 83 ++++++++++++------- .../dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 14 ++-- .../src/dotty/tools/dotc/cc/Synthetics.scala | 2 + 4 files changed, 67 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f10ccdac4113..9b7f216f546c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -28,6 +28,16 @@ private val adaptUnpickledFunctionTypes = false */ private val constrainRootsWhenMapping = true +/** If true, most vals can be level owners. If false, only vals defined by a + * closure as RHS can be level owners + */ +private val valsCanBeLevelOwners = false + +/** If true, only vals, defs, and classes with a universal capability in a parameter + * or self type are considered as level owners. + */ +private val levelOwnersNeedCapParam = false + def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) @@ -345,54 +355,71 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox - // Not yet needed - 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 _ => annot.tree.retainedElems.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 + def takesCappedParamIn(info: Type)(using Context): Boolean = + + def isOwnRoot(tp: Type): Boolean = + tp.isCapabilityClassRef + || tp.dealias.match + case tp: CaptureRef => + tp.isGenericRootCapability || tp.localRootOwner == sym + case _ => + false + + def hasUniversalCap(tp: Type): Boolean = tp.dealiasKeepAnnots match + case tp @ AnnotatedType(parent, annot) => + val found = annot match + case CaptureAnnotation(refs, _) => refs.elems.exists(isOwnRoot) + case _ => annot.tree.retainedElems.exists(tree => isOwnRoot(tree.tpe)) + found || hasUniversalCap(parent) + case tp: TypeRef => + tp.isRef(defn.Caps_Cap) || tp.isCapabilityClassRef + case tp: LazyRef => + hasUniversalCap(tp.ref) + case tp: TypeVar => + hasUniversalCap(tp.underlying) + case _ => + tp.isCapabilityClassRef + + info.dealias.stripPoly match case mt: MethodType => - mt.paramInfos.exists(search(false, _)) + (mt.paramInfos.exists(hasUniversalCap) || takesCappedParamIn(mt.resType)) + //.showing(i"takes capped param1 $sym: $mt = $result") + case AppliedType(fn, args) if defn.isFunctionClass(fn.typeSymbol) => + args.init.exists(hasUniversalCap) || takesCappedParamIn(args.last) + case defn.RefinedFunctionOf(rinfo) => + takesCappedParamIn(rinfo) + //.showing(i"takes capped param2 $sym: $rinfo = $result") case _ => false + end takesCappedParamIn // TODO Also include vals (right now they are manually entered in levelOwners by Setup) def isLevelOwner(using Context): Boolean = val symd = sym.denot def isCaseClassSynthetic = // TODO drop symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty + def classQualifies = + !levelOwnersNeedCapParam || takesCappedParamIn(symd.primaryConstructor.info) + def termQualifies = + !levelOwnersNeedCapParam || takesCappedParamIn(symd.info) def compute = if symd.isClass then - symd.is(CaptureChecked) || symd.isRoot + symd.is(CaptureChecked) && classQualifies || symd.isRoot else - symd.is(Method, butNot = Accessor) + (symd.is(Method, butNot = Accessor) + || valsCanBeLevelOwners && symd.isTerm && !symd.isOneOf(TermParamOrAccessor | Mutable)) && (!symd.owner.isClass || symd.owner.is(CaptureChecked) || Synthetics.needsTransform(symd) ) - //&& !Synthetics.isExcluded(sym) && !isCaseClassSynthetic && !symd.isConstructor && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) + && termQualifies + && { ccSetup.println(i"Level owner $sym"); true } + ccState.isLevelOwner.getOrElseUpdate(sym, compute) + end isLevelOwner /** The owner of the current level. Qualifying owners are * - methods other than constructors and anonymous functions diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index e5d505a6578b..ecf15804a340 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -741,7 +741,7 @@ class CheckCaptures extends Recheck, SymTransformer: //println(i"check conforms $actualBoxed <<< $expected1") var ok = isCompatible(actualBoxed, expected1) if !ok then stripTyped(tree) match - case tree: RefTree if tree.symbol.isLevelOwner => + case tree: RefTree if !ctx.owner.isContainedIn(tree.symbol.levelOwner) => // 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`. diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index d17447221b3f..54497a49c978 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -460,10 +460,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): tree.rhs match - case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) => - if !sym.is(Mutable) then ccState.isLevelOwner(sym) = true - // TODO Drop the possibleyTypedClosureDef condition anc replace by the - // condition that the val takes a cap parameter + case possiblyTypedClosureDef(ddef) + if !mentionsCap(rhsOfEtaExpansion(ddef)) + && !sym.is(Mutable) + && (!levelOwnersNeedCapParam + || ddef.symbol.takesCappedParamIn(ddef.symbol.info)) => + ccSetup.println(i"Level owner at setup $sym / ${ddef.symbol.info}") + ccState.isLevelOwner(sym) = true case _ => transformResultType(tpt, sym) ccSetup.println(i"mapped $tree = ${tpt.knownType}") @@ -772,7 +775,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if ref.captureSetOfInfo.elems.isEmpty then report.error(em"$ref cannot be tracked since its capture set is empty", pos) - check(parent.captureSet, parent) + 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 diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 7932a29fea1b..4b20533c074e 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -157,10 +157,12 @@ object Synthetics: case DefaultGetterName(nme.copy, n) => transformDefaultGetterCaptures(info, symd.owner, n) case nme.unapply => + if levelOwnersNeedCapParam then ccState.isLevelOwner(symd.symbol) = true transformUnapplyCaptures(info) case nme.apply | nme.copy => addCaptureDeps(info) case nme.andThen | nme.compose => + if levelOwnersNeedCapParam then ccState.isLevelOwner(symd.symbol) = true transformComposeCaptures(info, symd.owner) case nme.curried | nme.tupled => transformCurriedTupledCaptures(info, symd.owner) From 8a8765d3bd1349a4630667920eec4b4ad7a1802f Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 29 Sep 2023 19:53:06 +0200 Subject: [PATCH 029/117] Decorate inferred type aliases as inferred types --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 23 ++++++++++---------- tests/neg-custom-args/captures/refs.scala | 5 +++-- tests/neg-custom-args/captures/try.check | 14 +++++------- tests/neg-custom-args/captures/try.scala | 6 ++--- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 54497a49c978..d40ad44670eb 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -102,7 +102,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => mapOver(t) if variance > 0 then t1 - else decorate(t1, rootTarget = NoSymbol, addedSet = Function.const(CaptureSet.Fluid)) + 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. @@ -171,7 +171,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - private def mapInferred(rootTarget: Symbol)(using Context) = new TypeMap: + private def mapInferred(using Context) = new TypeMap: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters @@ -249,12 +249,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: box(this(tp1)) case _ => mapOver(tp) - addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner, rootTarget) + addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) end apply end mapInferred - private def transformInferredType(tp: Type, rootTarget: Symbol)(using Context): Type = - mapInferred(rootTarget)(tp) + private def transformInferredType(tp: Type)(using Context): Type = + mapInferred(tp) private def transformExplicitType(tp: Type, rootTarget: Symbol, tptToCheck: Option[Tree] = None)(using Context): Type = val expandAliases = new DeepTypeMap: @@ -372,7 +372,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val tp = if boxed then Box(tree.tpe) else tree.tpe tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tp, rootTarget) + then transformInferredType(tp) else transformExplicitType(tp, rootTarget, tptToCheck = Some(tree))) /** Substitute parameter symbols in `from` to paramRefs in corresponding @@ -585,7 +585,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: Bind => val sym = tree.symbol - updateInfo(sym, transformInferredType(sym.info, rootTarget = ctx.owner)) + updateInfo(sym, transformInferredType(sym.info)) case tree: TypeDef => tree.symbol match case cls: ClassSymbol => @@ -711,7 +711,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Add a capture set variable to `tp` if necessary, or maybe pull out * an embedded capture set variable from a part of `tp`. */ - def decorate(tp: Type, rootTarget: Symbol, 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 @@ -721,16 +721,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else fallback val dealiased = tp.dealiasKeepAnnots if dealiased ne tp then - val transformed = transformExplicitType(dealiased, rootTarget) - // !!! TODO: We should map roots to root vars here + 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, rootTarget: Symbol)(using Context): Type = - decorate(tp, rootTarget, + 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)) diff --git a/tests/neg-custom-args/captures/refs.scala b/tests/neg-custom-args/captures/refs.scala index b64b27ef4af0..b20e8029b65e 100644 --- a/tests/neg-custom-args/captures/refs.scala +++ b/tests/neg-custom-args/captures/refs.scala @@ -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/try.check b/tests/neg-custom-args/captures/try.check index feb16d8674ea..9bea04c527ed 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,10 +1,8 @@ --- [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]^{cap[test]} -24 | (x: CanThrow[Exception]) => x -25 | }{ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:24:32 ------------------------------------------ +24 | (x: CanThrow[Exception]) => x // error + | ^ + | Found: (x : CT[Exception]^{lcap}) + | Required: CT[Exception]^? | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- @@ -18,7 +16,7 @@ 49 | () => 50 | raise(new Exception)(using x) 51 | 22 -52 |} { // error +52 |} { // error | ^ | Found: () ->{x$0, lcap} Int | Required: () -> Int diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index fe58145bca54..71db5d8ed7b1 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (lcap: caps.Cap) ?-> CT[E]^{lcap} => R) catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { // error - (x: CanThrow[Exception]) => x + val a = handle[Exception, CanThrow[Exception]] { + (x: CanThrow[Exception]) => x // error }{ (ex: Exception) => ??? } @@ -49,6 +49,6 @@ val global: () -> Int = handle { () => raise(new Exception)(using x) 22 -} { // error +} { // error (ex: Exception) => () => 22 } From bbde15f0db87db8ad323b978bc5ee9ab8ee5a32f Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 14:59:38 +0200 Subject: [PATCH 030/117] Setup correct environment when completing definitions --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index ecf15804a340..107125d11f45 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -54,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 @@ -220,8 +228,9 @@ 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 @@ -615,7 +624,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) @@ -1014,10 +1024,32 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) + /** Check a ValDef or DefDef as an action performed in a completer. + */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + val saved = curEnv + try + // Setup environment to reflect the new owner. + val envForOwner = curEnv.outersIterator + .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) + .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.setupUnit(ctx.compilationUnit.tpdTree, recheckDef) + setup.setupUnit(ctx.compilationUnit.tpdTree, completeDef) if ctx.settings.YccPrintSetup.value then val echoHeader = "[[syntax tree at end of cc setup]]" From cbd0e0c45f407f7e2fce61d3a4679592dbb5a6cb Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:05:42 +0200 Subject: [PATCH 031/117] Treat classes with universal self types as level owners Rataional: subclasses might have universal parameters, which would make *them* level owners. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 9b7f216f546c..8433cd660703 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -300,6 +300,32 @@ extension (tp: Type) mapOver(t) tm(tp) + def hasUniversalRootOf(sym: Symbol)(using Context): Boolean = + + def isOwnRoot(tp: Type)(using Context): Boolean = + tp.isCapabilityClassRef + || tp.dealias.match + case tp: TermRef => + tp.isGenericRootCapability || tp.localRootOwner == sym + case _ => + false + + tp.dealiasKeepAnnots match + case tp @ AnnotatedType(parent, annot) => + val found = annot match + case CaptureAnnotation(refs, _) => refs.elems.exists(isOwnRoot(_)) + case _ => annot.tree.retainedElems.exists(tree => isOwnRoot(tree.tpe)) + found || parent.hasUniversalRootOf(sym) + case tp: TypeRef => + tp.isRef(defn.Caps_Cap) || tp.isCapabilityClassRef + case tp: LazyRef => + tp.ref.hasUniversalRootOf(sym) + case tp: TypeVar => + tp.underlying.hasUniversalRootOf(sym) + case _ => + tp.isCapabilityClassRef + end hasUniversalRootOf + extension (cls: Symbol) def pureBaseClass(using Context): Option[Symbol] = @@ -382,16 +408,15 @@ extension (sym: Symbol) info.dealias.stripPoly match case mt: MethodType => - (mt.paramInfos.exists(hasUniversalCap) || takesCappedParamIn(mt.resType)) + (mt.paramInfos.exists(_.hasUniversalRootOf(sym)) || takesCappedParamIn(mt.resType)) //.showing(i"takes capped param1 $sym: $mt = $result") case AppliedType(fn, args) if defn.isFunctionClass(fn.typeSymbol) => - args.init.exists(hasUniversalCap) || takesCappedParamIn(args.last) + args.init.exists(_.hasUniversalRootOf(sym)) || takesCappedParamIn(args.last) case defn.RefinedFunctionOf(rinfo) => takesCappedParamIn(rinfo) //.showing(i"takes capped param2 $sym: $rinfo = $result") case _ => false - end takesCappedParamIn // TODO Also include vals (right now they are manually entered in levelOwners by Setup) def isLevelOwner(using Context): Boolean = @@ -399,7 +424,9 @@ extension (sym: Symbol) def isCaseClassSynthetic = // TODO drop symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty def classQualifies = - !levelOwnersNeedCapParam || takesCappedParamIn(symd.primaryConstructor.info) + !levelOwnersNeedCapParam + || takesCappedParamIn(symd.primaryConstructor.info) + || symd.asClass.givenSelfType.hasUniversalRootOf(sym) def termQualifies = !levelOwnersNeedCapParam || takesCappedParamIn(symd.info) def compute = From f79e6ae0be28ee97d69437d634dd714cfee7e39f Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:13:13 +0200 Subject: [PATCH 032/117] Revamp capture roots and captureset vars - Drop usage of nesting level - Work directly with owner inclusion --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 57 ++----- .../src/dotty/tools/dotc/cc/CaptureRoot.scala | 141 ++++++++---------- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 78 +++++----- .../dotty/tools/dotc/cc/CheckCaptures.scala | 14 +- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 14 +- .../tools/dotc/printing/PlainPrinter.scala | 6 +- 7 files changed, 140 insertions(+), 172 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8433cd660703..b2df95c5ce76 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -91,23 +91,18 @@ trait FollowAliases extends TypeMap: if t2 ne t1 then return t2 mapOver(t) -class mapRoots(from: CaptureRoot, to: CaptureRoot)(using Context) extends BiTypeMap, FollowAliases: +class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends BiTypeMap, FollowAliases: thisMap => + val from = from0.followAlias + + //override val toString = i"mapRoots($from, $to)" + 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 t: CaptureRoot.Var if constrainRootsWhenMapping && t.unifiesWith(from) => + to case t @ Setup.Box(t1) => t.derivedBox(this(t1)) case _ => @@ -382,30 +377,6 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeUnbox def takesCappedParamIn(info: Type)(using Context): Boolean = - - def isOwnRoot(tp: Type): Boolean = - tp.isCapabilityClassRef - || tp.dealias.match - case tp: CaptureRef => - tp.isGenericRootCapability || tp.localRootOwner == sym - case _ => - false - - def hasUniversalCap(tp: Type): Boolean = tp.dealiasKeepAnnots match - case tp @ AnnotatedType(parent, annot) => - val found = annot match - case CaptureAnnotation(refs, _) => refs.elems.exists(isOwnRoot) - case _ => annot.tree.retainedElems.exists(tree => isOwnRoot(tree.tpe)) - found || hasUniversalCap(parent) - case tp: TypeRef => - tp.isRef(defn.Caps_Cap) || tp.isCapabilityClassRef - case tp: LazyRef => - hasUniversalCap(tp.ref) - case tp: TypeVar => - hasUniversalCap(tp.underlying) - case _ => - tp.isCapabilityClassRef - info.dealias.stripPoly match case mt: MethodType => (mt.paramInfos.exists(_.hasUniversalRootOf(sym)) || takesCappedParamIn(mt.resType)) @@ -517,17 +488,17 @@ extension (sym: Symbol) else newRoot ccState.localRoots.getOrElseUpdate(owner, lclRoot) - 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 + def maxNested(other: Symbol, pickFirstOnConflict: Boolean = false)(using Context): Symbol = + if !sym.exists || other.isContainedIn(sym) then other + else if !other.exists || sym.isContainedIn(other) then sym else - assert(sym == other, i"conflicting symbols at same nesting level: $sym, $other") + assert(pickFirstOnConflict, i"incomparable nesting: $sym and $other") sym - */ def minNested(other: Symbol)(using Context): Symbol = - if sym.ccNestingLevel > other.ccNestingLevel then other else sym + 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: TermRef | ThisType) /** The nesting level of this reference as defined by capture checking */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 0651d5699892..6bab3d331bb7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -14,98 +14,85 @@ type CaptureRoot = TermRef | CaptureRoot.Var object CaptureRoot: - case class Var(owner: Symbol, source: Symbol = NoSymbol)(using @constructorOnly ictx: Context) extends CaptureRef, Showable: + private var nextId = 0 - 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 + case class Var(owner: Symbol, source: Symbol)(using @constructorOnly ictx: Context) extends CaptureRef, Showable: - override def localRootOwner(using Context) = owner - override def isTrackableRef(using Context): Boolean = true - override def captureSetOfInfo(using Context) = CaptureSet.universal + val id = + nextId += 1 + nextId - def setAlias(target: CaptureRoot) = - alias = target + var innerLimit: Symbol = owner.levelOwner + var outerLimit: Symbol = defn.RootClass + var outerRoots: SimpleIdentitySet[Var] = SimpleIdentitySet.empty - def followAlias: CaptureRoot = alias match - case alias: Var if alias ne this => alias.followAlias - case _ => alias + override def isTrackableRef(using Context): Boolean = true + override def captureSetOfInfo(using Context) = CaptureSet.universal - def locallyConsistent = - lowerLevel <= upperLevel - && lowerRoots.forall(_.upperLevel <= upperLevel) - && upperRoots.forall(_.lowerLevel >= lowerLevel) + private var myAlias: CaptureRoot = this + def alias = myAlias + def alias_=(r: CaptureRoot)(using Context) = + //assert(id != 2, i"$this := $r") + alias match + case alias: TermRef => + val owner = alias.localRootOwner + assert( + owner.isContainedIn(outerLimit) && innerLimit.isContainedIn(owner), + i"illegal alias $owner for $this") + case _ => + myAlias = r 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 + extension (r: CaptureRoot) + + def followAlias(using Context): CaptureRoot = r match + case r: Var if r.alias ne r => r.alias.followAlias + case _ => r + + def unifiesWith(other: CaptureRoot)(using Context): Boolean = + r.encloses(other) && other.encloses(r) + + def encloses(other: CaptureRoot)(using Context): Boolean = + val (r1, r2) = (followAlias, other.followAlias) + (r1 eq r2) || (r1, r2).match + case (r1: TermRef, r2: TermRef) => + r2.localRootOwner.isContainedIn(r1.localRootOwner) + case (r1: TermRef, r2: Var) => + val r1Owner = r1.localRootOwner + if r2.outerLimit.isContainedIn(r1Owner) then true + else if !r2.innerLimit.isContainedIn(r1Owner) then false 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 + if r2.innerLimit == r1Owner then r2.alias = r1 + else r2.outerLimit = r1Owner + true + case (r1: Var, r2: TermRef) => + val r2Owner = r2.localRootOwner + if r2Owner.isContainedIn(r1.innerLimit) then true + else if !r2Owner.isContainedIn(r1.outerLimit) then false 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 + if r1.outerLimit == r2Owner then r1.alias = r2 + else r1.innerLimit = r2Owner + true + case (r1: Var, r2: Var) => + if r2.outerRoots.contains(r1) then true // no change + else if !r2.innerLimit.isContainedIn(r1.outerLimit) then false // no overlap + else if r1.outerRoots.contains(r2) then // unify + r1.alias = r2 + r2.outerLimit = r1.outerLimit.maxNested(r2.outerLimit) + r2.innerLimit = r1.innerLimit.minNested(r2.innerLimit) true else - c1.upperRoots -= c2 - false - end isEnclosingRoot + r2.outerRoots += r1 // set early to prevent infinite looping + if r1.outerRoots.forall(_.encloses(r2)) then true + else + r2.outerRoots -= r2 + false + end encloses + end CaptureRoot diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 6cb555ef3f88..1ff8204602aa 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -56,13 +56,14 @@ 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 optinal 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 def rootSet(using Context): CaptureSet = - owner.localRoot.termRef.singletonCaptureSet + assert(levelLimit.exists, this) + levelLimit.localRoot.termRef.singletonCaptureSet /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -143,15 +144,15 @@ sealed abstract class CaptureSet extends Showable: private def subsumes(y: CaptureRef) = (x eq y) || y.match - case y: TermRef => (y.prefix eq x) || x.isRootIncluding(y) - case y: CaptureRoot.Var => x.isRootIncluding(y) + case y: TermRef => (y.prefix eq x) || y.isLocalRootCapability && x.isSuperRootOf(y) + case y: CaptureRoot.Var => x.isSuperRootOf(y) case _ => false || (x.isGenericRootCapability || y.isRootCapability && x.isRootCapability) && ctx.property(LooseRootChecking).isDefined - private def isRootIncluding(y: CaptureRoot) = - x.isLocalRootCapability && y.isLocalRootCapability - && CaptureRoot.isEnclosingRoot(y, x.asInstanceOf[CaptureRoot]) + private def isSuperRootOf(y: CaptureRoot) = x match + case x: CaptureRoot if x.isLocalRootCapability => y.encloses(x) + case _ => false end extension /** {x} <:< this where <:< is subcapturing, but treating all variables @@ -231,7 +232,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, pickFirstOnConflict = true), + this.elems ++ that.elems) .addAsDependentTo(this).addAsDependentTo(that) /** The smallest superset (via <:<) of this capture set that also contains `ref`. @@ -406,7 +409,7 @@ object CaptureSet: def withDescription(description: String): Const = Const(elems, description) - def owner = NoSymbol + def levelLimit = NoSymbol override def toString = elems.toString end Const @@ -433,16 +436,14 @@ object CaptureSet: varId += 1 varId - override val owner = directOwner.levelOwner + //assert(id != 95) + + 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 @@ -503,11 +504,18 @@ object CaptureSet: r.andAlso(dep.tryInclude(elem, this)) } - private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match - case elem: TermRef if elem.symbol.isLevelOwner => elem.ccNestingLevel - 1 <= ownLevel - case elem: (TermRef | ThisType) => elem.ccNestingLevel <= ownLevel - case elem: CaptureRoot.Var => CaptureRoot.isEnclosingRoot(elem, owner.localRoot.termRef) - case _ => true + private def levelOK(elem: CaptureRef)(using Context): Boolean = + !levelLimit.exists + || elem.match + case elem: TermRef => + var sym = elem.symbol + if sym.isLevelOwner then sym = sym.owner + levelLimit.isContainedIn(sym.levelOwner) + case elem: ThisType => + levelLimit.isContainedIn(elem.cls.levelOwner) + case elem: CaptureRoot.Var => + elem.encloses(levelLimit.localRoot.termRef) + case _ => true def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = if (cs eq this) || cs.isUniversal || isConst then @@ -578,11 +586,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 @@ -594,8 +602,8 @@ 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 @@ -635,7 +643,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] @@ -701,7 +709,7 @@ 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 = if origin eq source then @@ -731,7 +739,7 @@ object CaptureSet: /** A variable with elements given at any time as { x <- source.elems | p(x) } */ class Filtered private[CaptureSet] (val source: Var, p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) - extends DerivedVar(source.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) @@ -762,7 +770,7 @@ 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 @@ -851,7 +859,7 @@ object CaptureSet: case OK => Str("OK") case Fail(blocking: CaptureSet) => blocking.show case LevelError(cs: CaptureSet, elem: CaptureRef) => - Str(i"($elem at wrong level for $cs in ${cs.owner})") + Str(i"($elem at wrong level for $cs in ${cs.levelLimit})") /** The result is OK */ def isOK: Boolean = this == OK @@ -1052,11 +1060,11 @@ object CaptureSet: for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield ccState.levelError = None val levelStr = ref match - case ref: (TermRef | ThisType) => i", defined at level ${ref.ccNestingLevel}" + case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}" case _ => "" i""" | |Note that reference ${ref}$levelStr - |cannot be included in outer capture set $cs, defined at level ${cs.owner.nestingLevel} in ${cs.owner}""" + |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/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 107125d11f45..a1632fa9d5f3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -182,8 +182,8 @@ class CheckCaptures extends Recheck, SymTransformer: ref match case ref: CaptureRoot.Var => ref.followAlias match - case rv: CaptureRoot.Var if rv.upperBound == ctx.owner.levelOwner => - rv.setAlias(ctx.owner.localRoot.termRef) + case rv: CaptureRoot.Var if rv.innerLimit == ctx.owner.levelOwner => + rv.alias = ctx.owner.localRoot.termRef case _ => case _ => traverse(parent) @@ -249,7 +249,7 @@ class CheckCaptures extends Recheck, SymTransformer: def capturedVars(sym: Symbol)(using Context) = myCapturedVars.getOrElseUpdate(sym, if sym.ownersIterator.exists(_.isTerm) - then CaptureSet.Var(sym.skipConstructor.owner) + then CaptureSet.Var(sym.owner) else CaptureSet.empty) /** For all nested environments up to `limit` or a closed environment perform `op`, @@ -376,7 +376,7 @@ class CheckCaptures extends Recheck, SymTransformer: || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto if sym.skipConstructor.isLevelOwner && canInstantiate then val tpw = tp.widen - val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner, sym))(tpw) + val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) .showing(i"INST $sym: $tp, ${sym.localRoot} = $result", ccSetup) if tpw eq tp1 then tp else tp1 else @@ -406,7 +406,7 @@ class CheckCaptures extends Recheck, SymTransformer: if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.capturing(CaptureSet(CaptureRoot.Var(ctx.owner)))) + val argType0 = recheck(arg, pt.capturing(CaptureSet(CaptureRoot.Var(ctx.owner, meth)))) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -756,14 +756,14 @@ class CheckCaptures extends Recheck, SymTransformer: // 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 actualWide = actual.widen - val actualInst = mapRoots(tree.symbol.localRoot.termRef, CaptureRoot.Var(ctx.owner.levelOwner))(actualWide) + val actualInst = mapRoots(tree.symbol.localRoot.termRef, CaptureRoot.Var(ctx.owner, tree.symbol))(actualWide) capt.println(i"fallBack from $actualWide to $actualInst to match $expected1") ok = (actualInst ne actualWide) && isCompatible(adaptBoxed(actualInst, expected1, tree.srcPos), expected1) case _ => if !ok then capt.println(i"conforms failed for ${tree}: $actual vs $expected") - err.typeMismatch(tree.withType(actualBoxed), expected, addenda ++ CaptureSet.levelErrors) + err.typeMismatch(tree.withType(actualBoxed), expected1, addenda ++ CaptureSet.levelErrors) end checkConformsExpr /** Turn `expected` into a dependent function when `actual` is dependent. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 62559a037703..7c3f098d8011 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2110,7 +2110,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // refining capture set. tp2.refinedInfo match case rinfo2 @ CapturingType(_, refs: CaptureSet.RefiningVar) => - info1 = mapRoots(refs.getter.owner.localRoot.termRef, refs.owner.localRoot.termRef)(info1) + info1 = mapRoots(refs.getter.owner.localRoot.termRef, refs.levelLimit.localRoot.termRef)(info1) case _ => isSubInfo(info1, info2, m.symbol.info.orElse(info1)) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4fb2e11d5b6d..d1f5f13bf40e 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, RetainingType, ccNestingLevel} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, ccNestingLevel, CaptureRoot} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -2186,12 +2186,10 @@ object Types { /** 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 tp: CaptureRoot.Var => true + case _ => false /** Is this reference the a (local or generic) root capability? */ def isRootCapability(using Context): Boolean = @@ -2932,7 +2930,7 @@ object Types { override def isGenericRootCapability(using Context): Boolean = name == nme.CAPTURE_ROOT && symbol == defn.captureRoot - override def localRootOwner(using Context): Symbol = + def localRootOwner(using Context): Symbol = val owner = symbol.maybeOwner def normOwner = if owner.isLocalDummy then owner.owner else owner if name == nme.LOCAL_CAPTURE_ROOT then normOwner diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 1ed02e8b6e7d..9fa31f4a8ab6 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -439,7 +439,11 @@ class PlainPrinter(_ctx: Context) extends Printer { (toTextRef(sym.termRef) ~ Str(s"/${sym.ccNestingLevel}").provided(showNestingLevel) ).provided(sym.exists) - "'cap[" ~ boundText(tp.lowerBound) ~ ".." ~ boundText(tp.upperBound) ~ "]" + "'cap[" + ~ toTextRef(tp.outerLimit.termRef).provided(!tp.outerLimit.isRoot) + ~ ".." + ~ toTextRef(tp.innerLimit.termRef) + ~ "]" ~ ("(from instantiating " ~ nameString(tp.source) ~ ")").provided(tp.source.exists) } } From d38c7bd6ba372225ec547d222420b47717394c16 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:20:38 +0200 Subject: [PATCH 033/117] Make RefiningVars level polymorphic --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index d40ad44670eb..0ff8ed18df8d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -187,7 +187,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if atPhase(thisPhase.next)(getter.termRef.isTracked) then val getterType = tp.memberInfo(getter).strippedDealias RefinedType(core, getter.name, - CapturingType(getterType, CaptureSet.RefiningVar(ctx.owner, getter))) + CapturingType(getterType, CaptureSet.RefiningVar(NoSymbol, getter))) .showing(i"add capture refinement $tp --> $result", ccSetup) else core From 8394962363fad6e7036665f4ab416c76c694f0f3 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:21:35 +0200 Subject: [PATCH 034/117] Enable takesCappedParam criterion for level ownership --- compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala | 1 + compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala b/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala index 2e9f7ecfccd2..b79bd2a9ccaa 100644 --- a/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala +++ b/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala @@ -33,6 +33,7 @@ class AddTryOwners extends Phase, IdentityDenotTransformer: case tree @ Try(expr, cases, finalizer) if Feature.enabled(Feature.saferExceptions) => val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType)) ccState.tryBlockOwner(tree) = tryOwner + ccState.isLevelOwner(tryOwner) = true expr.changeOwnerAfter(ctx.owner, tryOwner, thisPhase) inContext(ctx.withOwner(tryOwner)): traverse(expr) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b2df95c5ce76..10cd9de807df 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -31,12 +31,12 @@ private val constrainRootsWhenMapping = true /** If true, most vals can be level owners. If false, only vals defined by a * closure as RHS can be level owners */ -private val valsCanBeLevelOwners = false +private val valsCanBeLevelOwners = true /** If true, only vals, defs, and classes with a universal capability in a parameter * or self type are considered as level owners. */ -private val levelOwnersNeedCapParam = false +private val levelOwnersNeedCapParam = true def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) From fdabd8a6140f32fdc693ae03d01417b51d752099 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:23:48 +0200 Subject: [PATCH 035/117] Use LooseCaptureRoot checking for checking bounds of applied types Temporary measure until we have class capture roots worked out --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a1632fa9d5f3..2988623f5320 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -376,7 +376,7 @@ class CheckCaptures extends Recheck, SymTransformer: || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto if sym.skipConstructor.isLevelOwner && canInstantiate then val tpw = tp.widen - val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) + var tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) .showing(i"INST $sym: $tp, ${sym.localRoot} = $result", ccSetup) if tpw eq tp1 then tp else tp1 else @@ -760,6 +760,8 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"fallBack from $actualWide to $actualInst to match $expected1") ok = (actualInst ne actualWide) && isCompatible(adaptBoxed(actualInst, expected1, tree.srcPos), expected1) + // Useful for debugging: + // if !ok then err.typeMismatch(tree.withType(actualInst), expected1, addenda ++ CaptureSet.levelErrors) case _ => if !ok then capt.println(i"conforms failed for ${tree}: $actual vs $expected") @@ -1231,6 +1233,6 @@ class CheckCaptures extends Recheck, SymTransformer: case tree: New => case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) case _ => traverseChildren(t) - checkApplied.traverse(unit) + checkApplied.traverse(unit)(using ctx.withProperty(LooseRootChecking, Some(()))) end CaptureChecker end CheckCaptures From 6da11f7a7ea541c4452cdd6b234eee1d52090500 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:24:26 +0200 Subject: [PATCH 036/117] Update tests can check files --- tests/neg-custom-args/captures/byname.check | 2 +- tests/neg-custom-args/captures/capt1.check | 4 ++-- tests/neg-custom-args/captures/i15772.check | 12 ++++++------ tests/neg-custom-args/captures/lazylist.check | 2 +- tests/neg-custom-args/captures/lazyref.check | 12 ++++++------ .../captures/leaked-curried.check | 2 +- tests/neg-custom-args/captures/levels.check | 8 ++++---- tests/neg-custom-args/captures/levels.scala | 3 +-- tests/neg-custom-args/captures/localcaps.scala | 4 +++- tests/neg-custom-args/captures/real-try.check | 4 ++-- .../captures/simple-escapes.check | 2 +- .../captures/usingLogFile-alt.check | 4 ++-- .../captures/usingLogFile.check | 4 ++-- tests/neg-custom-args/captures/vars.check | 18 +++++++++--------- tests/neg-custom-args/captures/vars.scala | 7 +++---- tests/pos-custom-args/captures/capt2.scala | 2 +- tests/pos-custom-args/captures/lists.scala | 2 +- tests/pos-custom-args/captures/pairs.scala | 4 ++-- tests/pos-special/stdlib/Test2.scala | 2 +- .../captures/colltest5/Test_2.scala | 2 +- 20 files changed, 50 insertions(+), 50 deletions(-) diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 67f42a4acc69..781b26b6944d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -2,7 +2,7 @@ 10 | h(f2()) // error | ^^^^ | Found: (x$0: Int) ->{cap1} Int - | Required: Int ->{cap2} Int + | Required: (x$0: Int) ->{cap2} Int | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 93b263aacba6..8e56f9515dbf 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -36,5 +36,5 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:32:30 ------------------------------------------------------------- 32 | val z2 = h[() -> Cap](() => x) // error | ^ - | (x : C^{cap[foo]}) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^{cap[foo]} + | (x : C^{cap[]}) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^{cap[]} diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index c62dae2f7627..d66fe7a4d063 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], c} Unit) ->{c} Unit - | Required: (C^{cap[main1]} ->{cap[main1]} Unit) -> Unit + | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[main1..boxed1](from instantiating box1), c} Unit) ->{c} Unit + | Required: (C^{cap[boxed1]} ->{cap[boxed1]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ @@ -18,15 +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], c} Unit) ->{c} Unit - | Required: (C^{cap[main2]} ->{cap[main2]} Unit) -> Unit + | Found: (C{val arg: C^{cap[C]}}^{c} ->{'cap[main2..boxed2](from instantiating box2), c} Unit) ->{c} Unit + | Required: (C^{cap[boxed2]} ->{cap[boxed2]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - | Found: (C{val arg: C^{cap[C]}}^{'cap[..main3](from instantiating c)} ->{cap[main3]} Unit) ->? Unit - | Required: (C ->{cap[main3]} Unit) ->{cap[main3]} Unit + | Found: (C{val arg: C^{cap[C]}}^{cap[main3]} ->{cap[main3]} Unit) ->{cap[main3]} Unit + | Required: (C ->{cap[boxed2]} Unit) ->{cap[boxed2]} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index ba5080d808f8..f8f1c1df229f 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -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]^{cap[]} has incompatible type | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index a3cf34067b30..a9806bc5494d 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -8,21 +8,21 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:21:35 -------------------------------------- 21 | val ref2c: LazyRef[Int]^{cap2} = ref2 // error | ^^^^ - | Found: (ref2 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, ref1}) - | Required: LazyRef[Int]^{cap2} + | Found: (ref2 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, ref1}) + | Required: LazyRef[Int]^{cap2} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:23:35 -------------------------------------- 23 | val ref3c: LazyRef[Int]^{ref1} = ref3 // error | ^^^^ - | Found: (ref3 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, ref1}) - | Required: LazyRef[Int]^{ref1} + | Found: (ref3 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, ref1}) + | Required: LazyRef[Int]^{ref1} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:25:35 -------------------------------------- 25 | val ref4c: LazyRef[Int]^{cap1} = ref4 // error | ^^^^ - | Found: (ref4 : LazyRef[Int]{val elem: () ->{cap[test]} Int}^{cap2, cap1}) - | Required: LazyRef[Int]^{cap1} + | Found: (ref4 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, cap1}) + | Required: LazyRef[Int]^{cap1} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index a3665b50b6f6..a13c15c7c7fd 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,4 +1,4 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:13:20 ---------------------------------------------------- 13 | () => () => io // error | ^^ - |(io : Cap^{cap[main]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure + |(io : Cap^{cap[]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 2a72dbd354d1..6ace4e09dfcc 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,10 +1,10 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:15:11 --------------------------------------- -15 | r.setV(g) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:14:11 --------------------------------------- +14 | r.setV(g) // error | ^ | Found: box (x: String) ->{cap3} String | Required: box (x$0: String) ->? String | - | Note that reference (cap3 : CC^{cap[scope]}), defined at level 2 - | cannot be included in outer capture set ?, defined at level 1 in method test + | Note that reference (cap3 : CC^{cap[scope]}), defined in method scope + | cannot be included in outer capture set ? which is associated with method test | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 35fb2d490398..47ad8e3c50db 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -9,8 +9,7 @@ def test(cap1: CC^) = 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 () diff --git a/tests/neg-custom-args/captures/localcaps.scala b/tests/neg-custom-args/captures/localcaps.scala index 50cbe8e0f8f9..5e69a8a48f1f 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[z]}) => x // ok private val z2 = identity((x: Int) => (c: C^{cap[z2]}) => x) // error diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index f969404cde73..12a4cbd62c63 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -24,5 +24,5 @@ |(canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}) cannot be referenced here; it is not included in the allowed capture set ? |of an enclosing function literal with expected type box () ->? Unit | - |Note that reference (canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}), defined at level 2 - |cannot be included in outer capture set ?, defined at level 1 in method test + |Note that reference (canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}), defined in method + |cannot be included in outer capture set ? which is associated with package diff --git a/tests/neg-custom-args/captures/simple-escapes.check b/tests/neg-custom-args/captures/simple-escapes.check index 611d4f0f0dc3..3171f6f72612 100644 --- a/tests/neg-custom-args/captures/simple-escapes.check +++ b/tests/neg-custom-args/captures/simple-escapes.check @@ -2,7 +2,7 @@ 16 | foo = f // error | ^ | Found: box FileOutputStream^{f} - | Required: box FileOutputStream^{cap[Test1]} + | Required: box FileOutputStream^{cap[]} | | 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/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 eb24f76d748c..433172663640 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -31,5 +31,5 @@ | ^^^^^^^^^ | reference (_$1 : java.io.OutputStream^{local}) is not included in the allowed capture set {x$0, local} | - | Note that reference (_$1 : java.io.OutputStream^{local}), defined at level 2 - | cannot be included in outer capture set {x$0, local}, defined at level 1 in method test + | Note that reference (_$1 : java.io.OutputStream^{local}), defined in method $anonfun + | cannot be included in outer capture set {x$0, local} which is associated with package diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 98991fdf9857..7447d65ef026 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -5,26 +5,26 @@ | 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^{cap[scope]}) 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 +-- [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 String ->{cap[test]} String + | Required: box (x$0: String) ->{cap[test]} String | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:26:13 ----------------------------------------- -26 | b = List(g) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:13 ----------------------------------------- +25 | b = List(g) // error | ^ | Found: Seq[(x: String) ->{cap3} String] | Required: Seq[box (x$0: String) ->{cap3} 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 { root => cap3 => // error | ^^^^^ | escaping local reference root.type diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 81a68ce6a005..8819a73d5bc8 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -16,8 +16,7 @@ def test(cap1: Cap, cap2: Cap) = a("") // was error, now ok b.head // was error, now ok - 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,8 +26,8 @@ def test(cap1: Cap, cap2: Cap) = val gc = g g - val s = scope - val sc: String => String = scope + 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()) diff --git a/tests/pos-custom-args/captures/capt2.scala b/tests/pos-custom-args/captures/capt2.scala index 1df2ea69b119..45381bf602ed 100644 --- a/tests/pos-custom-args/captures/capt2.scala +++ b/tests/pos-custom-args/captures/capt2.scala @@ -13,7 +13,7 @@ def test2() = z: (() -> Unit) @retains(x) def z2: (() -> Unit) @retains(y) = y z2: (() -> Unit) @retains(y) - val p: () ->{cap[test2]} String = () => "abc" + val p: () => String = () => "abc" val q: C^{p} = ??? val _ = p: (() ->{p} String) 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/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index e722e2fa74bd..93bf919a4750 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -13,9 +13,9 @@ 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^{cap[test]} ->{c} Unit = x1 val y1 = p.snd - val y1c: Cap ->{d} Unit = y1 + val y1c: Cap^{cap[test]} ->{d} Unit = y1 object Monomorphic: diff --git a/tests/pos-special/stdlib/Test2.scala b/tests/pos-special/stdlib/Test2.scala index 26b2faee1165..a59da522b183 100644 --- a/tests/pos-special/stdlib/Test2.scala +++ b/tests/pos-special/stdlib/Test2.scala @@ -8,7 +8,7 @@ object Test { def seqOps(xs: Seq[Int]) = { // try with Seq[Int]^{cap} val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int ->{cap[seqOps]} Boolean = _ % 2 == 0 + val isEven: Int => 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/run-custom-args/captures/colltest5/Test_2.scala b/tests/run-custom-args/captures/colltest5/Test_2.scala index 64e8d4a88cf3..5ea259b965ed 100644 --- a/tests/run-custom-args/captures/colltest5/Test_2.scala +++ b/tests/run-custom-args/captures/colltest5/Test_2.scala @@ -8,7 +8,7 @@ object Test { def seqOps(xs: Seq[Int]) = { // try with Seq[Int]^{cap} val strPlusInt: (String, Int) => String = _ + _ val intPlusStr: (Int, String) => String = _ + _ - val isEven: Int ->{cap[seqOps]} Boolean = _ % 2 == 0 + val isEven: Int => Boolean = _ % 2 == 0 val isNonNeg: Int => Boolean = _ > 0 val flips: Int => List[Int] = x => Cons(x, Cons(-x, Nil)) val x1 = xs.foldLeft("")(strPlusInt) From 568867a98c25c3e41ec69f709b9895e862a6a97a Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 15:31:56 +0200 Subject: [PATCH 037/117] Revert "Use LooseCaptureRoot checking for checking bounds of applied types" No tests fail if LooseCaptureRoot is disabled again. This reverts commit b337b08a14ccb2c3a76651da0cd2aec60e8d1595. --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 2988623f5320..a1632fa9d5f3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -376,7 +376,7 @@ class CheckCaptures extends Recheck, SymTransformer: || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto if sym.skipConstructor.isLevelOwner && canInstantiate then val tpw = tp.widen - var tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) + val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) .showing(i"INST $sym: $tp, ${sym.localRoot} = $result", ccSetup) if tpw eq tp1 then tp else tp1 else @@ -760,8 +760,6 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"fallBack from $actualWide to $actualInst to match $expected1") ok = (actualInst ne actualWide) && isCompatible(adaptBoxed(actualInst, expected1, tree.srcPos), expected1) - // Useful for debugging: - // if !ok then err.typeMismatch(tree.withType(actualInst), expected1, addenda ++ CaptureSet.levelErrors) case _ => if !ok then capt.println(i"conforms failed for ${tree}: $actual vs $expected") @@ -1233,6 +1231,6 @@ class CheckCaptures extends Recheck, SymTransformer: case tree: New => case tree: TypeTree => checkAppliedTypesIn(tree.withKnownType) case _ => traverseChildren(t) - checkApplied.traverse(unit)(using ctx.withProperty(LooseRootChecking, Some(()))) + checkApplied.traverse(unit) end CaptureChecker end CheckCaptures From 4e67d95d278244f290b6811939d8ba30627f1f98 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 18:13:38 +0200 Subject: [PATCH 038/117] Simplification: drop ccNestingLevel --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 29 +------------------ .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/printing/PlainPrinter.scala | 19 ++++-------- .../tools/dotc/printing/RefinedPrinter.scala | 7 ++--- 4 files changed, 10 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 10cd9de807df..edbf1c971aa5 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -57,9 +57,6 @@ class CCState: /** Cache for level ownership */ val isLevelOwner: mutable.HashMap[Symbol, Boolean] = new mutable.HashMap - /** Associates certain symbols (the nesting level owners) with their ccNestingLevel */ - val nestingLevels: mutable.HashMap[Symbol, Int] = new mutable.HashMap - /** Associates nesting level owners with the local roots valid in their scopes. */ val localRoots: mutable.HashMap[Symbol, Symbol] = new mutable.HashMap @@ -451,24 +448,6 @@ extension (sym: Symbol) recur(sym, NoSymbol) .showing(i"find outer $sym [ $name ] = $result", capt) - /** 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.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev - then Some(ccNestingLevel) - else None - /** The parameter with type caps.Cap in the leading term parameter section, * or NoSymbol, if none exists. */ @@ -482,7 +461,7 @@ extension (sym: 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) + nme.LOCAL_CAPTURE_ROOT, Synthetic, defn.Caps_Cap.typeRef) def lclRoot = if owner.isTerm then owner.definedLocalRoot.orElse(newRoot) else newRoot @@ -500,12 +479,6 @@ extension (sym: Symbol) else if !sym.exists || sym.isContainedIn(other) then other else sym.owner.minNested(other.owner) -extension (tp: TermRef | ThisType) - /** The nesting level of this reference as defined by capture checking */ - def ccNestingLevel(using Context): Int = tp match - case tp: TermRef => tp.symbol.ccNestingLevel - case tp: ThisType => tp.cls.ccNestingLevel - extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d1f5f13bf40e..44e62d85f0e5 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, RetainingType, ccNestingLevel, CaptureRoot} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, CaptureRoot} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 9fa31f4a8ab6..02fc1e44c12a 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,7 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.{Config, Feature} -import cc.{CapturingType, RetainingType, CaptureSet, CaptureRoot, isBoxed, ccNestingLevel, levelOwner, retainedElems} +import cc.{CapturingType, RetainingType, CaptureSet, CaptureRoot, isBoxed, levelOwner, retainedElems} class PlainPrinter(_ctx: Context) extends Printer { @@ -374,10 +374,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 "") + ">" @@ -408,11 +405,9 @@ 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) + if ctx.owner.levelOwner == tp.localRootOwner && !printDebug && shortenCap + then Str("cap") + else Str(s"cap[${tp.localRootOwner.name}]") else toTextPrefixOf(tp) ~ selectionString(tp) case tp: ThisType => nameString(tp.cls) + ".this" @@ -436,9 +431,7 @@ class PlainPrinter(_ctx: Context) extends Printer { 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) + toTextRef(sym.termRef).provided(sym.exists) "'cap[" ~ toTextRef(tp.outerLimit.termRef).provided(!tp.outerLimit.isRoot) ~ ".." 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}" From b8b94d3a051726f6c0ce9109358b8ee3d0364862 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 1 Oct 2023 18:23:30 +0200 Subject: [PATCH 039/117] Add @sharable annotation to global counter --- compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 6bab3d331bb7..6913740307ae 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -9,12 +9,13 @@ import printing.Showable import util.SimpleIdentitySet import Decorators.i import scala.annotation.constructorOnly +import scala.annotation.internal.sharable type CaptureRoot = TermRef | CaptureRoot.Var object CaptureRoot: - private var nextId = 0 + @sharable private var nextId = 0 case class Var(owner: Symbol, source: Symbol)(using @constructorOnly ictx: Context) extends CaptureRef, Showable: From 7b04b704173a5990c043cd6b3de0069e58291bbd Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 3 Oct 2023 18:14:07 +0200 Subject: [PATCH 040/117] Handle outer class roots when instantiating class members The problem arises if we have a class like the one in pos-custom-args/captures/refs.scala: ```scala class MonoRef(init: Proc): type MonoProc = Proc var x: MonoProc = init def getX: MonoProc = x def setX(x: MonoProc): Unit = this.x = x ``` The type of `getX` and `setX` refer to the local root capability of class `MonoRef`. When we call `m.getX` or `m.setX` in `m: MonoRef`, these occurrences have to be adapted to capture roots in the scope of the selection. We determine these roots by inspecting the capture set of `m` and picking a root that corresponds to it. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 8 +- .../src/dotty/tools/dotc/cc/CaptureRoot.scala | 97 ++++++++++++++++++- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 18 +++- .../dotty/tools/dotc/cc/CheckCaptures.scala | 24 +++-- tests/neg-custom-args/captures/pairs.check | 28 ++++++ tests/neg-custom-args/captures/pairs.scala | 37 +++++++ .../captures/outer-roots.scala | 26 +++++ tests/pos-custom-args/captures/pairs.scala | 9 +- tests/pos-custom-args/captures/refs.scala | 29 ++++++ 9 files changed, 256 insertions(+), 20 deletions(-) create mode 100644 tests/neg-custom-args/captures/pairs.check create mode 100644 tests/neg-custom-args/captures/pairs.scala create mode 100644 tests/pos-custom-args/captures/outer-roots.scala create mode 100644 tests/pos-custom-args/captures/refs.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index edbf1c971aa5..986edf4d0a59 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -89,8 +89,6 @@ trait FollowAliases extends TypeMap: mapOver(t) class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends BiTypeMap, FollowAliases: - thisMap => - val from = from0.followAlias //override val toString = i"mapRoots($from, $to)" @@ -467,12 +465,10 @@ extension (sym: Symbol) else newRoot ccState.localRoots.getOrElseUpdate(owner, lclRoot) - def maxNested(other: Symbol, pickFirstOnConflict: Boolean = false)(using Context): Symbol = + def maxNested(other: Symbol, onConflict: (Symbol, Symbol) => Context ?=> Symbol)(using Context): Symbol = if !sym.exists || other.isContainedIn(sym) then other else if !other.exists || sym.isContainedIn(other) then sym - else - assert(pickFirstOnConflict, i"incomparable nesting: $sym and $other") - sym + else onConflict(sym, other) def minNested(other: Symbol)(using Context): Symbol = if !other.exists || other.isContainedIn(sym) then sym diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 6913740307ae..1295695cf52b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -4,10 +4,12 @@ package cc import core.* import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* +import config.Printers.capt import Hashable.Binders import printing.Showable import util.SimpleIdentitySet -import Decorators.i +import Decorators.* +import StdNames.nme import scala.annotation.constructorOnly import scala.annotation.internal.sharable @@ -43,11 +45,31 @@ object CaptureRoot: case _ => myAlias = r + /** A fresh var with the same limits and outerRoots as this one */ + def fresh(using Context): Var = + val r = Var(owner, NoSymbol) + r.innerLimit = innerLimit + r.outerLimit = outerLimit + r.outerRoots = outerRoots + r + + /** A fresh var that is enclosed by all roots in `rs`. + * @throws A NoCommonRoot exception if this is not possible + * since root scopes dont' overlap. + */ + def freshEnclosedBy(rs: CaptureRoot*)(using Context): CaptureRoot = + val r = fresh + if rs.forall(_.encloses(r)) then r else throw NoCommonRoot(rs*) + def computeHash(bs: Binders): Int = hash def hash: Int = System.identityHashCode(this) def underlying(using Context): Type = defn.Caps_Cap.typeRef end Var + class NoCommonRoot(rs: CaptureRoot*)(using Context) extends Exception( + i"No common capture root nested in ${rs.mkString(" and ")}" + ) + extension (r: CaptureRoot) def followAlias(using Context): CaptureRoot = r match @@ -83,7 +105,9 @@ object CaptureRoot: else if !r2.innerLimit.isContainedIn(r1.outerLimit) then false // no overlap else if r1.outerRoots.contains(r2) then // unify r1.alias = r2 - r2.outerLimit = r1.outerLimit.maxNested(r2.outerLimit) + r2.outerLimit = + r1.outerLimit.maxNested(r2.outerLimit, + onConflict = (_, _) => throw NoCommonRoot(r1, r2)) r2.innerLimit = r1.innerLimit.minNested(r2.innerLimit) true else @@ -93,6 +117,75 @@ object CaptureRoot: r2.outerRoots -= r2 false end encloses + end extension + + /** The capture root enclosed by `root1` and `root2`. + * If one of these is a Var, create a fresh Var with the appropriate constraints. + * If the scopes of `root1` and `root2` don't overlap, thow a `NoCommonRoot` exception. + */ + def lub(root1: CaptureRoot, root2: CaptureRoot)(using Context): CaptureRoot = + val (r1, r2) = (root1.followAlias, root2.followAlias) + if r1 eq r2 then r1 + else (r1, r2) match + case (r1: TermRef, r2: TermRef) => + r1.localRootOwner.maxNested(r2.localRootOwner, + onConflict = (_, _) => throw NoCommonRoot(r1, r2) + ).termRef + case (r1: TermRef, r2: Var) => + r2.freshEnclosedBy(r1, r2) + case (r1: Var, r2) => + r1.freshEnclosedBy(r1, r2) + + /** A map that instantiates all outer class roots in the info of `sym` + * according to prefix `pre`. This is called for adapting the info of + * a selection `pre.sym`. The logic of the function is modeled after + * AsSeenFrom. But where AsSeenFrom maps a `this` of class `C` to a corresponding + * prefix, the present method maps a local root corresponding to a class to + * the root implied by the capture set of the corresponding prefix. + * @param sym the class member symbol whose info is mapped + * @param pre the prefix from which `sym` is selected + * @param deafilt the capture root to use if the capture set of the corresponding + * prfefix is empty. + */ + class instantiateOuterClassRoots(sym: Symbol, pre: Type, default: CaptureRoot)(using Context) extends ApproximatingTypeMap: + val cls = sym.owner.asClass + + def apply(tp: Type): Type = + + /** Analogous to `toPrefix` in `AssSeenFromMap`, but result prefix gets + * further mapped to a capture root via `impliedRoot`. + */ + def mapCaptureRoot(pre: Type, cls: Symbol, thiscls: ClassSymbol, fallBack: CaptureRoot): CaptureRoot = + if (pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass) then + fallBack + else pre match + case pre: SuperType => + mapCaptureRoot(pre.thistpe, cls, thiscls, fallBack) + case _ => + if thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists then + pre.captureSet.impliedRoot(default) + else if pre.termSymbol.is(Package) && !thiscls.is(Package) then + mapCaptureRoot(pre.select(nme.PACKAGE), cls, thiscls, fallBack) + else + mapCaptureRoot(pre.baseType(cls).normalizedPrefix, cls.owner, thiscls, fallBack) + + def instRoot(elem: CaptureRef): CaptureRef = elem match + case elem: TermRef + if elem.name == nme.LOCAL_CAPTURE_ROOT && elem.symbol.owner.isLocalDummy => + mapCaptureRoot(pre, cls, elem.localRootOwner.asClass, elem) + .showing(i"mapped capture root $elem in $cls to $result", capt) + case _ => + elem + + tp match + case t @ CapturingType(parent, refs) => + val elems = refs.elems.toList + val elems1 = elems.mapConserve(instRoot) + val refs1 = if elems1 eq elems then refs else CaptureSet(elems1*) + t.derivedCapturingType(apply(parent), refs1) + case _ => + mapOver(tp) + end instantiateOuterClassRoots end CaptureRoot diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 1ff8204602aa..6af9e3ad56c8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -233,7 +233,7 @@ sealed abstract class CaptureSet extends Showable: else if that.subCaptures(this, frozen = true).isOK then this else if this.isConst && that.isConst then Const(this.elems ++ that.elems) else Var( - this.levelLimit.maxNested(that.levelLimit, pickFirstOnConflict = true), + this.levelLimit.maxNested(that.levelLimit, onConflict = (sym1, sym2) => sym1), this.elems ++ that.elems) .addAsDependentTo(this).addAsDependentTo(that) @@ -315,6 +315,22 @@ sealed abstract class CaptureSet extends Showable: def substParams(tl: BindingType, to: List[Type])(using Context) = map(Substituters.SubstParamsMap(tl, to)) + /** The capture root that corresponds to this capture set. This is: + * - if the capture set is a Var with a defined level limit, the + * associated capture root, + * - otherwise, if the set is nonempty, the innermost root such + * that some element of the set subcaptures this root, + * - otherwise, if the set is empty, `default`. + */ + def impliedRoot(default: CaptureRoot)(using Context): CaptureRoot = + if levelLimit.exists then levelLimit.localRoot.termRef + else if elems.isEmpty then default + else elems.toList + .map: + case elem: CaptureRoot if elem.isLocalRootCapability => elem + case elem => elem.captureSetOfInfo.impliedRoot(default) + .reduce((x: CaptureRoot, y: CaptureRoot) => CaptureRoot.lub(x, y)) + /** Invoke handler if this set has (or later aquires) the root capability `cap` */ def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = if isUniversal then handler() diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a1632fa9d5f3..600b9fc6f04b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -319,7 +319,7 @@ class CheckCaptures extends Recheck, SymTransformer: includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - instantiateLocalRoots(tree.symbol, pt): + instantiateLocalRoots(tree.symbol, NoPrefix, pt): super.recheckIdent(tree, pt) /** A specialized implementation of the selection rule. @@ -349,7 +349,7 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet - instantiateLocalRoots(tree.symbol, pt): + instantiateLocalRoots(tree.symbol, qualType, pt): if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then selType else @@ -370,14 +370,23 @@ class CheckCaptures extends Recheck, SymTransformer: * - `tp` is the type of a function that gets applied, either as a method * or as a function value that gets applied. */ - def instantiateLocalRoots(sym: Symbol, pt: Type)(tp: Type)(using Context): Type = + def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type)(tp: Type)(using Context): Type = def canInstantiate = sym.is(Method, butNot = Accessor) || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto - if sym.skipConstructor.isLevelOwner && canInstantiate then + if canInstantiate then val tpw = tp.widen - val tp1 = mapRoots(sym.localRoot.termRef, CaptureRoot.Var(ctx.owner, sym))(tpw) - .showing(i"INST $sym: $tp, ${sym.localRoot} = $result", ccSetup) + var tp1 = tpw + val rootVar = CaptureRoot.Var(ctx.owner, sym) + if sym.skipConstructor.isLevelOwner then + tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1) + if tp1 ne tpw then + ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1") + if sym.owner.isClass then + val tp2 = CaptureRoot.instantiateOuterClassRoots(sym, pre, rootVar)(tp1) + if tp2 ne tp1 then + ccSetup.println(i"INST class $sym: $tp, ${sym.localRoot} in $pre = $tp2") + tp1 = tp2 if tpw eq tp1 then tp else tp1 else tp @@ -695,6 +704,9 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => val res = try super.recheck(tree, pt) + catch case ex: CaptureRoot.NoCommonRoot => + report.error(ex.getMessage.nn) + tree.tpe finally curEnv = saved if tree.isTerm && !pt.isBoxedCapturing then markFree(res.boxedCaptureSet, tree.srcPos) diff --git a/tests/neg-custom-args/captures/pairs.check b/tests/neg-custom-args/captures/pairs.check new file mode 100644 index 000000000000..6054d94c278e --- /dev/null +++ b/tests/neg-custom-args/captures/pairs.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:15:31 ---------------------------------------- +15 | val x1c: Cap^ ->{c} Unit = x1 // error + | ^^ + | Found: (x$0: Cap^{cap[test]}) ->{x1} Unit + | Required: Cap^{cap[x1c]} ->{c} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:17:30 ---------------------------------------- +17 | val y1c: Cap ->{d} Unit = y1 // error + | ^^ + | Found: (x$0: Cap^{cap[test]}) ->{y1} Unit + | Required: Cap^{cap[y1c]} ->{d} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:34:30 ---------------------------------------- +34 | val x1c: Cap ->{c} Unit = x1 // error + | ^^ + | Found: (x$0: Cap^{cap[test]}) ->{x1} Unit + | Required: Cap^{cap[x1c]} ->{c} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:36:30 ---------------------------------------- +36 | val y1c: Cap ->{d} Unit = y1 // error + | ^^ + | Found: (x$0: Cap^{cap[test]}) ->{y1} Unit + | Required: Cap^{cap[y1c]} ->{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..c78772eb7438 --- /dev/null +++ b/tests/neg-custom-args/captures/pairs.scala @@ -0,0 +1,37 @@ +@annotation.capability class Cap + +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 // error + val y1 = p.snd + val y1c: Cap ->{d} Unit = y1 // error + +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 // error + val y1 = p.snd + val y1c: Cap ->{d} Unit = y1 // error + diff --git a/tests/pos-custom-args/captures/outer-roots.scala b/tests/pos-custom-args/captures/outer-roots.scala new file mode 100644 index 000000000000..75b27e89667c --- /dev/null +++ b/tests/pos-custom-args/captures/outer-roots.scala @@ -0,0 +1,26 @@ +class C +type Cap = C^ + +class A +class B + +class Foo(x: Cap): + + def foo: A ->{cap[Foo]} Unit = ??? + + class Bar(y: Cap): + + def bar: B ->{cap[Bar]} Unit = ??? + + def f(a: A ->{cap[Foo]} Unit, b: B ->{cap[Bar]} Unit) + : (A ->{a} Unit, B ->{b} Unit) + = (a, b) + +def test(c1: Cap, c2: Cap) = + val x = Foo(c1) + val y = x.Bar(c2) + val xfoo = x.foo + val ybar = y.bar + val z1 = y.f(xfoo, ybar) + val z2 = y.f(x.foo, y.bar) + () diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index 93bf919a4750..c8c169a1041c 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -29,9 +29,9 @@ object Monomorphic: 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^{cap[test]} ->{c} Unit = x1 val y1 = p.snd - val y1c: Cap ->{d} Unit = y1 + val y1c: Cap^{cap[test]} ->{d} Unit = y1 object Monomorphic2: @@ -48,7 +48,6 @@ object Monomorphic2: 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^{cap[test]} ->{c} Unit = x1 val y1 = p.snd - val y1c: Cap ->{d} Unit = y1 - + val y1c: Cap^{cap[test]} ->{d} Unit = y1 diff --git a/tests/pos-custom-args/captures/refs.scala b/tests/pos-custom-args/captures/refs.scala new file mode 100644 index 000000000000..372b93421c82 --- /dev/null +++ b/tests/pos-custom-args/captures/refs.scala @@ -0,0 +1,29 @@ +type Proc = () => Unit + +class MonoRef(init: Proc): + type MonoProc = Proc + var x: MonoProc = init + def getX: MonoProc = x + def setX(x: MonoProc): Unit = this.x = x + +def test(p: Proc) = + val x = MonoRef(p) + x.setX(p) + val y = x.getX + val yc1: Proc = y + val yc2: () ->{x} Unit = y + val yc3: () ->{cap[test]} Unit = y + +class MonoRef2(init: () => Unit): + var x: () ->{cap[MonoRef2]} Unit = init + def getX: () ->{cap[MonoRef2]} Unit = x + def setX(x: () ->{cap[MonoRef2]} Unit): Unit = this.x = x + +def test2(p: Proc) = + val x = MonoRef2(p) + x.setX(p) + val y = x.getX + val yc1: Proc = y + val yc2: () ->{x} Unit = y + val yc3: () ->{cap[test2]} Unit = y + From c0b8fb50d0612216f7ce614634353680efe0d6bf Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 5 Oct 2023 16:30:24 +0200 Subject: [PATCH 041/117] Simplifications - Drop valsCanBeLevelOwners and levelOwnersNeedCapParam config settings - Drop !isCaseClassSynthetic condition in isLevelOwner - Drop handling of anonymous functions in levelOwnerNamed Neither are needed anymore with the the conflg flags gone. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 32 +++++-------------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 5 +-- .../src/dotty/tools/dotc/cc/Synthetics.scala | 4 +-- .../captures/concat-iterator.scala | 17 ++++++++++ 4 files changed, 30 insertions(+), 28 deletions(-) create mode 100644 tests/pos-custom-args/captures/concat-iterator.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 986edf4d0a59..f54261de79c8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -28,15 +28,7 @@ private val adaptUnpickledFunctionTypes = false */ private val constrainRootsWhenMapping = true -/** If true, most vals can be level owners. If false, only vals defined by a - * closure as RHS can be level owners - */ -private val valsCanBeLevelOwners = true - -/** If true, only vals, defs, and classes with a universal capability in a parameter - * or self type are considered as level owners. - */ -private val levelOwnersNeedCapParam = true +private val constructorsAreLevelOwners = false def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) @@ -390,25 +382,21 @@ extension (sym: Symbol) def isCaseClassSynthetic = // TODO drop symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty def classQualifies = - !levelOwnersNeedCapParam - || takesCappedParamIn(symd.primaryConstructor.info) + takesCappedParamIn(symd.primaryConstructor.info) || symd.asClass.givenSelfType.hasUniversalRootOf(sym) - def termQualifies = - !levelOwnersNeedCapParam || takesCappedParamIn(symd.info) def compute = if symd.isClass then symd.is(CaptureChecked) && classQualifies || symd.isRoot else (symd.is(Method, butNot = Accessor) - || valsCanBeLevelOwners && symd.isTerm && !symd.isOneOf(TermParamOrAccessor | Mutable)) + || symd.isTerm && !symd.isOneOf(TermParamOrAccessor | Mutable)) && (!symd.owner.isClass || symd.owner.is(CaptureChecked) || Synthetics.needsTransform(symd) ) - && !isCaseClassSynthetic && !symd.isConstructor && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) - && termQualifies + && takesCappedParamIn(symd.info) && { ccSetup.println(i"Level owner $sym"); true } ccState.isLevelOwner.getOrElseUpdate(sym, compute) @@ -433,17 +421,13 @@ extension (sym: Symbol) * owner. */ def levelOwnerNamed(name: String)(using Context): Symbol = - def recur(sym: Symbol, prev: Symbol): Symbol = + def recur(sym: Symbol): Symbol = if sym.name.toString == name then if sym.isLevelOwner then sym - else if sym.isTerm && !sym.isOneOf(Method | Module) && prev.exists then prev else NoSymbol - else if sym == defn.RootClass then - NoSymbol - else - val prev1 = if sym.isAnonymousFunction && sym.isLevelOwner then sym else NoSymbol - recur(sym.owner, prev1) - recur(sym, NoSymbol) + else if sym == defn.RootClass then NoSymbol + else recur(sym.owner) + recur(sym) .showing(i"find outer $sym [ $name ] = $result", capt) /** The parameter with type caps.Cap in the leading term parameter section, diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 0ff8ed18df8d..c6ea8c9b488d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -463,8 +463,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case possiblyTypedClosureDef(ddef) if !mentionsCap(rhsOfEtaExpansion(ddef)) && !sym.is(Mutable) - && (!levelOwnersNeedCapParam - || ddef.symbol.takesCappedParamIn(ddef.symbol.info)) => + && ddef.symbol.takesCappedParamIn(ddef.symbol.info) => ccSetup.println(i"Level owner at setup $sym / ${ddef.symbol.info}") ccState.isLevelOwner(sym) = true case _ => @@ -571,6 +570,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // closures are handled specially; the newInfo is constrained from // the expected type and only afterwards we recheck the definition newInfo + else if sym.isPrimaryConstructor then + newInfo else new LazyType: def complete(denot: SymDenotation)(using Context) = // infos of other methods are determined from their definitions which diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 4b20533c074e..9cd7b7cec3cb 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -157,12 +157,12 @@ object Synthetics: case DefaultGetterName(nme.copy, n) => transformDefaultGetterCaptures(info, symd.owner, n) case nme.unapply => - if levelOwnersNeedCapParam then ccState.isLevelOwner(symd.symbol) = true + ccState.isLevelOwner(symd.symbol) = true transformUnapplyCaptures(info) case nme.apply | nme.copy => addCaptureDeps(info) case nme.andThen | nme.compose => - if levelOwnersNeedCapParam then ccState.isLevelOwner(symd.symbol) = true + ccState.isLevelOwner(symd.symbol) = true transformComposeCaptures(info, symd.owner) case nme.curried | nme.tupled => transformCurriedTupledCaptures(info, symd.owner) 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} = ??? + + + + From 3fc63fc20ff265f836ba53170cce6e81427edeb0 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 5 Oct 2023 17:07:55 +0200 Subject: [PATCH 042/117] Make constructors level owners --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 3 --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 8 +++++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f54261de79c8..5cc2f0950f53 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -28,8 +28,6 @@ private val adaptUnpickledFunctionTypes = false */ private val constrainRootsWhenMapping = true -private val constructorsAreLevelOwners = false - def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) @@ -394,7 +392,6 @@ extension (sym: Symbol) || symd.owner.is(CaptureChecked) || Synthetics.needsTransform(symd) ) - && !symd.isConstructor && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) && takesCappedParamIn(symd.info) && { ccSetup.println(i"Level owner $sym"); true } diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 600b9fc6f04b..bef7507850d6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -378,7 +378,7 @@ class CheckCaptures extends Recheck, SymTransformer: val tpw = tp.widen var tp1 = tpw val rootVar = CaptureRoot.Var(ctx.owner, sym) - if sym.skipConstructor.isLevelOwner then + if sym.isLevelOwner then tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1) if tp1 ne tpw then ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1") diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index c6ea8c9b488d..28fdaac3c165 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -566,12 +566,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val newInfo = absInfo(localReturnType) if newInfo ne sym.info then val updatedInfo = - if sym.isAnonymousFunction || sym.is(Param) || sym.is(ParamAccessor) then + 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 if sym.isPrimaryConstructor then - newInfo else new LazyType: def complete(denot: SymDenotation)(using Context) = // infos of other methods are determined from their definitions which From a4e47b1d41ba54ac24c25a098f147d6b1e24551d Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 6 Oct 2023 16:32:47 +0200 Subject: [PATCH 043/117] Work capture root determination into AsSeenFrom --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 5 ----- .../src/dotty/tools/dotc/core/TypeOps.scala | 16 ++++++++++++++-- tests/pos-custom-args/captures/test.scala | 17 ++++++++++++----- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 6af9e3ad56c8..d280664dd063 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -511,7 +511,7 @@ object CaptureSet: if elem.isRootCapability then res else res.orElse(addNewElems(elem.captureSetOfInfo.elems, origin)) else - //assert(id != 2, newElems) + //assert(id != 19 || !elem.isLocalRootCapability, elem.asInstanceOf[TermRef].localRootOwner) elems += elem if elem.isGenericRootCapability then rootAddedHandler() newElemAddedHandler(elem) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index bef7507850d6..8af9be85ebd7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -382,11 +382,6 @@ class CheckCaptures extends Recheck, SymTransformer: tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1) if tp1 ne tpw then ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1") - if sym.owner.isClass then - val tp2 = CaptureRoot.instantiateOuterClassRoots(sym, pre, rootVar)(tp1) - if tp2 ne tp1 then - ccSetup.println(i"INST class $sym: $tp, ${sym.localRoot} in $pre = $tp2") - tp1 = tp2 if tpw eq tp1 then tp else tp1 else tp diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 2b4dc05c1a16..abbaf77dc699 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -11,7 +11,7 @@ import StdNames._ import collection.mutable import ast.tpd._ import reporting.trace -import config.Printers.typr +import config.Printers.{typr, ccSetup} import config.Feature import transform.SymUtils.* import typer.ProtoTypes._ @@ -93,13 +93,25 @@ object TypeOps: } } + def mapLocalRoot(tp: TermRef): Type = + if tp.symbol.owner.isLocalDummy then + val pre1 = toPrefix(pre, cls, tp.localRootOwner.asClass) + if pre1 ne tp then pre1.captureSet.impliedRoot(tp) + .showing(i"map local root $tp from $pre to $result", ccSetup) + else tp + else tp + trace.conditionally(track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG // All cases except for ThisType are the same as in Map. Inlined for performance // TODO: generalize the inlining trick? 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 then tp + else if tp.prefix `eq` NoPrefix then + if tp.name == nme.LOCAL_CAPTURE_ROOT + then mapLocalRoot(tp.asInstanceOf[TermRef]) + else tp else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix))) case tp: LambdaType => mapOverLambda(tp) // special cased common case diff --git a/tests/pos-custom-args/captures/test.scala b/tests/pos-custom-args/captures/test.scala index cf532bbdf34a..974e7673aa86 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(p: () => Unit): + private var x: () => Unit = p + def set(x: () ->{cap[Ref]} Unit): Unit = this.x = x + def get: () => Unit = 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) - () From dac35e1cb34cba6fa3b596de0f0a1363fd9ff2e2 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 7 Oct 2023 11:53:42 +0200 Subject: [PATCH 044/117] Keep track of failure traces when subCapturing fails under -Ycc-debug --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index d280664dd063..8413aa1c1e93 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -417,7 +417,7 @@ object CaptureSet: def isAlwaysEmpty = elems.isEmpty def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = - CompareResult.Fail(this) + CompareResult.Fail(this :: Nil) def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -505,7 +505,7 @@ object CaptureSet: final def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = if isConst || !recordElemsState() then - CompareResult.Fail(this) // fail if variable is solved or given VarState is frozen + CompareResult.Fail(this :: Nil) // fail if variable is solved or given VarState is frozen else if !levelOK(elem) then val res = CompareResult.LevelError(this, elem) if elem.isRootCapability then res @@ -518,7 +518,7 @@ object CaptureSet: // assert(id != 5 || elems.size != 3, this) (CompareResult.OK /: deps) { (r, dep) => r.andAlso(dep.tryInclude(elem, this)) - } + }.addToTrace(this) private def levelOK(elem: CaptureRef)(using Context): Boolean = !levelLimit.exists @@ -540,7 +540,7 @@ object CaptureSet: deps += cs CompareResult.OK else - CompareResult.Fail(this) + CompareResult.Fail(this :: Nil) override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = rootAddedHandler = handler @@ -684,7 +684,7 @@ object CaptureSet: .andAlso { if added.isConst then CompareResult.OK else if added.asVar.recordDepsState() then { addAsDependentTo(added); CompareResult.OK } - else CompareResult.Fail(this) + else CompareResult.Fail(this :: Nil) } .andAlso { if (origin ne source) && (origin ne initial) && mapIsIdempotent then @@ -701,7 +701,7 @@ object CaptureSet: // 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) + CompareResult.Fail(this :: Nil) else CompareResult.OK } @@ -767,7 +767,7 @@ object CaptureSet: super.addNewElems(newElems, origin) .andAlso { if filtered.size == newElems.size then source.tryInclude(newElems, this) - else CompareResult.Fail(this) + else CompareResult.Fail(this :: Nil) } override def computeApprox(origin: CaptureSet)(using Context): CaptureSet = @@ -866,14 +866,16 @@ object CaptureSet: enum CompareResult extends Showable: case OK - case Fail(cs: CaptureSet) + 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(blocking: CaptureSet) => blocking.show + 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})") @@ -882,7 +884,7 @@ object CaptureSet: /** If not isOK, the blocking capture set */ def blocking: CaptureSet = (this: @unchecked) match - case Fail(cs) => cs + case Fail(cs) => cs.last case LevelError(cs, _) => cs /** Optionally, this result if it is a level error */ @@ -899,6 +901,10 @@ object CaptureSet: val alt = op if alt.isOK then alt else this + + inline def addToTrace(cs: CaptureSet) = this match + case Fail(trace) => Fail(cs :: trace) + case _ => this end CompareResult /** A VarState serves as a snapshot mechanism that can undo From c769305d8c55663f266c76a1b74046ff043fe26d Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 7 Oct 2023 14:55:40 +0200 Subject: [PATCH 045/117] Keep track of enclosing capture sets when comparing refined types When comparing refined types C{x: T}^cs <:< C{x: T'}^cs' we need to remember the capture set `cs` on the left hand side when we get to ask whether the refined type has a member `x: T'`, so that we can correctly instantiate any local root `cap[C]` in the member type of `x`. Same if the LHS is simply `C^cs`. In both cases we strip the capture set before we get to take the compute `x`, so we have to re-add it at the point of that member computation. --- .../dotty/tools/dotc/core/TypeComparer.scala | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7c3f098d8011..d41b54c2c1fb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -68,6 +68,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private var myInstance: TypeComparer = this def currentInstance: TypeComparer = myInstance + /** All capturing types in the original `tp1` enclosing the currently + * compared type. + */ + private var enclosingCapturing1: List[AnnotatedType] = Nil + /** Is a subtype check in progress? In that case we may not * permanently instantiate type variables, because the corresponding * constraint might still be retracted and the instantiation should @@ -256,7 +261,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,17 +543,23 @@ 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 - val tp2a = - if tp1.isBoxedCapturing && !parent1.isBoxedCapturing - then tp2.unboxed - else tp2 - recur(parent1, tp2a) - 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 + try + enclosingCapturing1 = tp1 :: enclosingCapturing1 + recur(parent1, tp2a) + finally + enclosingCapturing1 = enclosingCapturing1.tail + else thirdTry + compareCapturing case tp1: AnnotatedType if !tp1.isRefining => recur(tp1.parent, tp2) case tp1: MatchType => @@ -660,10 +671,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) @@ -672,7 +685,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if defn.isFunctionType(tp2) then if tp2.derivesFrom(defn.PolyFunctionClass) then - return isSubInfo(tp1.member(nme.apply).info, tp2.refinedInfo) + return isSubInfo(tp1.ccMember(nme.apply).info, tp2.refinedInfo) else tp1w.widenDealias match case tp1: RefinedType => @@ -2028,7 +2041,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * rebase both itself and the member info of `tp` on a freshly created skolem type. */ def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = - trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) { + trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.ccMember(name).info}", subtyping) { // If the member is an abstract type and the prefix is a path, compare the member itself // instead of its bounds. This case is needed situations like: @@ -2118,9 +2131,30 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || (tp1.isStable && m.symbol.isStableMember && isSubType(TermRef(tp1, m.symbol), tp2.refinedInfo)) end qualifies - tp1.member(name).hasAltWithInline(qualifies) + tp1.ccMember(name).hasAltWithInline(qualifies) } + extension (qual: Type) + /** Add all directly enclosing capture sets to `qual` and select `name` on the + * resulting type. A capture set is directly enclosing if there is an enclosing + * capturing type with the set and all types between `qual` and that type + * are RefinedTypes or CapturingTypes. + */ + def ccMember(name: Name): Denotation = + def isEnclosing(tp: Type): Boolean = tp match + case RefinedType(parent, _, _) => isEnclosing(parent) + case CapturingType(parent, _) => isEnclosing(parent) + case _ => tp eq qual + + def addCaptures(tp: Type, encls: List[AnnotatedType]): Type = encls match + case (ct @ CapturingType(parent, refs)) :: encls1 if isEnclosing(parent) => + addCaptures(CapturingType(tp, refs, ct.isBoxedCapturing), encls1) + case _ => + tp + + addCaptures(qual, enclosingCapturing1).member(name) + end ccMember + final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { case tp: SingletonType if tp.isStable => tp case tp: ValueType => SkolemType(tp) @@ -3397,7 +3431,7 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { } override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = - traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") { + traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.ccMember(name).info)}") { super.hasMatchingMember(name, tp1, tp2) } From 542354186db982da4ebff7393aa894118a58e76a Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 7 Oct 2023 14:56:48 +0200 Subject: [PATCH 046/117] Change owner of computed capture set for constructor applications --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 9 +++++++++ compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 8413aa1c1e93..748fd36f6be7 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -274,6 +274,11 @@ sealed abstract class CaptureSet extends Showable: else Const(elems.filter(p)) else Filtered(asVar, p) + /** This set with a new owner and therefore also a new levelLimit */ + def changeOwner(newOwner: Symbol)(using Context): CaptureSet = + if this.isConst then this + else ChangedOwner(asVar, newOwner) + /** Capture set obtained by applying `tm` to all elements of the current capture set * and joining the results. If the current capture set is a variable, the same * transformation is applied to all future additions of new elements. @@ -752,6 +757,10 @@ object CaptureSet: override def toString = s"BiMapped$id($source, elems = $elems)" end BiMapped + /** Same as `source` but with a new directOwner */ + class ChangedOwner private[CaptureSet](val source: Var, newOwner: Symbol)(using @constructorOnly ctx: Context) + extends DerivedVar(newOwner, source.elems) + /** 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) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 8af9be85ebd7..a7775a83d596 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -509,7 +509,7 @@ class CheckCaptures extends Recheck, SymTransformer: augmentConstructorType(parent, initCs ++ refs) case _ => val (refined, cs) = addParamArgRefinements(core, initCs) - refined.capturing(cs) + refined.capturing(cs.changeOwner(ctx.owner)) augmentConstructorType(ownType, capturedVars(cls) ++ capturedVars(sym)) .showing(i"constr type $mt with $argTypes%, % in $cls = $result", capt) From 8a679aa8bdc86e1b2f8b9c6e06b668ab2590638e Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 7 Oct 2023 14:58:26 +0200 Subject: [PATCH 047/117] Treat added class refinements as inferred types for further processing Without that step, class refinements can contain references to the generic root `cap`. --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 11 ++++---- tests/neg-custom-args/captures/i15772.check | 12 ++++----- tests/neg-custom-args/captures/lazylist.check | 4 +-- tests/neg-custom-args/captures/lazyref.check | 14 +++++------ .../captures/nested-classes-2.scala | 25 +++++++++++++++++++ tests/pos/functorial-functors.scala | 14 +++++++++++ 6 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 tests/pos-custom-args/captures/nested-classes-2.scala create mode 100644 tests/pos/functorial-functors.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 28fdaac3c165..cb46270d89e2 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -166,12 +166,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * 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(using Context) = new TypeMap: + private def mapInferred(refine: Boolean)(using Context): TypeMap = new TypeMap: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters @@ -179,13 +179,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * where CV_1, ..., CV_n are fresh capture sets. */ def addCaptureRefinements(tp: Type): Type = tp match - case _: TypeRef | _: AppliedType if tp.typeParams.isEmpty => + 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 = tp.memberInfo(getter).strippedDealias + val getterType = + mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias RefinedType(core, getter.name, CapturingType(getterType, CaptureSet.RefiningVar(NoSymbol, getter))) .showing(i"add capture refinement $tp --> $result", ccSetup) @@ -254,7 +255,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end mapInferred private def transformInferredType(tp: Type)(using Context): Type = - mapInferred(tp) + mapInferred(refine = true)(tp) private def transformExplicitType(tp: Type, rootTarget: Symbol, tptToCheck: Option[Tree] = None)(using Context): Type = val expandAliases = new DeepTypeMap: diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index d66fe7a4d063..e6c2dff87dc7 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..boxed1](from instantiating box1), c} Unit) ->{c} Unit - | Required: (C^{cap[boxed1]} ->{cap[boxed1]} Unit) -> Unit + | Found: (C{val arg: C^{cap[main1]}}^{c} ->{'cap[main1..boxed1](from instantiating box1), c} Unit) ->{c} Unit + | Required: (C^{cap[boxed1]} ->{cap[boxed1]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ @@ -18,15 +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..boxed2](from instantiating box2), c} Unit) ->{c} Unit - | Required: (C^{cap[boxed2]} ->{cap[boxed2]} Unit) -> Unit + | Found: (C{val arg: C^{cap[main2]}}^{c} ->{'cap[main2..boxed2](from instantiating box2), c} Unit) ->{c} Unit + | Required: (C^{cap[boxed2]} ->{cap[boxed2]} Unit) -> Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - | Found: (C{val arg: C^{cap[C]}}^{cap[main3]} ->{cap[main3]} Unit) ->{cap[main3]} Unit - | Required: (C ->{cap[boxed2]} Unit) ->{cap[boxed2]} Unit + | Found: (C{val arg: C^{cap[main3]}}^{cap[main3]} ->{cap[main3]} Unit) ->{cap[main3]} Unit + | Required: (C ->{cap[boxed2]} Unit) ->{cap[boxed2]} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index f8f1c1df229f..29a667c038c5 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^}^{cap1}) - | Required: lazylists.LazyList[Int] + | Found: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index a9806bc5494d..c1ecdb141bb3 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -1,28 +1,28 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:19:28 -------------------------------------- 19 | val ref1c: LazyRef[Int] = ref1 // error | ^^^^ - | Found: (ref1 : LazyRef[Int]{val elem: () ->{cap1} Int}^{cap1}) + | Found: LazyRef[Int]{val elem: () ->{cap1} Int}^{ref1} | Required: LazyRef[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:21:35 -------------------------------------- 21 | val ref2c: LazyRef[Int]^{cap2} = ref2 // error | ^^^^ - | Found: (ref2 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, ref1}) - | Required: LazyRef[Int]^{cap2} + | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref2} + | Required: LazyRef[Int]^{cap2} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:23:35 -------------------------------------- 23 | val ref3c: LazyRef[Int]^{ref1} = ref3 // error | ^^^^ - | Found: (ref3 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, ref1}) - | Required: LazyRef[Int]^{ref1} + | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref3} + | Required: LazyRef[Int]^{ref1} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:25:35 -------------------------------------- 25 | val ref4c: LazyRef[Int]^{cap1} = ref4 // error | ^^^^ - | Found: (ref4 : LazyRef[Int]{val elem: () ->{cap[]} Int}^{cap2, cap1}) - | Required: LazyRef[Int]^{cap1} + | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref4} + | Required: LazyRef[Int]^{cap1} | | longer explanation available when compiling with `-explain` 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..643584f3b719 --- /dev/null +++ b/tests/pos-custom-args/captures/nested-classes-2.scala @@ -0,0 +1,25 @@ + +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/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 + From b62e5500e0adda314d10080f198367c0e0d083c9 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 7 Oct 2023 16:18:44 +0200 Subject: [PATCH 048/117] A first attempt at level checking for classes Check that instantiated root variable of a method inside a class is nested in the instance variable of the class. For the moment I was not able to construct a counter example where this makes a difference. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 16 +++++-- .../captures/nesting-inversion.scala | 43 +++++++++++++++++++ .../captures/nested-classes-2.scala | 1 - 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/neg-custom-args/captures/nesting-inversion.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a7775a83d596..aa776643d2c6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -319,7 +319,7 @@ class CheckCaptures extends Recheck, SymTransformer: includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - instantiateLocalRoots(tree.symbol, NoPrefix, pt): + instantiateLocalRoots(tree.symbol, NoPrefix, pt, tree.srcPos): super.recheckIdent(tree, pt) /** A specialized implementation of the selection rule. @@ -349,7 +349,7 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet - instantiateLocalRoots(tree.symbol, qualType, pt): + instantiateLocalRoots(tree.symbol, qualType, pt, tree.srcPos): if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then selType else @@ -370,7 +370,7 @@ class CheckCaptures extends Recheck, SymTransformer: * - `tp` is the type of a function that gets applied, either as a method * or as a function value that gets applied. */ - def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type)(tp: Type)(using Context): Type = + def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type, pos: SrcPos)(tp: Type)(using Context): Type = def canInstantiate = sym.is(Method, butNot = Accessor) || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto @@ -379,6 +379,16 @@ class CheckCaptures extends Recheck, SymTransformer: var tp1 = tpw val rootVar = CaptureRoot.Var(ctx.owner, sym) if sym.isLevelOwner then + val outerOwner = sym.skipConstructor.owner.levelOwner + if outerOwner.isClass then + val outerRoot = outerOwner.localRoot.termRef + outerRoot.asSeenFrom(pre, sym.owner) match + case outerLimit: CaptureRoot if outerLimit ne outerRoot => + capt.println(i"constraining $rootVar of $sym by $outerLimit") + if !outerLimit.encloses(rootVar) then + // Should this be an assertion failure instead? + report.error(em"outer instance $outerLimit does not enclose local root $rootVar", pos) + case _ => tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1) if tp1 ne tpw then ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1") 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/pos-custom-args/captures/nested-classes-2.scala b/tests/pos-custom-args/captures/nested-classes-2.scala index 643584f3b719..e9e74dedb7c1 100644 --- a/tests/pos-custom-args/captures/nested-classes-2.scala +++ b/tests/pos-custom-args/captures/nested-classes-2.scala @@ -20,6 +20,5 @@ def test2(x1: (() => Unit), x2: (() => Unit) => Unit) = 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 From 71f7478638b7c03346940c51b289021e6b8db4b2 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 8 Oct 2023 12:38:14 +0200 Subject: [PATCH 049/117] Drop instantiateOuterClassRoots --- .../src/dotty/tools/dotc/cc/CaptureRoot.scala | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 1295695cf52b..68323120c75b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -136,57 +136,6 @@ object CaptureRoot: case (r1: Var, r2) => r1.freshEnclosedBy(r1, r2) - /** A map that instantiates all outer class roots in the info of `sym` - * according to prefix `pre`. This is called for adapting the info of - * a selection `pre.sym`. The logic of the function is modeled after - * AsSeenFrom. But where AsSeenFrom maps a `this` of class `C` to a corresponding - * prefix, the present method maps a local root corresponding to a class to - * the root implied by the capture set of the corresponding prefix. - * @param sym the class member symbol whose info is mapped - * @param pre the prefix from which `sym` is selected - * @param deafilt the capture root to use if the capture set of the corresponding - * prfefix is empty. - */ - class instantiateOuterClassRoots(sym: Symbol, pre: Type, default: CaptureRoot)(using Context) extends ApproximatingTypeMap: - val cls = sym.owner.asClass - - def apply(tp: Type): Type = - - /** Analogous to `toPrefix` in `AssSeenFromMap`, but result prefix gets - * further mapped to a capture root via `impliedRoot`. - */ - def mapCaptureRoot(pre: Type, cls: Symbol, thiscls: ClassSymbol, fallBack: CaptureRoot): CaptureRoot = - if (pre eq NoType) || (pre eq NoPrefix) || (cls is PackageClass) then - fallBack - else pre match - case pre: SuperType => - mapCaptureRoot(pre.thistpe, cls, thiscls, fallBack) - case _ => - if thiscls.derivesFrom(cls) && pre.baseType(thiscls).exists then - pre.captureSet.impliedRoot(default) - else if pre.termSymbol.is(Package) && !thiscls.is(Package) then - mapCaptureRoot(pre.select(nme.PACKAGE), cls, thiscls, fallBack) - else - mapCaptureRoot(pre.baseType(cls).normalizedPrefix, cls.owner, thiscls, fallBack) - - def instRoot(elem: CaptureRef): CaptureRef = elem match - case elem: TermRef - if elem.name == nme.LOCAL_CAPTURE_ROOT && elem.symbol.owner.isLocalDummy => - mapCaptureRoot(pre, cls, elem.localRootOwner.asClass, elem) - .showing(i"mapped capture root $elem in $cls to $result", capt) - case _ => - elem - - tp match - case t @ CapturingType(parent, refs) => - val elems = refs.elems.toList - val elems1 = elems.mapConserve(instRoot) - val refs1 = if elems1 eq elems then refs else CaptureSet(elems1*) - t.derivedCapturingType(apply(parent), refs1) - case _ => - mapOver(tp) - end instantiateOuterClassRoots - end CaptureRoot From 0fbd6b89dbcb451a8722ecd73ea704fee3b5459c Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 8 Oct 2023 12:50:46 +0200 Subject: [PATCH 050/117] Disable some special cases about RefiningVars --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 31 +++++++++++-------- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 5cc2f0950f53..fdd2b128d627 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -17,16 +17,21 @@ 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 + + /** 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[cc] val constrainRootsWhenMapping = true + + val oldRefiningRoots = false +end ccConfig def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) @@ -86,7 +91,7 @@ class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends BiTyp def apply(t: Type): Type = if t eq from then to else t match - case t: CaptureRoot.Var if constrainRootsWhenMapping && t.unifiesWith(from) => + case t: CaptureRoot.Var if ccConfig.constrainRootsWhenMapping && t.unifiesWith(from) => to case t @ Setup.Box(t1) => t.derivedBox(this(t1)) @@ -129,7 +134,7 @@ extension (tree: Tree) * 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( @@ -228,7 +233,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,7 +246,7 @@ extension (tp: Type) * a by name parameter type, turning the latter into an impure by name parameter type. */ def adaptByNameArgUnderPureFuns(using Context): Type = - if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then + if ccConfig.adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then AnnotatedType(tp, CaptureAnnotation(CaptureSet.universal, boxed = false)(defn.RetainsByNameAnnot)) else diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d41b54c2c1fb..78720ea6d96c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2117,7 +2117,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling ExprType(info1.resType) case info1 => info1 - if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then + if ccConfig.oldRefiningRoots && ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then // When comparing against a RefiningVar refinement, map the // localRoot of the corresponding class in `tp1` to the owner of the // refining capture set. From 09f00bb2b09fdfd19493ec63da537383d9f34e79 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 8 Oct 2023 13:43:30 +0200 Subject: [PATCH 051/117] Introduce isCaptureChecking test --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 14 +++++++++++++- .../src/dotty/tools/dotc/cc/CapturingType.scala | 4 ++-- compiler/src/dotty/tools/dotc/cc/Synthetics.scala | 3 +-- .../src/dotty/tools/dotc/core/TypeComparer.scala | 11 ++++------- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- .../tools/dotc/transform/OverridingPairs.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Checking.scala | 5 +++-- .../dotty/tools/dotc/typer/ImportSuggestions.scala | 3 ++- .../src/dotty/tools/dotc/typer/RefChecks.scala | 4 ++-- 9 files changed, 31 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index fdd2b128d627..39dcad78d7cb 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -30,12 +30,24 @@ object ccConfig: */ private[cc] val constrainRootsWhenMapping = true - val oldRefiningRoots = false + /** Use old scheme for refining vars, which should be no longer necessary + */ + val oldRefiningVars = false end ccConfig def allowUniversalInBoxed(using Context) = Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`) +/** Are we at checkCaptures phase? */ +def isCaptureChecking(using Context): Boolean = + ctx.phaseId == Phases.checkCapturesPhase.id + +/** 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 + /** A dependent function type with given arguments and result type * TODO Move somewhere else where we treat all function type related ops together. */ diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index ae7838662c1d..e3b30cb64f20 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -56,10 +56,10 @@ object CapturingType: /** 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 ctx.phaseId <= Phases.checkCapturesPhase.id => + if isCaptureCheckingOrSetup => Some((parent, ann.refs)) case AnnotatedType(parent, ann) - if ann.symbol == defn.RetainsAnnot && ctx.phase == Phases.checkCapturesPhase => + 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 diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 9cd7b7cec3cb..c84467160b50 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 @@ -75,7 +74,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)) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 78720ea6d96c..82dc04e8372d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -657,7 +657,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def compareRefined: Boolean = val tp1w = tp1.widen - if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then + if isCaptureCheckingOrSetup then // A relaxed version of subtyping for dependent functions where method types // are treated as contravariant. @@ -1013,10 +1013,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def tp1widened = val tp1w = tp1.underlying.widenExpr tp1 match - case tp1: CaptureRef - if (ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev) - && tp1.isTracked - => + case tp1: CaptureRef if isCaptureCheckingOrSetup && tp1.isTracked => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w @@ -2117,7 +2114,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling ExprType(info1.resType) case info1 => info1 - if ccConfig.oldRefiningRoots && ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then + if ccConfig.oldRefiningVars && isCaptureCheckingOrSetup then // When comparing against a RefiningVar refinement, map the // localRoot of the corresponding class in `tp1` to the owner of the // refining capture set. @@ -2262,7 +2259,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val paramsMatch = if precise then isSameTypeWhenFrozen(formal1, formal2a) - else if ctx.phase == Phases.checkCapturesPhase || ctx.phase == Phases.checkCapturesPhase.prev then + else if isCaptureCheckingOrSetup then // allow to constrain capture set variables isSubType(formal2a, formal1) else diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 44e62d85f0e5..5fa24de9b273 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, RetainingType, CaptureRoot} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, CaptureRoot, isCaptureChecking} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -2211,7 +2211,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 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/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c98be88487f5..0240be422aee 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. 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/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c13b3854a7dd..1a7f75181bb2 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -20,7 +20,7 @@ import config.SourceVersion.{`3.0`, `future`} import config.Printers.refcheck import reporting._ import Constants.Constant -import cc.{mapRoots, localRoot} +import cc.{mapRoots, localRoot, isCaptureChecking} object RefChecks { import tpd._ @@ -105,7 +105,7 @@ object RefChecks { def checkSelfConforms(other: ClassSymbol) = var otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType) - if ctx.phase == Phases.checkCapturesPhase then + if isCaptureChecking then otherSelf = mapRoots(other.localRoot.termRef, cls.localRoot.termRef)(otherSelf) .showing(i"map self $otherSelf = $result", capt) if otherSelf.exists then From c9a02c86c40878db2bdf9e01b65cad5abc554cc7 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 8 Oct 2023 13:43:46 +0200 Subject: [PATCH 052/117] Restrict isUncachable --- compiler/src/dotty/tools/dotc/cc/CapturingType.scala | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index e3b30cb64f20..294414a6c37b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -77,15 +77,10 @@ object CapturingType: 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, since the + */ def isUncachable(tp: Type)(using Context): Boolean = - ctx.phase == Phases.checkCapturesPhase - && (Setup.isDuringSetup - || ctx.mode.is(Mode.IgnoreCaptures) && decomposeCapturingType(tp).isDefined) + ctx.mode.is(Mode.IgnoreCaptures) && decomposeCapturingType(tp).isDefined end CapturingType From c78df14ec7f05533d91085e3ebbd8c56eb4ca578 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 9 Oct 2023 09:47:22 +0200 Subject: [PATCH 053/117] Move most of self type setup to transformSym We need to do it for compiled as well as imported types. We only keep adding variables to selftypes of local classes in postProcess. This allows us to drop one occurrence of LooseRootChecking --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- .../dotty/tools/dotc/cc/CheckCaptures.scala | 28 ++++------ compiler/src/dotty/tools/dotc/cc/Setup.scala | 54 +++++++++---------- .../src/dotty/tools/dotc/core/Types.scala | 8 ++- 4 files changed, 41 insertions(+), 51 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 39dcad78d7cb..a78ec449bde9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -95,7 +95,7 @@ trait FollowAliases extends TypeMap: if t2 ne t1 then return t2 mapOver(t) -class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends BiTypeMap, FollowAliases: +class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends DeepTypeMap, BiTypeMap, FollowAliases: val from = from0.followAlias //override val toString = i"mapRoots($from, $to)" diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index aa776643d2c6..c09b8cb9da45 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1110,12 +1110,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) @@ -1238,16 +1233,15 @@ class CheckCaptures extends Recheck, SymTransformer: 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/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index cb46270d89e2..827dfdb37964 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -113,9 +113,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformSym(symd: SymDenotation)(using Context): SymDenotation = if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol + def rootTarget = + if sym.isAliasType && sym.isStatic then NoSymbol else sym def mappedInfo = if toBeUpdated.contains(sym) then symd.info - else transformExplicitType(symd.info, rootTarget = sym) + else transformExplicitType(symd.info, rootTarget) // TODO if sym is class && level owner: add a capture root if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) @@ -123,7 +125,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 + var newInfo = mappedInfo + if sym.is(ModuleVal) && !sym.isStatic then + val selfType1 = transformSelfType(sym.moduleClass) + if selfType1.exists then + newInfo = newInfo.capturing(selfType1.captureSet) + sym.termRef.invalidateCaches() + 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 @@ -131,6 +140,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd end transformSym + def transformSelfType(sym: Symbol)(using Context) = (sym.info: @unchecked) match + case ClassInfo(_, cls, _, _, selfInfo: Type) => + inContext(ctx.withOwner(cls)): + transformExplicitType(selfInfo, rootTarget = ctx.owner) + case _ => NoType + /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, * convert it to be boxed. */ @@ -490,15 +505,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: postProcess(tree) end traverse - extension (sym: Symbol)(using Context) def unlessStatic = - val owner = sym.levelOwner - if sym.isStaticOwner then NoSymbol else owner - def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => val lowner = transformTT(tree, boxed = false, exact = false, - rootTarget = ctx.owner.unlessStatic // roots of other types in static locations are not mapped + rootTarget = if ctx.owner.isStaticOwner then NoSymbol else ctx.owner + // roots of other types in static locations are not mapped ) case tree: ValOrDefDef => val sym = tree.symbol @@ -594,22 +606,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.symbol match case cls: ClassSymbol => val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - val newSelfType = - if (selfInfo eq NoType) || cls.is(ModuleClass) && !cls.isStatic then - // add capture set to self type of nested classes if no self type is given explicitly. - // 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 => - val selfInfo1 = inContext(ctx.withOwner(cls)): - transformExplicitType(selfInfo, rootTarget = ctx.owner) - if selfInfo1 eq selfInfo then NoType else selfInfo1 - case _ => - NoType - if newSelfType.exists then + 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. + val newSelfType = CapturingType(cinfo.selfType, CaptureSet.Var(cls)) ccSetup.println(i"mapped self type for $cls: $newSelfType, was $selfInfo") val ps1 = ps.mapConserve(transformExplicitType(_, rootTarget = ctx.owner)) val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) @@ -621,11 +624,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: updateInfo(modul, CapturingType(modul.info, newSelfType.captureSet)) modul.termRef.invalidateCaches() case _ => - val info = tree.symbol.info - val newInfo = transformExplicitType(info, rootTarget = ctx.owner.unlessStatic) - updateInfo(tree.symbol, newInfo) - if newInfo ne info then - ccSetup.println(i"update info of ${tree.symbol} from $info to $newInfo") case _ => end postProcess end setupTraverser diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5fa24de9b273..4ec2f21ee07c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5120,10 +5120,8 @@ object Types { else if (clsd.is(Module)) givenSelf else if (ctx.erasedTypes) appliedRef else givenSelf.dealiasKeepAnnots match - case givenSelf1 @ CapturingType(tp, _) => - givenSelf1.derivedAnnotatedType(tp & appliedRef, givenSelf1.annot) - case givenSelf1 @ RetainingType(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) } @@ -5969,7 +5967,7 @@ object Types { val prefix1 = this(tp.prefix) val parents1 = tp.declaredParents mapConserve this val selfInfo1: TypeOrSymbol = tp.selfInfo match { - case selfInfo: Type => this(selfInfo) + case selfInfo: Type => inContext(ctx.withOwner(tp.cls))(this(selfInfo)) case selfInfo => selfInfo } tp.derivedClassInfo(prefix1, parents1, tp.decls, selfInfo1) From 388349afd3bc84be0b788ad3cad4292305c0ff6b Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 9 Oct 2023 15:53:19 +0200 Subject: [PATCH 054/117] Make non-final impure classes level owners That's needed since a subclass might be a level owner. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index a78ec449bde9..5fcd6d243076 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -397,8 +397,11 @@ extension (sym: Symbol) def isCaseClassSynthetic = // TODO drop symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty def classQualifies = - takesCappedParamIn(symd.primaryConstructor.info) - || symd.asClass.givenSelfType.hasUniversalRootOf(sym) + if sym.isEffectivelyFinal then + takesCappedParamIn(symd.primaryConstructor.info) + || symd.asClass.givenSelfType.hasUniversalRootOf(sym) + else + !sym.isPureClass def compute = if symd.isClass then symd.is(CaptureChecked) && classQualifies || symd.isRoot From 9be0f6f576e54b670a025af3434f3fd3045ded5c Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 9 Oct 2023 17:40:31 +0200 Subject: [PATCH 055/117] Translate local roots when doing override checks --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1a7f75181bb2..4b418f779f95 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -20,7 +20,7 @@ import config.SourceVersion.{`3.0`, `future`} import config.Printers.refcheck import reporting._ import Constants.Constant -import cc.{mapRoots, localRoot, isCaptureChecking} +import cc.{mapRoots, localRoot, isCaptureChecking, isLevelOwner} object RefChecks { import tpd._ @@ -269,7 +269,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 @@ -375,7 +375,11 @@ object RefChecks { def memberTp(self: Type) = if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) else self.memberInfo(member) - def otherTp(self: Type) = self.memberInfo(other) + def otherTp(self: Type) = + val info = self.memberInfo(other) + if isCaptureChecking && member.isLevelOwner && other.isLevelOwner + then info.substSym(other.localRoot :: Nil, member.localRoot :: Nil) + else info refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") From b3c4717570faaf4586e2d32903a905b9ba3a9091 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 9 Oct 2023 18:10:57 +0200 Subject: [PATCH 056/117] Map parents of classes with the class as owner. Also: Print local roots with id of owner under -uniqid --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 5 +++-- .../src/dotty/tools/dotc/core/Types.scala | 19 +++++++++---------- .../tools/dotc/printing/PlainPrinter.scala | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 827dfdb37964..2850a22200b1 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -613,10 +613,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // why the class was picked as level owner. But self types should not be able // to mention other fields. val newSelfType = CapturingType(cinfo.selfType, CaptureSet.Var(cls)) - ccSetup.println(i"mapped self type for $cls: $newSelfType, was $selfInfo") - val ps1 = ps.mapConserve(transformExplicitType(_, rootTarget = ctx.owner)) + val ps1 = inContext(ctx.withOwner(cls)): + ps.mapConserve(transformExplicitType(_, rootTarget = cls)) val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) updateInfo(cls, newInfo) + ccSetup.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 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4ec2f21ee07c..60598977b59a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5962,17 +5962,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 => inContext(ctx.withOwner(tp.cls))(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/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 02fc1e44c12a..174454d24751 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -407,7 +407,7 @@ class PlainPrinter(_ctx: Context) extends Printer { 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}]") + else Str(s"cap[${nameString(tp.localRootOwner)}]") else toTextPrefixOf(tp) ~ selectionString(tp) case tp: ThisType => nameString(tp.cls) + ".this" From dd80312122ff85bf599f8d7057b788142ed9dd83 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 9 Oct 2023 18:19:43 +0200 Subject: [PATCH 057/117] Fix CC asSeenFrom when prefix is `this` --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index abbaf77dc699..9eb18629b730 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -19,7 +19,7 @@ import typer.ForceDegree import typer.Inferencing._ import typer.IfBottom import reporting.TestingReporter -import cc.{CapturingType, derivedCapturingType, CaptureSet, isBoxed, isBoxedCapturing} +import cc.{CapturingType, derivedCapturingType, CaptureSet, isBoxed, isBoxedCapturing, isLevelOwner, localRoot} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -96,8 +96,9 @@ object TypeOps: def mapLocalRoot(tp: TermRef): Type = if tp.symbol.owner.isLocalDummy then val pre1 = toPrefix(pre, cls, tp.localRootOwner.asClass) - if pre1 ne tp then pre1.captureSet.impliedRoot(tp) - .showing(i"map local root $tp from $pre to $result", ccSetup) + if pre1 ne tp then pre1 match + case pre1: ThisType if pre1.cls.isLevelOwner => pre1.cls.localRoot.termRef + case _ => pre1.captureSet.impliedRoot(tp) else tp else tp From 6f9e565957d2c21e20e2d86cace4c2b964b253b7 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 10 Oct 2023 10:48:25 +0200 Subject: [PATCH 058/117] Drop unnecessary code in transformSym The only time a nested object class could have a self type with a non-empty capture set is when it declared in the current compilation unit. Then that capture set is a Var that gets installed via an updateInfo. But then we do also an updateInfo of the module val, so no need to do the same thing in transformSym. --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 13 +------------ .../src/dotty/tools/dotc/core/TypeComparer.scala | 1 - 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 2850a22200b1..7dacbb3e5e05 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -125,12 +125,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: symd.copySymDenotation(info = fluidify(sym.info)) else if symd.owner.isTerm || symd.is(CaptureChecked) || symd.owner.is(CaptureChecked) then val newFlags = newFlagsFor(symd) - var newInfo = mappedInfo - if sym.is(ModuleVal) && !sym.isStatic then - val selfType1 = transformSelfType(sym.moduleClass) - if selfType1.exists then - newInfo = newInfo.capturing(selfType1.captureSet) - sym.termRef.invalidateCaches() + val newInfo = mappedInfo if sym.isClass then sym.thisType.asInstanceOf[ThisType].invalidateCaches() if newFlags != symd.flags || (newInfo ne sym.info) @@ -140,12 +135,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd end transformSym - def transformSelfType(sym: Symbol)(using Context) = (sym.info: @unchecked) match - case ClassInfo(_, cls, _, _, selfInfo: Type) => - inContext(ctx.withOwner(cls)): - transformExplicitType(selfInfo, rootTarget = ctx.owner) - case _ => NoType - /** If `tp` is an unboxed capturing type or a function returning an unboxed capturing type, * convert it to be boxed. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 82dc04e8372d..aa8933e19c39 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2411,7 +2411,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 From ad43c6603bf99917ccbe11f5a5b37df84cb7fd2f Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 12 Oct 2023 17:24:47 +0200 Subject: [PATCH 059/117] Revert "Revert "Implement sealed type variables"" This reverts commit 494272475b091034339fdd079294c59ebb7e41d3. # Conflicts: # compiler/src/dotty/tools/dotc/cc/Setup.scala # compiler/src/dotty/tools/dotc/core/Definitions.scala # tests/neg-custom-args/captures/heal-tparam-cs.scala # tests/neg-custom-args/captures/usingLogFile-alt.check # tests/neg-custom-args/captures/usingLogFile-alt.scala # tests/pos-special/stdlib/Test1.scala --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 5 +-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 23 ++++++++++++- compiler/src/dotty/tools/dotc/cc/Setup.scala | 10 ++++++ .../dotty/tools/dotc/core/Definitions.scala | 2 ++ .../src/dotty/tools/dotc/core/Types.scala | 17 +++++++++- .../dotty/tools/dotc/parsing/Parsers.scala | 32 ++++++++++--------- .../src/dotty/tools/dotc/typer/Checking.scala | 7 +++- library/src/scala/caps.scala | 6 ++++ .../captures/heal-tparam-cs.scala | 24 +++++++------- tests/pos-custom-args/captures/i15749a.scala | 5 ++- tests/pos-custom-args/captures/vars1.scala | 9 +++++- tests/pos-special/stdlib/Test1.scala | 2 +- tests/pos-with-compiler-cc/dotc/Run.scala | 5 +-- .../dotc/core/Scopes.scala | 7 ++-- .../dotc/core/TypeComparer.scala | 11 ++++--- .../dotc/core/tasty/TreeUnpickler.scala | 10 +++--- .../dotc/typer/Namer.scala | 4 ++- .../dotc/typer/Typer.scala | 15 +++++---- 18 files changed, 130 insertions(+), 64 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 748fd36f6be7..0b766be2d790 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -82,10 +82,7 @@ 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(_.isGenericRootCapability) /** Does this capture set contain the root reference `cap` as element? */ final def containsRoot(using Context) = diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c09b8cb9da45..a3022e1c5cc3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -140,6 +140,21 @@ object CheckCaptures: case tpe => report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) + /** Report an error if some part of `tp` contains the root capability in its capture set */ + def disallowRootCapabilitiesIn(tp: Type, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = + val check = new TypeTraverser: + def traverse(t: Type) = + if variance >= 0 then + t.captureSet.disallowRootCapability: () => + def part = if t eq tp then "" else i"the part $t of " + report.error( + em"""$what cannot $have $tp since + |${part}that type captures the root capability `cap`. + |$addendum""", + pos) + traverseChildren(t) + check.traverse(tp) + /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -672,11 +687,17 @@ class CheckCaptures extends Recheck, SymTransformer: val tryOwner = ccState.tryBlockOwner.remove(tree).getOrElse(ctx.owner) val saved = curEnv curEnv = Env(tryOwner, EnvKind.Regular, CaptureSet.Var(curEnv.owner), curEnv) - try + val tp = try inContext(ctx.withOwner(tryOwner)): super.recheckTry(tree, pt) finally curEnv = saved + if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then + disallowRootCapabilitiesIn(tp, + "Result of `try`", "have type", + "This is often caused by a locally generated exception capability leaking as part of its result.", + tree.srcPos) + tp /* Currently not needed, since capture checking takes place after ElimByName. * Keep around in case we need to get back to it diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 7dacbb3e5e05..434c59d0abd3 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -481,6 +481,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for case arg: TypeTree <- args do transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed + if allowUniversalInBoxed then + val polyType = fn.tpe.widen.asInstanceOf[TypeLambda] + for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do + if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then + def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else "" + CheckCaptures.disallowRootCapabilitiesIn(arg.knownType, + i"Sealed type variable $pname", "be instantiated to", + i"This is often caused by a local capability$where\nleaking as part of its result.", + tree.srcPos) + case tree: TypeDef if tree.symbol.isClass => inContext(ctx.withOwner(tree.symbol)): traverseChildren(tree) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ae1e2eb2b4e5..d2c141d72f47 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -983,6 +983,7 @@ 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") @@ -1034,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") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 60598977b59a..1d45297d3925 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4086,10 +4086,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)) } @@ -4411,6 +4416,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)) } 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/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0240be422aee..301fb0f55691 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -518,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.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/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/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index d362f9681d6e..291109b4a6f3 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 + val test1 = localCap { c => // should be error () => { c.use() } } - val test2: (c: Cap^{cap}) -> () ->{cap} Unit = - localCap { c => // should work (was error) - (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/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/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 d5f26060651c..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() 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 } From c2b805a9f8e611f5b6fdd8a47e8c4776e17de6c6 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Oct 2023 11:33:31 +0200 Subject: [PATCH 060/117] Add adapation to convert global to local root --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 13 ++++++- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 7 ++++ .../dotty/tools/dotc/cc/CheckCaptures.scala | 39 +++++++++++++++++-- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 5fcd6d243076..9b1e99341fda 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -83,6 +83,10 @@ end CCState def ccState(using Context) = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState +class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( + i"No common capture root nested in ${rs.mkString(" and ")}" +) + trait FollowAliases extends TypeMap: def mapOverFollowingAliases(t: Type): Type = t match case t: LazyRef => @@ -323,7 +327,7 @@ extension (tp: Type) tp.isCapabilityClassRef end hasUniversalRootOf -extension (cls: Symbol) +extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = if cls.isClass then cls.asClass.baseClasses.find: bc => @@ -391,6 +395,13 @@ extension (sym: Symbol) case _ => false + def isTrackedSomewhere(using Context): Boolean = + val search = new TypeAccumulator[Boolean]: + def apply(found: Boolean, tp: Type) = + def isTrackedHere = variance >= 0 && !tp.captureSet.isAlwaysEmpty + found || isTrackedHere || foldOver(found, tp) + search(false, sym.info) + // TODO Also include vals (right now they are manually entered in levelOwners by Setup) def isLevelOwner(using Context): Boolean = val symd = sym.denot diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 0b766be2d790..c5b48a27fafe 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -90,6 +90,10 @@ sealed abstract class CaptureSet extends Showable: case ref: TermRef => ref.isRootCapability case _ => false + final def disallowsUniversal(using Context) = + if isConst then !isUniversal && elems.exists(_.isLocalRootCapability) + else asVar.noUniversal + /** 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. @@ -476,6 +480,8 @@ 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: CaptureRef => Context ?=> Unit = _ => () @@ -545,6 +551,7 @@ object CaptureSet: CompareResult.Fail(this :: Nil) override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = + noUniversal = true rootAddedHandler = handler super.disallowRootCapability(handler) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index a3022e1c5cc3..7482afab2501 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -730,7 +730,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => val res = try super.recheck(tree, pt) - catch case ex: CaptureRoot.NoCommonRoot => + catch case ex: NoCommonRoot => report.error(ex.getMessage.nn) tree.tpe finally curEnv = saved @@ -760,9 +760,13 @@ class CheckCaptures extends Recheck, SymTransformer: } checkNotUniversal(parent) case _ => - if !allowUniversalInBoxed && needsUniversalCheck then - checkNotUniversal(tpe) - super.recheckFinish(tpe, tree, pt) + val adapted = + if allowUniversalInBoxed then + adaptUniversal(tpe, pt, tree) + else + if needsUniversalCheck then checkNotUniversal(tpe) + tpe + super.recheckFinish(adapted, tree, pt) end recheckFinish // ------------------ Adaptation ------------------------------------- @@ -776,6 +780,32 @@ 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. + def impliedRoot(tree: Tree)(using Context) = + 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) && t.symbol.isTrackedSomewhere => + max(s, t.symbol.levelOwner) + case t: DefTree => + locals += t.symbol + foldOver(s, t) + case _ => + foldOver(s, t) + acc(defn.RootClass, tree).localRoot + + 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") + else actual + /** 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 @@ -865,6 +895,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 From 1df49f19e469687599f36f8d21027c59f5313a20 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Oct 2023 12:21:47 +0200 Subject: [PATCH 061/117] Make cap a supercapture of local roots --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 3 ++- .../captures/{unbox.scala => unbox.scala.disabled} | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename tests/neg-custom-args/captures/{unbox.scala => unbox.scala.disabled} (100%) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index c5b48a27fafe..ee379a6a4463 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -152,7 +152,8 @@ sealed abstract class CaptureSet extends Showable: && ctx.property(LooseRootChecking).isDefined private def isSuperRootOf(y: CaptureRoot) = x match - case x: CaptureRoot if x.isLocalRootCapability => y.encloses(x) + case x: CaptureRoot => + x.isGenericRootCapability || x.isLocalRootCapability && y.encloses(x) case _ => false end extension diff --git a/tests/neg-custom-args/captures/unbox.scala b/tests/neg-custom-args/captures/unbox.scala.disabled similarity index 100% rename from tests/neg-custom-args/captures/unbox.scala rename to tests/neg-custom-args/captures/unbox.scala.disabled From 2be1b41dd6312b8806524d0246ad3039d4dfc85c Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 13:54:57 +0200 Subject: [PATCH 062/117] New scheme --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 - .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 110 ++---------- .../src/dotty/tools/dotc/cc/CaptureRoot.scala | 6 +- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 92 +++++----- .../dotty/tools/dotc/cc/CheckCaptures.scala | 166 +++++++++--------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 49 +++--- .../src/dotty/tools/dotc/cc/Synthetics.scala | 10 +- .../dotty/tools/dotc/core/TypeComparer.scala | 9 - .../src/dotty/tools/dotc/core/TypeOps.scala | 14 +- .../src/dotty/tools/dotc/core/Types.scala | 3 +- .../dotty/tools/dotc/transform/Recheck.scala | 24 +-- .../dotty/tools/dotc/typer/ProtoTypes.scala | 3 + .../dotty/tools/dotc/typer/RefChecks.scala | 10 +- ...s.scala => box-unsoundness.scala.disabled} | 2 +- tests/neg-custom-args/captures/byname.check | 2 +- .../neg-custom-args/captures/capt-test.scala | 6 +- tests/neg-custom-args/captures/capt1.check | 8 +- tests/neg-custom-args/captures/cc-this.check | 4 +- tests/neg-custom-args/captures/cc-this2.check | 2 +- tests/neg-custom-args/captures/cc-this3.check | 2 +- tests/neg-custom-args/captures/cc-this5.check | 4 +- tests/neg-custom-args/captures/eta.check | 4 +- .../captures/exception-definitions.check | 6 +- tests/neg-custom-args/captures/filevar.scala | 2 +- .../captures/heal-tparam-cs.scala | 2 +- tests/neg-custom-args/captures/i15049.scala | 2 +- tests/neg-custom-args/captures/i15116.check | 8 +- tests/neg-custom-args/captures/i15772.check | 20 +-- tests/neg-custom-args/captures/i16114.scala | 2 +- tests/neg-custom-args/captures/lazylist.check | 6 +- .../captures/lazylists-exceptions.check | 15 +- .../captures/lazylists-exceptions.scala | 4 +- .../neg-custom-args/captures/lazylists2.check | 4 +- tests/neg-custom-args/captures/lazyref.check | 8 +- .../captures/leaked-curried.check | 2 +- tests/neg-custom-args/captures/levels.check | 22 +-- tests/neg-custom-args/captures/levels.scala | 29 ++- .../neg-custom-args/captures/localcaps.scala | 2 +- .../neg-custom-args/captures/outer-var.check | 44 +++++ .../neg-custom-args/captures/outer-var.scala | 18 ++ tests/neg-custom-args/captures/pairs.check | 30 +--- tests/neg-custom-args/captures/pairs.scala | 20 --- tests/neg-custom-args/captures/real-try.check | 62 ++++--- tests/neg-custom-args/captures/real-try.scala | 16 +- .../{refs.scala => refs.scala.disabled} | 0 .../captures/sealed-leaks.scala | 7 +- .../captures/simple-escapes.check | 4 +- .../captures/simple-escapes.scala | 2 +- tests/neg-custom-args/captures/try.check | 31 ++-- tests/neg-custom-args/captures/try.scala | 6 +- .../captures/usingLogFile.check | 22 ++- .../captures/usingLogFile.scala | 6 +- .../captures/vars-simple.check | 21 +++ .../captures/vars-simple.scala | 18 ++ tests/neg-custom-args/captures/vars.check | 38 ++-- tests/neg-custom-args/captures/vars.scala | 16 +- tests/neg-custom-args/captures/withFile.scala | 10 ++ tests/neg/x | 151 ++++++++++++++++ .../captures/lazylists-exceptions.scala | 3 +- ...roots.scala => outer-roots.scala.disabled} | 0 tests/pos-custom-args/captures/pairs.scala | 39 +--- .../{refs.scala => refs.scala.disabled} | 0 tests/pos-custom-args/captures/test.scala | 8 +- .../stdlib/collection/IterableOnce.scala | 6 +- .../stdlib/collection/Iterator.scala | 20 +-- .../pos-special/stdlib/collection/View.scala | 3 +- .../stdlib/collection/immutable/List.scala | 12 +- .../collection/mutable/ListBuffer.scala | 9 +- .../colltest5/CollectionStrawManCC5_1.scala | 14 +- 70 files changed, 710 insertions(+), 593 deletions(-) rename tests/neg-custom-args/captures/{box-unsoundness.scala => box-unsoundness.scala.disabled} (90%) create mode 100644 tests/neg-custom-args/captures/outer-var.check create mode 100644 tests/neg-custom-args/captures/outer-var.scala rename tests/neg-custom-args/captures/{refs.scala => refs.scala.disabled} (100%) create mode 100644 tests/neg-custom-args/captures/vars-simple.check create mode 100644 tests/neg-custom-args/captures/vars-simple.scala create mode 100644 tests/neg-custom-args/captures/withFile.scala create mode 100644 tests/neg/x rename tests/pos-custom-args/captures/{outer-roots.scala => outer-roots.scala.disabled} (100%) rename tests/pos-custom-args/captures/{refs.scala => refs.scala.disabled} (100%) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index b6efedce1ffd..c195c85c6b57 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -83,7 +83,6 @@ 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 cc.AddTryOwners) :: // Add symbols as owners of try blocks, 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 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/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 9b1e99341fda..3db6b23cf983 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -99,31 +99,13 @@ trait FollowAliases extends TypeMap: if t2 ne t1 then return t2 mapOver(t) -class mapRoots(from0: CaptureRoot, to: CaptureRoot)(using Context) extends DeepTypeMap, BiTypeMap, FollowAliases: - val from = from0.followAlias - - //override val toString = i"mapRoots($from, $to)" - - def apply(t: Type): Type = - if t eq from then to - else t match - case t: CaptureRoot.Var if ccConfig.constrainRootsWhenMapping && t.unifiesWith(from) => - to - case t @ Setup.Box(t1) => - t.derivedBox(this(t1)) - case _ => - mapOverFollowingAliases(t) - - def inverse = mapRoots(to, from) -end mapRoots - extension (tree: Tree) /** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */ def toCaptureRef(using Context): CaptureRef = tree 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 @@ -301,32 +283,6 @@ extension (tp: Type) mapOver(t) tm(tp) - def hasUniversalRootOf(sym: Symbol)(using Context): Boolean = - - def isOwnRoot(tp: Type)(using Context): Boolean = - tp.isCapabilityClassRef - || tp.dealias.match - case tp: TermRef => - tp.isGenericRootCapability || tp.localRootOwner == sym - case _ => - false - - tp.dealiasKeepAnnots match - case tp @ AnnotatedType(parent, annot) => - val found = annot match - case CaptureAnnotation(refs, _) => refs.elems.exists(isOwnRoot(_)) - case _ => annot.tree.retainedElems.exists(tree => isOwnRoot(tree.tpe)) - found || parent.hasUniversalRootOf(sym) - case tp: TypeRef => - tp.isRef(defn.Caps_Cap) || tp.isCapabilityClassRef - case tp: LazyRef => - tp.ref.hasUniversalRootOf(sym) - case tp: TypeVar => - tp.underlying.hasUniversalRootOf(sym) - case _ => - tp.isCapabilityClassRef - end hasUniversalRootOf - extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = @@ -382,19 +338,6 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox - def takesCappedParamIn(info: Type)(using Context): Boolean = - info.dealias.stripPoly match - case mt: MethodType => - (mt.paramInfos.exists(_.hasUniversalRootOf(sym)) || takesCappedParamIn(mt.resType)) - //.showing(i"takes capped param1 $sym: $mt = $result") - case AppliedType(fn, args) if defn.isFunctionClass(fn.typeSymbol) => - args.init.exists(_.hasUniversalRootOf(sym)) || takesCappedParamIn(args.last) - case defn.RefinedFunctionOf(rinfo) => - takesCappedParamIn(rinfo) - //.showing(i"takes capped param2 $sym: $rinfo = $result") - case _ => - false - def isTrackedSomewhere(using Context): Boolean = val search = new TypeAccumulator[Boolean]: def apply(found: Boolean, tp: Type) = @@ -404,31 +347,18 @@ extension (sym: Symbol) // TODO Also include vals (right now they are manually entered in levelOwners by Setup) def isLevelOwner(using Context): Boolean = - val symd = sym.denot - def isCaseClassSynthetic = // TODO drop - symd.maybeOwner.isClass && symd.owner.is(Case) && symd.is(Synthetic) && symd.info.firstParamNames.isEmpty - def classQualifies = - if sym.isEffectivelyFinal then - takesCappedParamIn(symd.primaryConstructor.info) - || symd.asClass.givenSelfType.hasUniversalRootOf(sym) - else - !sym.isPureClass - def compute = - if symd.isClass then - symd.is(CaptureChecked) && classQualifies || symd.isRoot - else - (symd.is(Method, butNot = Accessor) - || symd.isTerm && !symd.isOneOf(TermParamOrAccessor | Mutable)) - && (!symd.owner.isClass - || symd.owner.is(CaptureChecked) - || Synthetics.needsTransform(symd) - ) - && (!symd.isAnonymousFunction || sym.definedLocalRoot.exists) - && takesCappedParamIn(symd.info) - && { ccSetup.println(i"Level owner $sym"); true } - - ccState.isLevelOwner.getOrElseUpdate(sym, compute) - end isLevelOwner + sym.isClass + || sym.is(Method, butNot = Accessor)// && !sym.isAnonymousFunction // TODO enable? + + /** 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 @@ -444,20 +374,6 @@ extension (sym: Symbol) else recur(sym.owner) recur(sym) - /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. - * If name refers to a val that has a closure as rhs, we return the closure as level - * owner. - */ - def levelOwnerNamed(name: String)(using Context): Symbol = - def recur(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) - .showing(i"find outer $sym [ $name ] = $result", capt) - /** The parameter with type caps.Cap in the leading term parameter section, * or NoSymbol, if none exists. */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala index 68323120c75b..0e900c74a717 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala @@ -59,7 +59,7 @@ object CaptureRoot: */ def freshEnclosedBy(rs: CaptureRoot*)(using Context): CaptureRoot = val r = fresh - if rs.forall(_.encloses(r)) then r else throw NoCommonRoot(rs*) + if rs.forall(_.encloses(r)) then r else throw CaptureRoot.NoCommonRoot(rs*) def computeHash(bs: Binders): Int = hash def hash: Int = System.identityHashCode(this) @@ -107,7 +107,7 @@ object CaptureRoot: r1.alias = r2 r2.outerLimit = r1.outerLimit.maxNested(r2.outerLimit, - onConflict = (_, _) => throw NoCommonRoot(r1, r2)) + onConflict = (_, _) => throw CaptureRoot.NoCommonRoot(r1, r2)) r2.innerLimit = r1.innerLimit.minNested(r2.innerLimit) true else @@ -129,7 +129,7 @@ object CaptureRoot: else (r1, r2) match case (r1: TermRef, r2: TermRef) => r1.localRootOwner.maxNested(r2.localRootOwner, - onConflict = (_, _) => throw NoCommonRoot(r1, r2) + onConflict = (_, _) => throw CaptureRoot.NoCommonRoot(r1, r2) ).termRef case (r1: TermRef, r2: Var) => r2.freshEnclosedBy(r1, r2) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index ee379a6a4463..42423f5247de 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -61,10 +61,6 @@ sealed abstract class CaptureSet extends Showable: */ def levelLimit: Symbol - def rootSet(using Context): CaptureSet = - assert(levelLimit.exists, this) - levelLimit.localRoot.termRef.singletonCaptureSet - /** Is this capture set definitely non-empty? */ final def isNotEmpty: Boolean = !elems.isEmpty @@ -144,16 +140,30 @@ sealed abstract class CaptureSet extends Showable: extension (x: CaptureRef)(using Context) private def subsumes(y: CaptureRef) = (x eq y) + || x.isSuperRootOf(y) || y.match - case y: TermRef => (y.prefix eq x) || y.isLocalRootCapability && x.isSuperRootOf(y) - case y: CaptureRoot.Var => x.isSuperRootOf(y) + case y: TermRef => y.prefix eq x case _ => false || (x.isGenericRootCapability || y.isRootCapability && x.isRootCapability) && ctx.property(LooseRootChecking).isDefined - private def isSuperRootOf(y: CaptureRoot) = x match - case x: CaptureRoot => - x.isGenericRootCapability || x.isLocalRootCapability && y.encloses(x) + /** 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 => + if x.isGenericRootCapability then true + else if x.isLocalRootCapability && !y.isGenericRootCapability then + val xowner = x.localRootOwner + y match + case y: TermRef => + xowner.isContainedIn(y.symbol.levelOwner) + case y: ThisType => + xowner.isContainedIn(y.cls) + case _ => + false + else false case _ => false end extension @@ -207,9 +217,10 @@ sealed abstract class CaptureSet extends Showable: 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 + if !result.isOK then ccState.levelError = ccState.levelError.orElse(result.levelError) - result = result.orElse(elem.captureSetOfInfo.subCaptures(that)) + if !elem.isRootCapability && summon[VarState] != FrozenState then + result = result.orElse(elem.captureSetOfInfo.subCaptures(that)) if result.isOK then recur(elems1) else @@ -322,22 +333,6 @@ sealed abstract class CaptureSet extends Showable: def substParams(tl: BindingType, to: List[Type])(using Context) = map(Substituters.SubstParamsMap(tl, to)) - /** The capture root that corresponds to this capture set. This is: - * - if the capture set is a Var with a defined level limit, the - * associated capture root, - * - otherwise, if the set is nonempty, the innermost root such - * that some element of the set subcaptures this root, - * - otherwise, if the set is empty, `default`. - */ - def impliedRoot(default: CaptureRoot)(using Context): CaptureRoot = - if levelLimit.exists then levelLimit.localRoot.termRef - else if elems.isEmpty then default - else elems.toList - .map: - case elem: CaptureRoot if elem.isLocalRootCapability => elem - case elem => elem.captureSetOfInfo.impliedRoot(default) - .reduce((x: CaptureRoot, y: CaptureRoot) => CaptureRoot.lub(x, y)) - /** Invoke handler if this set has (or later aquires) the root capability `cap` */ def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = if isUniversal then handler() @@ -459,7 +454,7 @@ object CaptureSet: varId += 1 varId - //assert(id != 95) + //assert(id != 40) override val levelLimit = if directOwner.exists then directOwner.levelOwner else NoSymbol @@ -525,12 +520,15 @@ object CaptureSet: if elem.isGenericRootCapability then rootAddedHandler() newElemAddedHandler(elem) // assert(id != 5 || elems.size != 3, this) - (CompareResult.OK /: deps) { (r, dep) => + val res = (CompareResult.OK /: deps) { (r, dep) => r.andAlso(dep.tryInclude(elem, this)) }.addToTrace(this) + if !res.isOK then elems -= elem + res private def levelOK(elem: CaptureRef)(using Context): Boolean = - !levelLimit.exists + if elem.isGenericRootCapability then !noUniversal + else !levelLimit.exists || elem.match case elem: TermRef => var sym = elem.symbol @@ -571,7 +569,7 @@ object CaptureSet: else if elems.exists(_.isRootCapability) then CaptureSet(elems.filter(_.isRootCapability).toList*) else if computingApprox then - rootSet + universal else computingApprox = true try computeApprox(origin).ensuring(_.isConst) @@ -579,7 +577,7 @@ object CaptureSet: /** The intersection of all upper approximations of dependent sets */ protected def computeApprox(origin: CaptureSet)(using Context): CaptureSet = - (rootSet /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } + (universal /: deps) { (acc, sup) => acc ** sup.upperApprox(this) } /** Widen the variable's elements to its upper approximation and * mark it as constant from now on. This is used for contra-variant type variables @@ -720,7 +718,7 @@ object CaptureSet: if source eq origin then // it's a mapping of origin, so not a superset of `origin`, // therefore don't contribute to the intersection. - rootSet + universal else source.upperApprox(this).map(tm) @@ -788,7 +786,7 @@ object CaptureSet: if source eq origin then // it's a filter of origin, so not a superset of `origin`, // therefore don't contribute to the intersection. - rootSet + universal else source.upperApprox(this).filter(p) @@ -819,7 +817,7 @@ object CaptureSet: if (origin eq cs1) || (origin eq cs2) then // it's a combination of origin with some other set, so not a superset of `origin`, // therefore don't contribute to the intersection. - rootSet + universal else CaptureSet(elemIntersection(cs1.upperApprox(this), cs2.upperApprox(this))) @@ -1010,10 +1008,6 @@ object CaptureSet: def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match case ref: TermRef if ref.isRootCapability => ref.singletonCaptureSet - case ref: TermRef if ref.symbol.isLevelOwner => - ofType(ref.underlying, followResult = true).filter( - ref.symbol.localRoot.termRef != _) - // TODO: Can replace filter with - ref.symbol.localRoot.termRef when we drop level nesting case _ => ofType(ref.underlying, followResult = true) @@ -1095,12 +1089,18 @@ object CaptureSet: override def toAdd(using Context) = for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield ccState.levelError = None - 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}""" + if ref.isGenericRootCapability 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/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 7482afab2501..14541f669176 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -13,7 +13,7 @@ import Trees.* import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} import typer.ErrorReporting.{Addenda, err} -import typer.ProtoTypes.AnySelectionProto +import typer.ProtoTypes.{AnySelectionProto, LhsProto} import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property} import transform.SymUtils.* import transform.{Recheck, PreRecheck} @@ -141,18 +141,46 @@ object CheckCaptures: report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) /** Report an error if some part of `tp` contains the root capability in its capture set */ - def disallowRootCapabilitiesIn(tp: Type, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = + 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) = - 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) + t 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(tp, ann) if ann.symbol == defn.UncheckedCapturesAnnot => + () + case _ => + 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) /** Attachment key for bodies of closures, provided they are values */ @@ -261,7 +289,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** 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(sym.owner) @@ -334,8 +362,7 @@ class CheckCaptures extends Recheck, SymTransformer: includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - instantiateLocalRoots(tree.symbol, NoPrefix, pt, tree.srcPos): - super.recheckIdent(tree, pt) + super.recheckIdent(tree, pt) /** A specialized implementation of the selection rule. * @@ -364,53 +391,25 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet - instantiateLocalRoots(tree.symbol, qualType, pt, tree.srcPos): - if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then - selType + 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") + if qualCs.mightSubcapture(selCs) + && !selCs.mightSubcapture(qualCs) + && !pt.stripCapturing.isInstanceOf[SingletonType] + then + selType.widen.stripCapturing.capturing(qualCs) + .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) else - val qualCs = qualType.captureSet - capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") - if qualCs.mightSubcapture(selCs) - && !selCs.mightSubcapture(qualCs) - && !pt.stripCapturing.isInstanceOf[SingletonType] - then - selType.widen.stripCapturing.capturing(qualCs) - .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) - else - selType + selType }//.showing(i"recheck sel $tree, $qualType = $result") - /** Instantiate local roots of `sym` in type `tp` to root variables, provided - * - `sym` is a level owner, and - * - `tp` is the type of a function that gets applied, either as a method - * or as a function value that gets applied. - */ - def instantiateLocalRoots(sym: Symbol, pre: Type, pt: Type, pos: SrcPos)(tp: Type)(using Context): Type = - def canInstantiate = - sym.is(Method, butNot = Accessor) - || sym.isTerm && defn.isFunctionType(sym.info) && pt == AnySelectionProto - if canInstantiate then - val tpw = tp.widen - var tp1 = tpw - val rootVar = CaptureRoot.Var(ctx.owner, sym) - if sym.isLevelOwner then - val outerOwner = sym.skipConstructor.owner.levelOwner - if outerOwner.isClass then - val outerRoot = outerOwner.localRoot.termRef - outerRoot.asSeenFrom(pre, sym.owner) match - case outerLimit: CaptureRoot if outerLimit ne outerRoot => - capt.println(i"constraining $rootVar of $sym by $outerLimit") - if !outerLimit.encloses(rootVar) then - // Should this be an assertion failure instead? - report.error(em"outer instance $outerLimit does not enclose local root $rootVar", pos) - case _ => - tp1 = mapRoots(sym.localRoot.termRef, rootVar)(tp1) - if tp1 ne tpw then - ccSetup.println(i"INST local $sym: $tp, ${sym.localRoot} = $tp1") - if tpw eq tp1 then tp else tp1 - else - tp - /** A specialized implementation of the apply rule. * * E |- f: Ra ->Cf Rr^Cr @@ -435,7 +434,7 @@ class CheckCaptures extends Recheck, SymTransformer: if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked - val argType0 = recheck(arg, pt.capturing(CaptureSet(CaptureRoot.Var(ctx.owner, meth)))) + val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) val argType = if argType0.captureSet.isAlwaysEmpty then argType0 else argType0.widen.stripCapturing @@ -693,8 +692,8 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then - disallowRootCapabilitiesIn(tp, - "Result of `try`", "have type", + 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 @@ -781,20 +780,29 @@ class CheckCaptures extends Recheck, SymTransformer: // - Instantiate `cap` in actual as needed to a local root. 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) && t.symbol.isTrackedSomewhere => + 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(defn.RootClass, tree).localRoot + acc(NoSymbol, tree).orElse(ctx.owner).localRoot def adaptUniversal(actual: Type, expected: Type, tree: Tree)(using Context): Type = if expected.captureSet.disallowsUniversal && actual.captureSet.isUniversal then @@ -803,9 +811,11 @@ class CheckCaptures extends Recheck, SymTransformer: actual.stripCapturing, localRoot.termRef.singletonCaptureSet, actual.isBoxedCapturing) - .showing(i"adapt universal $actual vs $expected = $result") + .showing(i"adapt universal $actual vs $expected = $result", capt) else actual + 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 @@ -813,25 +823,20 @@ class CheckCaptures extends Recheck, SymTransformer: * 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): Unit = + override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type = val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) val actualBoxed = adaptBoxed(actual, expected1, tree.srcPos) //println(i"check conforms $actualBoxed <<< $expected1") - var ok = isCompatible(actualBoxed, expected1) - if !ok then stripTyped(tree) match - case tree: RefTree if !ctx.owner.isContainedIn(tree.symbol.levelOwner) => - // 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 actualWide = actual.widen - val actualInst = mapRoots(tree.symbol.localRoot.termRef, CaptureRoot.Var(ctx.owner, tree.symbol))(actualWide) - capt.println(i"fallBack from $actualWide to $actualInst to match $expected1") - ok = (actualInst ne actualWide) - && isCompatible(adaptBoxed(actualInst, expected1, tree.srcPos), expected1) - case _ => - if !ok then + 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. */ @@ -1003,7 +1008,8 @@ class CheckCaptures extends Recheck, SymTransformer: // 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` @@ -1032,7 +1038,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 diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 434c59d0abd3..79a8b125bb26 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -361,14 +361,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: recur(t) end expandAliases - val tp1 = expandAliases(tp) - val tp2 = - if rootTarget.exists - then mapRoots(defn.captureRoot.termRef, rootTarget.localRoot.termRef)(tp1) - .showing(i"map roots $tp1, ${tp1.getClass} == $result", capt) - else tp1 - if tp2 ne tp then ccSetup.println(i"expanded in ${ctx.owner}: $tp --> $tp1 --> $tp2") - tp2 + val tp1 = expandAliases(tp) // TODO: Do we still need to follow aliases? + if tp1 ne tp then ccSetup.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 */ @@ -431,11 +426,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: - def transformResultType(tpt: TypeTree, sym: Symbol)(using Context) = + def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = transformTT(tpt, - boxed = sym.is(Mutable, butNot = Method), // types of mutable variables are boxed - exact = sym.allOverriddenSymbols.hasNext, // types of symbols that override a parent don't get a capture set TODO drop + boxed = + !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 rootTarget = ctx.owner) + 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 @@ -464,15 +470,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val sym = tree.symbol val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): - tree.rhs match - case possiblyTypedClosureDef(ddef) - if !mentionsCap(rhsOfEtaExpansion(ddef)) - && !sym.is(Mutable) - && ddef.symbol.takesCappedParamIn(ddef.symbol.info) => - ccSetup.println(i"Level owner at setup $sym / ${ddef.symbol.info}") - ccState.isLevelOwner(sym) = true - case _ => transformResultType(tpt, sym) + if sym.is(Mutable) && !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then + CheckCaptures.disallowRootCapabilitiesIn(tpt.knownType, sym, + i"mutable $sym", "have type", "", sym.srcPos) ccSetup.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) @@ -486,7 +487,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else "" - CheckCaptures.disallowRootCapabilitiesIn(arg.knownType, + CheckCaptures.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) @@ -754,11 +755,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: */ private def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = capt.println(i"checkWF post $parent ${ann.retainedElems} in $tpt") - val normCap = new TypeMap: - def apply(t: Type): Type = t match - case t: TermRef if t.isGenericRootCapability => ctx.owner.localRoot.termRef - case _ => t - var retained = ann.retainedElems.toArray for i <- 0 until retained.length do val refTree = retained(i) @@ -770,8 +766,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else tpt.srcPos def check(others: CaptureSet, dom: Type | CaptureSet): Unit = - val remaining = others.map(normCap) - if remaining.accountsFor(ref) then + if others.accountsFor(ref) then report.warning(em"redundant capture: $dom already accounts for $ref", pos) if ref.captureSetOfInfo.elems.isEmpty then diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index c84467160b50..352c5170bef0 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -62,8 +62,6 @@ object Synthetics: */ def transform(symd: SymDenotation, info: Type)(using Context): SymDenotation = - def localRootSet = symd.symbol.localRoot.termRef.singletonCaptureSet - /** 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: * case class CC(a: A^{d}, b: B, c: C^{cap}) @@ -118,7 +116,7 @@ object Synthetics: def transformUnapplyCaptures(info: Type)(using Context): Type = info match case info: MethodType => val paramInfo :: Nil = info.paramInfos: @unchecked - val newParamInfo = CapturingType(paramInfo, localRootSet) + val newParamInfo = CapturingType(paramInfo, CaptureSet.universal) val trackedParam = info.paramRefs.head def newResult(tp: Type): Type = tp match case tp: MethodOrPoly => @@ -135,7 +133,7 @@ object Synthetics: val (mt: MethodType) = pt.resType: @unchecked val (enclThis: ThisType) = owner.thisType: @unchecked pt.derivedLambdaType(resType = MethodType(mt.paramNames)( - mt1 => mt.paramInfos.map(_.capturing(localRootSet)), + mt1 => mt.paramInfos.map(_.capturing(CaptureSet.universal)), mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head)))) def transformCurriedTupledCaptures(info: Type, owner: Symbol) = @@ -150,18 +148,16 @@ object Synthetics: ExprType(mapFinalResult(et.resType, CapturingType(_, CaptureSet(enclThis)))) def transformCompareCaptures = - MethodType(defn.ObjectType.capturing(localRootSet) :: Nil, defn.BooleanType) + MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType) symd.copySymDenotation(info = symd.name match case DefaultGetterName(nme.copy, n) => transformDefaultGetterCaptures(info, symd.owner, n) case nme.unapply => - ccState.isLevelOwner(symd.symbol) = true transformUnapplyCaptures(info) case nme.apply | nme.copy => addCaptureDeps(info) case nme.andThen | nme.compose => - ccState.isLevelOwner(symd.symbol) = true transformComposeCaptures(info, symd.owner) case nme.curried | nme.tupled => transformCurriedTupledCaptures(info, symd.owner) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index aa8933e19c39..7568cdf92f3e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2114,15 +2114,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling ExprType(info1.resType) case info1 => info1 - if ccConfig.oldRefiningVars && isCaptureCheckingOrSetup 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.levelLimit.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)) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9eb18629b730..50d0dde0e056 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -93,15 +93,6 @@ object TypeOps: } } - def mapLocalRoot(tp: TermRef): Type = - if tp.symbol.owner.isLocalDummy then - val pre1 = toPrefix(pre, cls, tp.localRootOwner.asClass) - if pre1 ne tp then pre1 match - case pre1: ThisType if pre1.cls.isLevelOwner => pre1.cls.localRoot.termRef - case _ => pre1.captureSet.impliedRoot(tp) - else tp - else tp - trace.conditionally(track, s"asSeen ${tp.show} from (${pre.show}, ${cls.show})", show = true) { // !!! DEBUG // All cases except for ThisType are the same as in Map. Inlined for performance // TODO: generalize the inlining trick? @@ -109,10 +100,7 @@ object TypeOps: case tp: NamedType => val sym = tp.symbol if sym.isStatic && !sym.maybeOwner.seesOpaques then tp - else if tp.prefix `eq` NoPrefix then - if tp.name == nme.LOCAL_CAPTURE_ROOT - then mapLocalRoot(tp.asInstanceOf[TermRef]) - else tp + else if 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 1d45297d3925..25fcc4148cbc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2178,7 +2178,8 @@ 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 diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 8fbfddf3581c..a7ecba54de19 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -340,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 @@ -531,9 +531,9 @@ 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 = def op = @@ -572,10 +572,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 @@ -587,17 +586,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 closureDef(_) => - 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/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 051c75522003..57629b9ad24c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -708,6 +708,9 @@ object ProtoTypes { @sharable object AnyTypeConstructorProto extends UncachedGroundType with MatchAlways: override def toString = "AnyTypeConstructorProto" + @sharable object LhsProto extends UncachedGroundType with MatchAlways: + override def toString = "LhsProto" + extension (pt: Type) def isExtensionApplyProto: Boolean = pt match case PolyProto(targs, res) => res.isExtensionApplyProto diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 4b418f779f95..54e75216367c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -20,7 +20,7 @@ import config.SourceVersion.{`3.0`, `future`} import config.Printers.refcheck import reporting._ import Constants.Constant -import cc.{mapRoots, localRoot, isCaptureChecking, isLevelOwner} +import cc.{localRoot, isCaptureChecking, isLevelOwner} object RefChecks { import tpd._ @@ -105,9 +105,6 @@ object RefChecks { def checkSelfConforms(other: ClassSymbol) = var otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType) - if isCaptureChecking 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), @@ -376,10 +373,7 @@ object RefChecks { if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) else self.memberInfo(member) def otherTp(self: Type) = - val info = self.memberInfo(other) - if isCaptureChecking && member.isLevelOwner && other.isLevelOwner - then info.substSym(other.localRoot :: Nil, member.localRoot :: Nil) - else info + self.memberInfo(other) refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") diff --git a/tests/neg-custom-args/captures/box-unsoundness.scala b/tests/neg-custom-args/captures/box-unsoundness.scala.disabled similarity index 90% rename from tests/neg-custom-args/captures/box-unsoundness.scala rename to tests/neg-custom-args/captures/box-unsoundness.scala.disabled index e9436b7236cc..213409365d46 100644 --- a/tests/neg-custom-args/captures/box-unsoundness.scala +++ b/tests/neg-custom-args/captures/box-unsoundness.scala.disabled @@ -1,6 +1,6 @@ @annotation.capability class CanIO { def use(): Unit = () } def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x) def test(io: CanIO): Unit = - val f = use[CanIO](io) + val f = use[CanIO](io) // val g: () -> Unit = () => f(x => x.use()) // error // was UNSOUND: g uses the capability io but has an empty capture set \ No newline at end of file diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 781b26b6944d..61b83fc24688 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ - | reference (cap2 : Cap^{cap[test2]}) is not included in the allowed capture set {cap1} + | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ?->{cap1} I 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/capt1.check b/tests/neg-custom-args/captures/capt1.check index 8e56f9515dbf..124bd61a0109 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:4:11 -------------------------------------------------------------- 4 | () => if x == null then y else y // error | ^ - | (x : C^{cap[f]}) cannot be referenced here; it is not included in the allowed capture set {} + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C -- Error: tests/neg-custom-args/captures/capt1.scala:7:11 -------------------------------------------------------------- 7 | () => if x == null then y else y // error | ^ - | (x : C^{cap[g]}) cannot be referenced here; it is not included in the allowed capture set {} + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:14:2 ----------------------------------------- 14 | def f(y: Int) = if x == null then y else y // error @@ -36,5 +36,5 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:32:30 ------------------------------------------------------------- 32 | val z2 = h[() -> Cap](() => x) // error | ^ - | (x : C^{cap[]}) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^{cap[]} + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^ diff --git a/tests/neg-custom-args/captures/cc-this.check b/tests/neg-custom-args/captures/cc-this.check index 798dd81bf6e1..335302c5c259 100644 --- a/tests/neg-custom-args/captures/cc-this.check +++ b/tests/neg-custom-args/captures/cc-this.check @@ -8,8 +8,8 @@ -- Error: tests/neg-custom-args/captures/cc-this.scala:10:15 ----------------------------------------------------------- 10 | class C2(val x: () => Int): // error | ^ - |reference (C2.this.x : () ->{cap[C2]} Int) is not included in the allowed capture set {} of the self type of class C2 + | reference (C2.this.x : () => Int) is not included in the allowed capture set {} of the self type of class C2 -- Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ------------------------------------------------------------ 17 | class C4(val f: () => Int) extends C3 // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |reference (C4.this.f : () ->{cap[C4]} Int) is not included in the allowed capture set {} of pure base class class C3 + | reference (C4.this.f : () => Int) is not included in the allowed capture set {} of pure base class class C3 diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index 14c4c76370a6..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[D] : 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-this3.check b/tests/neg-custom-args/captures/cc-this3.check index 2ab46bb677a0..d57471c6872e 100644 --- a/tests/neg-custom-args/captures/cc-this3.check +++ b/tests/neg-custom-args/captures/cc-this3.check @@ -1,7 +1,7 @@ -- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this3.scala:8:6 --------------------------------------- 8 |class B extends A: // error | ^ - | illegal inheritance: self type B^{cap[B]} of class B does not conform to self type A^{} + | illegal inheritance: self type B^ of class B does not conform to self type A^{} | of parent class A | | longer explanation available when compiling with `-explain` 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 b7103511cb3a..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 : () ->{cap[foo]} Unit) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box () ->? Unit + | (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 6b676bc59539..16d623e64f7c 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,13 +1,13 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:2:6 ----------------------------------------------- 2 |class Err extends Exception: // error |^ - |reference (cap[Err] : 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 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |reference (Err3.this.c : Any^{cap[Err3]}) is not included in the allowed capture set {} of pure base class class Throwable + | reference (Err3.this.c : Any^) is not included in the allowed capture set {} of pure base class class Throwable diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index 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 291109b4a6f3..8987614eec38 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -6,7 +6,7 @@ def localCap[sealed T](op: (c: Capp^{cap}) => T): T = ??? def main(io: Capp^{cap}, net: Capp^{cap}): Unit = { - val test1 = localCap { c => // should be error + val test1 = localCap { c => // error () => { c.use() } } 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 3a41fdae73ec..df05324866e1 100644 --- a/tests/neg-custom-args/captures/i15116.check +++ b/tests/neg-custom-args/captures/i15116.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:3:13 ---------------------------------------- 3 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m²: (Bar.this.m : String^{cap[Bar]})}^{Bar.this.m} + | Found: Foo{val m²: (Bar.this.m : String^)}^{Bar.this.m} | Required: Foo | | where: m is a value in class Bar @@ -11,7 +11,7 @@ | 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^{cap[Bar]})}^{Bar.this.m} + | 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` @@ -31,7 +31,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:7:13 ---------------------------------------- 7 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m²: (Bar1.this.m : String^{cap[Bar1]})}^{Bar1.this.m} + | Found: Foo{val m²: (Bar1.this.m : String^)}^{Bar1.this.m} | Required: Foo | | where: m is a value in class Bar1 @@ -41,7 +41,7 @@ | 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^{cap[Bar1]})}^{Bar1.this.m} + | 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` diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index e6c2dff87dc7..03c5173c4326 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,32 +1,32 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ 19 | val c : C^{x} = new C(x) // error | ^ - | (x : C^{cap[main1]}) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error | ^^^^^^^ - | Found: (C{val arg: C^{cap[main1]}}^{c} ->{'cap[main1..boxed1](from instantiating box1), c} Unit) ->{c} Unit - | Required: (C^{cap[boxed1]} ->{cap[boxed1]} 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 ------------------------------------------------------------ 26 | val c : C^{x} = new C(x) // error | ^ - | (x : C^{cap[main2]}) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int + | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ - | Found: (C{val arg: C^{cap[main2]}}^{c} ->{'cap[main2..boxed2](from instantiating box2), c} Unit) ->{c} Unit - | Required: (C^{cap[boxed2]} ->{cap[boxed2]} 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:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ - | Found: (C{val arg: C^{cap[main3]}}^{cap[main3]} ->{cap[main3]} Unit) ->{cap[main3]} Unit - | Required: (C ->{cap[boxed2]} Unit) ->{cap[boxed2]} Unit + | Found: (C{val arg: C^}^ => Unit) ->? Unit + | Required: (C => Unit) => Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/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 29a667c038c5..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: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} - | 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[]} 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 702f8e6260b9..3095c1f2f4f9 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,10 +1,11 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:37:4 -------------------------- -37 | tabulate(10) { i => // error - | ^ - | Found: LazyList[Int]^{cap[]} - | Required: LazyList[Int]^? +-- 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 70a94edcd31a..13b1da6eaf1c 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -25,11 +25,11 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(f : A ->{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 | ^ diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index c1ecdb141bb3..8c91ec13b5d8 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -1,28 +1,28 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:19:28 -------------------------------------- 19 | val ref1c: LazyRef[Int] = ref1 // error | ^^^^ - | Found: LazyRef[Int]{val elem: () ->{cap1} Int}^{ref1} + | Found: (ref1 : LazyRef[Int]{val elem: () ->{cap1} Int}^{cap1}) | Required: LazyRef[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:21:35 -------------------------------------- 21 | val ref2c: LazyRef[Int]^{cap2} = ref2 // error | ^^^^ - | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref2} + | Found: (ref2 : LazyRef[Int]{val elem: () => Int}^{cap2, ref1}) | Required: LazyRef[Int]^{cap2} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:23:35 -------------------------------------- 23 | val ref3c: LazyRef[Int]^{ref1} = ref3 // error | ^^^^ - | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref3} + | Found: (ref3 : LazyRef[Int]{val elem: () => Int}^{cap2, ref1}) | Required: LazyRef[Int]^{ref1} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazyref.scala:25:35 -------------------------------------- 25 | val ref4c: LazyRef[Int]^{cap1} = ref4 // error | ^^^^ - | Found: LazyRef[Int]{val elem: () ->{cap[test]} Int}^{ref4} + | Found: (ref4 : LazyRef[Int]{val elem: () => Int}^{cap2, cap1}) | Required: LazyRef[Int]^{cap1} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index a13c15c7c7fd..979eed9b3098 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,4 +1,4 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:13:20 ---------------------------------------------------- 13 | () => () => io // error | ^^ - |(io : Cap^{cap[]}) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure + |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index 6ace4e09dfcc..b5b0384c82d7 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,10 +1,12 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:14:11 --------------------------------------- -14 | r.setV(g) // error - | ^ - | Found: box (x: String) ->{cap3} String - | Required: box (x$0: String) ->? String - | - | Note that reference (cap3 : CC^{cap[scope]}), defined in method scope - | cannot be included in outer capture set ? which is associated with method test - | - | longer explanation available when compiling with `-explain` +-- 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. diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 47ad8e3c50db..5e1687670547 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,32 +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(cap3: CC^) = def g(x: String): String = if cap3 == cap3 then "" else "a" - r.setV(g) // error + r.setV(g) // should be 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 5e69a8a48f1f..f5227bfef96b 100644 --- a/tests/neg-custom-args/captures/localcaps.scala +++ b/tests/neg-custom-args/captures/localcaps.scala @@ -4,6 +4,6 @@ class C: def x: C^{cap[d]} = ??? // error def y: C^{cap[C]} = ??? // ok - private val z = (c0: caps.Cap) => (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/outer-var.check b/tests/neg-custom-args/captures/outer-var.check new file mode 100644 index 000000000000..8231a04c503c --- /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 : () => Unit), 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..a680af68198f --- /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) = + var x: () ->{cap[test]} Unit = p + var y = p + + 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 index 6054d94c278e..38712469879f 100644 --- a/tests/neg-custom-args/captures/pairs.check +++ b/tests/neg-custom-args/captures/pairs.check @@ -1,28 +1,14 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:15:31 ---------------------------------------- -15 | val x1c: Cap^ ->{c} Unit = x1 // error - | ^^ - | Found: (x$0: Cap^{cap[test]}) ->{x1} Unit - | Required: Cap^{cap[x1c]} ->{c} Unit - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:17:30 ---------------------------------------- -17 | val y1c: Cap ->{d} Unit = y1 // error - | ^^ - | Found: (x$0: Cap^{cap[test]}) ->{y1} Unit - | Required: Cap^{cap[y1c]} ->{d} Unit - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/pairs.scala:34:30 ---------------------------------------- -34 | val x1c: Cap ->{c} Unit = x1 // error +-- [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^{cap[test]}) ->{x1} Unit - | Required: Cap^{cap[x1c]} ->{c} Unit + | 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:36:30 ---------------------------------------- -36 | val y1c: Cap ->{d} Unit = y1 // error +-- [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^{cap[test]}) ->{y1} Unit - | Required: Cap^{cap[y1c]} ->{d} Unit + | 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 index c78772eb7438..4fc495d60f95 100644 --- a/tests/neg-custom-args/captures/pairs.scala +++ b/tests/neg-custom-args/captures/pairs.scala @@ -1,31 +1,11 @@ @annotation.capability class Cap -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 // error - val y1 = p.snd - val y1c: Cap ->{d} Unit = y1 // error - 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 () diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 12a4cbd62c63..65419793d010 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -4,25 +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: () ->{cap[]} Unit - | Required: () ->? Unit - | - | 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: () ->{cap[]} Cell[Unit]^? - | Required: () ->? Cell[Unit]^? - | - | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/real-try.scala:31:21 ---------------------------------------------------------- -31 | Cell(() => foo(1))// // error - | ^ - |(canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}) cannot be referenced here; it is not included in the allowed capture set ? - |of an enclosing function literal with expected type box () ->? Unit - | - |Note that reference (canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[]}), defined in method - |cannot be included in outer capture set ? which is associated with package +-- 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 100% rename from tests/neg-custom-args/captures/refs.scala rename to tests/neg-custom-args/captures/refs.scala.disabled 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 3171f6f72612..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[]} + | 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/try.check b/tests/neg-custom-args/captures/try.check index 9bea04c527ed..c76c753487c7 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,15 +1,15 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:24:32 ------------------------------------------ -24 | (x: CanThrow[Exception]) => x // error - | ^ - | Found: (x : CT[Exception]^{lcap}) - | Required: CT[Exception]^? - | - | 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 : CT[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]) => @@ -18,8 +18,11 @@ 51 | 22 52 |} { // error | ^ - | Found: () ->{x$0, lcap} Int + | Found: () ->{x$0, x$0²} Int | Required: () -> Int + | + | where: x$0 is a reference to a value parameter + | x$0² is a reference to a value parameter 53 | (ex: Exception) => () => 22 54 |} | @@ -27,4 +30,10 @@ -- Error: tests/neg-custom-args/captures/try.scala:35:11 --------------------------------------------------------------- 35 | val xx = handle { // error | ^^^^^^ - | escaping local reference lcap.type + | reference (caps.cap : caps.Cap) is not included in the allowed capture set {x$0, x$0²} + | + | Note that the universal capability `cap` + | cannot be included in capture set {x$0, x$0} + | + | 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/try.scala b/tests/neg-custom-args/captures/try.scala index 71db5d8ed7b1..0d593b67f77b 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.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]^{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]] { - (x: CanThrow[Exception]) => x // error + val a = handle[Exception, CanThrow[Exception]] { // error + (x: CanThrow[Exception]) => x }{ (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 433172663640..34b4feb97a8a 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,8 +1,8 @@ -- 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 | ^^^^^^^^^^^^^^^^^^^^^^ @@ -13,11 +13,23 @@ -- Error: tests/neg-custom-args/captures/usingLogFile.scala:23:14 ------------------------------------------------------ 23 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^ - | escaping local reference local.type + | reference (caps.cap : caps.Cap) is not included in the allowed capture set {x$0, x$0²} + | + | Note that the universal capability `cap` + | cannot be included in capture set {x$0, x$0} + | + | where: x$0 is a reference to a value parameter + | x$0² is a reference to a value parameter -- 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 + | reference (caps.cap : caps.Cap) is not included in the allowed capture set {x$0, x$0²} + | + | Note that the universal capability `cap` + | cannot be included in capture set {x$0, x$0} + | + | where: x$0 is a reference to a value parameter + | x$0² is a reference to a value parameter -- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:14 ------------------------------------------------------ 47 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^ @@ -32,4 +44,4 @@ | reference (_$1 : java.io.OutputStream^{local}) is not included in the allowed capture set {x$0, local} | | Note that reference (_$1 : java.io.OutputStream^{local}), defined in method $anonfun - | cannot be included in outer capture set {x$0, local} which is associated with package + | cannot be included in outer capture set {x$0, local} which is associated with method test diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index a983231ff51a..9fefc25512d1 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,11 +28,11 @@ 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() 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..00ee8d59a410 --- /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$0: 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 7447d65ef026..84eadb00d7a7 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,30 +1,34 @@ --- [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:22:14 -------------------------------------------------------------- 22 | a = x => g(x) // error | ^^^^ - | reference (cap3 : CC^{cap[scope]}) is not included in the allowed capture set {cap[test]} - | of an enclosing function literal with expected type box String ->{cap[test]} String + | reference (cap3 : CC^) is not included in the allowed capture set {cap1} of variable a + | + | Note that reference (cap3 : CC^), 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 : CC^), 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:25:13 ----------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:12 ----------------------------------------- 25 | b = List(g) // error - | ^ - | Found: Seq[(x: String) ->{cap3} String] - | Required: Seq[box (x$0: String) ->{cap3} String] + | ^^^^^^^ + | Found: List[box (x$0: String) ->{cap3} String] + | Required: List[box String ->{cap[test]} String] | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/vars.scala:34:2 --------------------------------------------------------------- -34 | local { root => cap3 => // error +34 | local { cap3 => // error | ^^^^^ - | escaping local reference root.type + | reference (caps.cap : caps.Cap) is not included in the allowed capture set {x$0, x$0²} + | + | Note that the universal capability `cap` + | cannot be included in capture set {x$0, x$0} + | + | 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/vars.scala b/tests/neg-custom-args/captures/vars.scala index 8819a73d5bc8..ede287e26010 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -8,13 +8,13 @@ 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(cap3: Cap) = def g(x: String): String = if cap3 == cap3 then "" else "a" @@ -29,9 +29,9 @@ def test(cap1: Cap, cap2: Cap) = 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/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/outer-roots.scala b/tests/pos-custom-args/captures/outer-roots.scala.disabled similarity index 100% rename from tests/pos-custom-args/captures/outer-roots.scala rename to tests/pos-custom-args/captures/outer-roots.scala.disabled diff --git a/tests/pos-custom-args/captures/pairs.scala b/tests/pos-custom-args/captures/pairs.scala index c8c169a1041c..e15a76970c29 100644 --- a/tests/pos-custom-args/captures/pairs.scala +++ b/tests/pos-custom-args/captures/pairs.scala @@ -13,41 +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^{cap[test]} ->{c} Unit = x1 + val x1c: Cap^ ->{c} Unit = x1 val y1 = p.snd - val y1c: Cap^{cap[test]} ->{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^{cap[test]} ->{c} Unit = x1 - val y1 = p.snd - val y1c: Cap^{cap[test]} ->{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^{cap[test]} ->{c} Unit = x1 - val y1 = p.snd - val y1c: Cap^{cap[test]} ->{d} Unit = y1 + val y1c: Cap^ ->{d} Unit = y1 diff --git a/tests/pos-custom-args/captures/refs.scala b/tests/pos-custom-args/captures/refs.scala.disabled similarity index 100% rename from tests/pos-custom-args/captures/refs.scala rename to tests/pos-custom-args/captures/refs.scala.disabled diff --git a/tests/pos-custom-args/captures/test.scala b/tests/pos-custom-args/captures/test.scala index 974e7673aa86..0664b2172198 100644 --- a/tests/pos-custom-args/captures/test.scala +++ b/tests/pos-custom-args/captures/test.scala @@ -3,10 +3,10 @@ type Cap = C^ type Proc = () => Unit -class Ref(p: () => Unit): - private var x: () => Unit = p - def set(x: () ->{cap[Ref]} Unit): Unit = this.x = x - def get: () => Unit = x +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 = ??? 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/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 */ From 32f240d743913834ecf40af5915a68f6695fff5e Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 15:02:14 +0200 Subject: [PATCH 063/117] Drop CaptureRoot and AddTryOwners --- .../dotty/tools/dotc/cc/AddTryOwners.scala | 46 ------ .../src/dotty/tools/dotc/cc/CaptureOps.scala | 8 - .../src/dotty/tools/dotc/cc/CaptureRoot.scala | 142 ------------------ .../src/dotty/tools/dotc/cc/CaptureSet.scala | 2 - .../dotty/tools/dotc/cc/CheckCaptures.scala | 21 +-- .../src/dotty/tools/dotc/core/Types.scala | 3 +- .../tools/dotc/printing/PlainPrinter.scala | 36 +---- 7 files changed, 12 insertions(+), 246 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala delete mode 100644 compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala diff --git a/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala b/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala deleted file mode 100644 index b79bd2a9ccaa..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/AddTryOwners.scala +++ /dev/null @@ -1,46 +0,0 @@ -package dotty.tools.dotc -package cc - -import core.* -import DenotTransformers.IdentityDenotTransformer -import Phases.Phase -import Contexts.*, Symbols.*, Flags.*, Types.* -import config.Feature -import ast.tpd -import StdNames.nme -import Decorators.i - -object AddTryOwners: - val name: String = "addTryOwners" - val description: String = "add symbols for try blocks in preparation of capture checking" - -class AddTryOwners extends Phase, IdentityDenotTransformer: - thisPhase => - - import tpd.* - - override def phaseName: String = AddTryOwners.name - override def description: String = AddTryOwners.description - - override def isRunnable(using Context) = - super.isRunnable && Feature.ccEnabledSomewhere - - override def isCheckable = false - - def run(using Context): Unit = - val addTryOwners = new TreeTraverserWithPreciseImportContexts: - def traverse(tree: Tree)(using Context): Unit = tree match - case tree @ Try(expr, cases, finalizer) if Feature.enabled(Feature.saferExceptions) => - val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType)) - ccState.tryBlockOwner(tree) = tryOwner - ccState.isLevelOwner(tryOwner) = true - expr.changeOwnerAfter(ctx.owner, tryOwner, thisPhase) - inContext(ctx.withOwner(tryOwner)): - traverse(expr) - traverse(cases) - traverse(finalizer) - case _ => - traverseChildren(tree) - addTryOwners.traverse(ctx.compilationUnit.tpdTree) -end AddTryOwners - diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3db6b23cf983..f7cf913afb50 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -61,9 +61,6 @@ class IllegalCaptureRef(tpe: Type) extends Exception(tpe.toString) /** Capture checking state, which is known to other capture checking components */ class CCState: - /** Cache for level ownership */ - val isLevelOwner: mutable.HashMap[Symbol, Boolean] = new mutable.HashMap - /** Associates nesting level owners with the local roots valid in their scopes. */ val localRoots: mutable.HashMap[Symbol, Symbol] = new mutable.HashMap @@ -72,11 +69,6 @@ class CCState: */ 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 /** The currently valid CCState */ 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 0e900c74a717..000000000000 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRoot.scala +++ /dev/null @@ -1,142 +0,0 @@ -package dotty.tools -package dotc -package cc - -import core.* -import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* -import config.Printers.capt -import Hashable.Binders -import printing.Showable -import util.SimpleIdentitySet -import Decorators.* -import StdNames.nme -import scala.annotation.constructorOnly -import scala.annotation.internal.sharable - -type CaptureRoot = TermRef | CaptureRoot.Var - -object CaptureRoot: - - @sharable private var nextId = 0 - - case class Var(owner: Symbol, source: Symbol)(using @constructorOnly ictx: Context) extends CaptureRef, Showable: - - val id = - nextId += 1 - nextId - - var innerLimit: Symbol = owner.levelOwner - var outerLimit: Symbol = defn.RootClass - var outerRoots: SimpleIdentitySet[Var] = SimpleIdentitySet.empty - - override def isTrackableRef(using Context): Boolean = true - override def captureSetOfInfo(using Context) = CaptureSet.universal - - private var myAlias: CaptureRoot = this - def alias = myAlias - def alias_=(r: CaptureRoot)(using Context) = - //assert(id != 2, i"$this := $r") - alias match - case alias: TermRef => - val owner = alias.localRootOwner - assert( - owner.isContainedIn(outerLimit) && innerLimit.isContainedIn(owner), - i"illegal alias $owner for $this") - case _ => - myAlias = r - - /** A fresh var with the same limits and outerRoots as this one */ - def fresh(using Context): Var = - val r = Var(owner, NoSymbol) - r.innerLimit = innerLimit - r.outerLimit = outerLimit - r.outerRoots = outerRoots - r - - /** A fresh var that is enclosed by all roots in `rs`. - * @throws A NoCommonRoot exception if this is not possible - * since root scopes dont' overlap. - */ - def freshEnclosedBy(rs: CaptureRoot*)(using Context): CaptureRoot = - val r = fresh - if rs.forall(_.encloses(r)) then r else throw CaptureRoot.NoCommonRoot(rs*) - - def computeHash(bs: Binders): Int = hash - def hash: Int = System.identityHashCode(this) - def underlying(using Context): Type = defn.Caps_Cap.typeRef - end Var - - class NoCommonRoot(rs: CaptureRoot*)(using Context) extends Exception( - i"No common capture root nested in ${rs.mkString(" and ")}" - ) - - extension (r: CaptureRoot) - - def followAlias(using Context): CaptureRoot = r match - case r: Var if r.alias ne r => r.alias.followAlias - case _ => r - - def unifiesWith(other: CaptureRoot)(using Context): Boolean = - r.encloses(other) && other.encloses(r) - - def encloses(other: CaptureRoot)(using Context): Boolean = - val (r1, r2) = (followAlias, other.followAlias) - (r1 eq r2) || (r1, r2).match - case (r1: TermRef, r2: TermRef) => - r2.localRootOwner.isContainedIn(r1.localRootOwner) - case (r1: TermRef, r2: Var) => - val r1Owner = r1.localRootOwner - if r2.outerLimit.isContainedIn(r1Owner) then true - else if !r2.innerLimit.isContainedIn(r1Owner) then false - else - if r2.innerLimit == r1Owner then r2.alias = r1 - else r2.outerLimit = r1Owner - true - case (r1: Var, r2: TermRef) => - val r2Owner = r2.localRootOwner - if r2Owner.isContainedIn(r1.innerLimit) then true - else if !r2Owner.isContainedIn(r1.outerLimit) then false - else - if r1.outerLimit == r2Owner then r1.alias = r2 - else r1.innerLimit = r2Owner - true - case (r1: Var, r2: Var) => - if r2.outerRoots.contains(r1) then true // no change - else if !r2.innerLimit.isContainedIn(r1.outerLimit) then false // no overlap - else if r1.outerRoots.contains(r2) then // unify - r1.alias = r2 - r2.outerLimit = - r1.outerLimit.maxNested(r2.outerLimit, - onConflict = (_, _) => throw CaptureRoot.NoCommonRoot(r1, r2)) - r2.innerLimit = r1.innerLimit.minNested(r2.innerLimit) - true - else - r2.outerRoots += r1 // set early to prevent infinite looping - if r1.outerRoots.forall(_.encloses(r2)) then true - else - r2.outerRoots -= r2 - false - end encloses - end extension - - /** The capture root enclosed by `root1` and `root2`. - * If one of these is a Var, create a fresh Var with the appropriate constraints. - * If the scopes of `root1` and `root2` don't overlap, thow a `NoCommonRoot` exception. - */ - def lub(root1: CaptureRoot, root2: CaptureRoot)(using Context): CaptureRoot = - val (r1, r2) = (root1.followAlias, root2.followAlias) - if r1 eq r2 then r1 - else (r1, r2) match - case (r1: TermRef, r2: TermRef) => - r1.localRootOwner.maxNested(r2.localRootOwner, - onConflict = (_, _) => throw CaptureRoot.NoCommonRoot(r1, r2) - ).termRef - case (r1: TermRef, r2: Var) => - r2.freshEnclosedBy(r1, r2) - case (r1: Var, r2) => - r1.freshEnclosedBy(r1, r2) - -end CaptureRoot - - - diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 42423f5247de..194c30733f1b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -536,8 +536,6 @@ object CaptureSet: levelLimit.isContainedIn(sym.levelOwner) case elem: ThisType => levelLimit.isContainedIn(elem.cls.levelOwner) - case elem: CaptureRoot.Var => - elem.encloses(levelLimit.localRoot.termRef) case _ => true def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 14541f669176..0f96a62e050a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -195,7 +195,7 @@ class CheckCaptures extends Recheck, SymTransformer: def phaseName: String = "cc" override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere - override def firstPrepPhase = preRecheckPhase.prev.asInstanceOf[AddTryOwners] + override def firstPrepPhase = preRecheckPhase def newRechecker()(using Context) = CaptureChecker(ctx) @@ -221,14 +221,6 @@ class CheckCaptures extends Recheck, SymTransformer: refs match case refs: CaptureSet.Var if variance < 0 => refs.solve() case _ => - for ref <- refs.elems do - ref match - case ref: CaptureRoot.Var => - ref.followAlias match - case rv: CaptureRoot.Var if rv.innerLimit == ctx.owner.levelOwner => - rv.alias = ctx.owner.localRoot.termRef - case _ => - case _ => traverse(parent) case t @ defn.RefinedFunctionOf(rinfo) => traverse(rinfo) @@ -683,14 +675,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) - val tp = try - inContext(ctx.withOwner(tryOwner)): - super.recheckTry(tree, pt) - finally - curEnv = saved + val tp = + try super.recheckTry(tree, pt) + finally curEnv = saved*/ + val tp = super.recheckTry(tree, pt) if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, "result of `try`", "have type", diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 25fcc4148cbc..9191dd45c4a3 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, RetainingType, CaptureRoot, isCaptureChecking} +import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, isCaptureChecking} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -2189,7 +2189,6 @@ object Types { */ def isLocalRootCapability(using Context): Boolean = this match case tp: TermRef => tp.localRootOwner.exists - case tp: CaptureRoot.Var => true case _ => false /** Is this reference the a (local or generic) root capability? */ diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 174454d24751..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, RetainingType, CaptureSet, CaptureRoot, isBoxed, levelOwner, retainedElems} +import cc.{CapturingType, RetainingType, CaptureSet, isBoxed, levelOwner, retainedElems} class PlainPrinter(_ctx: Context) extends Printer { @@ -48,12 +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`, - * For now it is better to turn this off, so that we get the best info for diagnosis. - * TODO: we should consider dropping this switch once we implemented disambiguation of capture roots. - */ - private val shortenCap = false - def homogenize(tp: Type): Type = if (homogenizedView) tp match { @@ -253,16 +247,9 @@ class PlainPrinter(_ctx: Context) extends Printer { 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) @@ -405,9 +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[${nameString(tp.localRootOwner)}]") + Str(s"cap[${nameString(tp.localRootOwner)}]") else toTextPrefixOf(tp) ~ selectionString(tp) case tp: ThisType => nameString(tp.cls) + ".this" @@ -427,17 +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).provided(sym.exists) - "'cap[" - ~ toTextRef(tp.outerLimit.termRef).provided(!tp.outerLimit.isRoot) - ~ ".." - ~ toTextRef(tp.innerLimit.termRef) - ~ "]" - ~ ("(from instantiating " ~ nameString(tp.source) ~ ")").provided(tp.source.exists) } } From 16a22168c7b343e1d11ee2157477cd228c89e876 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 15:25:59 +0200 Subject: [PATCH 064/117] Drop redundant elements of Setup --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 12 ---- compiler/src/dotty/tools/dotc/cc/Setup.scala | 56 ++++++------------- 2 files changed, 16 insertions(+), 52 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f7cf913afb50..4ae50d5b3cd0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -79,18 +79,6 @@ class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( i"No common capture root nested in ${rs.mkString(" and ")}" ) -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) - extension (tree: Tree) /** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */ diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 79a8b125bb26..52f9f1ee5056 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -113,11 +113,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformSym(symd: SymDenotation)(using Context): SymDenotation = if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol - def rootTarget = - if sym.isAliasType && sym.isStatic then NoSymbol else sym def mappedInfo = if toBeUpdated.contains(sym) then symd.info - else transformExplicitType(symd.info, rootTarget) + else transformExplicitType(symd.info) // TODO if sym is class && level owner: add a capture root if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) @@ -261,7 +259,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def transformInferredType(tp: Type)(using Context): Type = mapInferred(refine = true)(tp) - private def transformExplicitType(tp: Type, rootTarget: Symbol, tptToCheck: Option[Tree] = None)(using Context): Type = + private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = val expandAliases = new DeepTypeMap: override def toString = "expand aliases" @@ -314,9 +312,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else t.info match case TypeAlias(alias) => val transformed = - if sym.isStatic then this(alias) - else transformExplicitType(alias, - if rootTarget.exists then sym else NoSymbol)( + if sym.isStatic || true then this(alias) + else transformExplicitType(alias)( using ctx.withOwner(sym.owner)) if transformed ne alias then transformed else t //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") @@ -367,13 +364,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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, rootTarget: Symbol)(using Context): Unit = + private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit = if !tree.hasRememberedType then val tp = if boxed then Box(tree.tpe) else tree.tpe tree.rememberType( if tree.isInstanceOf[InferredTypeTree] && !exact then transformInferredType(tp) - else transformExplicitType(tp, rootTarget, tptToCheck = Some(tree))) + else transformExplicitType(tp, tptToCheck = Some(tree))) /** Substitute parameter symbols in `from` to paramRefs in corresponding * method or poly types `to`. We use a single BiTypeMap to do everything. @@ -428,12 +425,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = transformTT(tpt, - boxed = - !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 - rootTarget = ctx.owner) + boxed = !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 + ) val addDescription = new TypeTraverser: def traverse(tp: Type) = tp match case tp @ CapturingType(parent, refs) => @@ -457,16 +453,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: //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(transformExplicitType(tree.tpe, rootTarget = NoSymbol)) - case _ => false - val sym = tree.symbol val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): @@ -480,7 +466,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ TypeApply(fn, args) => traverse(fn) for case arg: TypeTree <- args do - transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed + transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed if allowUniversalInBoxed then val polyType = fn.tpe.widen.asInstanceOf[TypeLambda] @@ -498,7 +484,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ SeqLiteral(elems, tpt: TypeTree) => traverse(elems) - transformTT(tpt, boxed = true, exact = false, rootTarget = ctx.owner) + transformTT(tpt, boxed = true, exact = false) case _ => traverseChildren(tree) @@ -507,11 +493,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => - val lowner = - transformTT(tree, boxed = false, exact = false, - rootTarget = if ctx.owner.isStaticOwner then NoSymbol else ctx.owner - // roots of other types in static locations are not mapped - ) + transformTT(tree, boxed = false, exact = false) case tree: ValOrDefDef => val sym = tree.symbol @@ -573,10 +555,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else SubstParams(prevPsymss, prevLambdas)(resType) if sym.exists && signatureChanges then - def absInfo(resType: Type): Type = - integrateRT(sym.info, sym.paramSymss, resType, Nil, Nil) + val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) .showing(i"update info $sym: ${sym.info} = $result", ccSetup) - val newInfo = absInfo(localReturnType) if newInfo ne sym.info then val updatedInfo = if sym.isAnonymousFunction @@ -608,13 +588,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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. val newSelfType = CapturingType(cinfo.selfType, CaptureSet.Var(cls)) val ps1 = inContext(ctx.withOwner(cls)): - ps.mapConserve(transformExplicitType(_, rootTarget = cls)) + ps.mapConserve(transformExplicitType(_)) val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) updateInfo(cls, newInfo) ccSetup.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo") From f7869793467142f044047909c618397b9cb988c3 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 15:55:16 +0200 Subject: [PATCH 065/117] Drop BoxedTypeCache --- .../dotty/tools/dotc/cc/BoxedTypeCache.scala | 19 ------------ .../src/dotty/tools/dotc/cc/CaptureOps.scala | 19 ++---------- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 31 ++++++------------- .../dotty/tools/dotc/cc/CapturingType.scala | 4 +-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 16 ++++------ compiler/src/dotty/tools/dotc/cc/Setup.scala | 7 ++--- .../src/dotty/tools/dotc/core/Types.scala | 6 ++-- 7 files changed, 25 insertions(+), 77 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/cc/BoxedTypeCache.scala 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/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 4ae50d5b3cd0..0648cf704228 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -15,7 +15,6 @@ import config.Feature import collection.mutable private val Captures: Key[CaptureSet] = Key() -private val BoxedType: Key[BoxedTypeCache] = Key() object ccConfig: @@ -25,14 +24,6 @@ object ccConfig: */ private[cc] val adaptUnpickledFunctionTypes = false - /** Switch whether we constrain a root var that includes the source of a - * root map to be an alias of that source (so that it can be mapped) - */ - private[cc] val constrainRootsWhenMapping = true - - /** Use old scheme for refining vars, which should be no longer necessary - */ - val oldRefiningVars = false end ccConfig def allowUniversalInBoxed(using Context) = @@ -132,7 +123,6 @@ extension (tp: Type) if (parent eq p) && (refs eq r) then tp else CapturingType(parent, refs, tp.isBoxed) - // TODO Move boxed/unboxed to CaapturingType? /** 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. @@ -140,13 +130,8 @@ 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 => - AnnotatedType(parent, ann.boxedAnnot) - case ann => - ann.tree.getAttachment(BoxedType) match // TODO drop - 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 _ => diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 194c30733f1b..10ca60821095 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -56,7 +56,7 @@ sealed abstract class CaptureSet extends Showable: */ def isAlwaysEmpty: Boolean - /** An optinal level limit, or NoSymbol if none exists. All elements of the set + /** An optional level limit, or NoSymbol if none exists. All elements of the set * must be in scopes visible from the level limit. */ def levelLimit: Symbol @@ -78,13 +78,11 @@ sealed abstract class CaptureSet extends Showable: /** Does this capture set contain the root reference `cap` as element? */ final def isUniversal(using Context) = - elems.exists(_.isGenericRootCapability) + elems.exists(_.isUniversalRootCapability) /** Does this capture set contain the root reference `cap` as element? */ final def containsRoot(using Context) = - elems.exists: - case ref: TermRef => ref.isRootCapability - case _ => false + elems.exists(_.isRootCapability) final def disallowsUniversal(using Context) = if isConst then !isUniversal && elems.exists(_.isLocalRootCapability) @@ -144,7 +142,7 @@ sealed abstract class CaptureSet extends Showable: || y.match case y: TermRef => y.prefix eq x case _ => false - || (x.isGenericRootCapability || y.isRootCapability && x.isRootCapability) + || (x.isUniversalRootCapability || y.isRootCapability && x.isRootCapability) && ctx.property(LooseRootChecking).isDefined /** x <:< cap, cap[x] <:< cap @@ -153,8 +151,8 @@ sealed abstract class CaptureSet extends Showable: */ private def isSuperRootOf(y: CaptureRef): Boolean = x match case x: TermRef => - if x.isGenericRootCapability then true - else if x.isLocalRootCapability && !y.isGenericRootCapability then + if x.isUniversalRootCapability then true + else if x.isLocalRootCapability && !y.isUniversalRootCapability then val xowner = x.localRootOwner y match case y: TermRef => @@ -287,11 +285,6 @@ sealed abstract class CaptureSet extends Showable: else Const(elems.filter(p)) else Filtered(asVar, p) - /** This set with a new owner and therefore also a new levelLimit */ - def changeOwner(newOwner: Symbol)(using Context): CaptureSet = - if this.isConst then this - else ChangedOwner(asVar, newOwner) - /** Capture set obtained by applying `tm` to all elements of the current capture set * and joining the results. If the current capture set is a variable, the same * transformation is applied to all future additions of new elements. @@ -517,7 +510,7 @@ object CaptureSet: else //assert(id != 19 || !elem.isLocalRootCapability, elem.asInstanceOf[TermRef].localRootOwner) elems += elem - if elem.isGenericRootCapability then rootAddedHandler() + if elem.isUniversalRootCapability then rootAddedHandler() newElemAddedHandler(elem) // assert(id != 5 || elems.size != 3, this) val res = (CompareResult.OK /: deps) { (r, dep) => @@ -527,7 +520,7 @@ object CaptureSet: res private def levelOK(elem: CaptureRef)(using Context): Boolean = - if elem.isGenericRootCapability then !noUniversal + if elem.isUniversalRootCapability then !noUniversal else !levelLimit.exists || elem.match case elem: TermRef => @@ -560,7 +553,7 @@ object CaptureSet: /** Roughly: the intersection of all constant known supersets of this set. * The aim is to find an as-good-as-possible constant set that is a superset - * of this set. The associated root set {cap[owner]} is a sound fallback. + * of this set. The universal set {cap} is a sound fallback. */ final def upperApprox(origin: CaptureSet)(using Context): CaptureSet = if isConst then this @@ -758,10 +751,6 @@ object CaptureSet: override def toString = s"BiMapped$id($source, elems = $elems)" end BiMapped - /** Same as `source` but with a new directOwner */ - class ChangedOwner private[CaptureSet](val source: Var, newOwner: Symbol)(using @constructorOnly ctx: Context) - extends DerivedVar(newOwner, source.elems) - /** 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) @@ -1087,7 +1076,7 @@ object CaptureSet: override def toAdd(using Context) = for CompareResult.LevelError(cs, ref) <- ccState.levelError.toList yield ccState.levelError = None - if ref.isGenericRootCapability then + if ref.isUniversalRootCapability then i""" | |Note that the universal capability `cap` diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 294414a6c37b..2dde12f705aa 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -28,7 +28,7 @@ object CapturingType: /** Smart constructor that * - drops empty capture sets - * - drops capability class ecpansion if they are refined with another capturing type + * - drops a capability class expansion if it is further refined with another capturing type * - fuses compatible capturiong 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. @@ -77,7 +77,7 @@ object CapturingType: None /** Check whether a type is uncachable when computing `baseType`. - * We avoid caching capturing types when IgnoreCaptures mode is set, since the + * We avoid caching capturing types when IgnoreCaptures mode is set. */ def isUncachable(tp: Type)(using Context): Boolean = ctx.mode.is(Mode.IgnoreCaptures) && decomposeCapturingType(tp).isDefined diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 0f96a62e050a..927116eace3f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -140,7 +140,10 @@ object CheckCaptures: case tpe => report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos) - /** Report an error if some part of `tp` contains the root capability in its capture set */ + /** 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 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 = @@ -195,7 +198,6 @@ class CheckCaptures extends Recheck, SymTransformer: def phaseName: String = "cc" override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere - override def firstPrepPhase = preRecheckPhase def newRechecker()(using Context) = CaptureChecker(ctx) @@ -525,7 +527,7 @@ class CheckCaptures extends Recheck, SymTransformer: augmentConstructorType(parent, initCs ++ refs) case _ => val (refined, cs) = addParamArgRefinements(core, initCs) - refined.capturing(cs.changeOwner(ctx.owner)) + refined.capturing(cs) augmentConstructorType(ownType, capturedVars(cls) ++ capturedVars(sym)) .showing(i"constr type $mt with $argTypes%, % in $cls = $result", capt) @@ -675,12 +677,6 @@ class CheckCaptures extends Recheck, SymTransformer: super.recheckTyped(tree) override def recheckTry(tree: Try, pt: Type)(using Context): Type = -/* - val saved = curEnv - curEnv = Env(tryOwner, EnvKind.Regular, CaptureSet.Var(curEnv.owner), curEnv) - val tp = - try super.recheckTry(tree, pt) - finally curEnv = saved*/ val tp = super.recheckTry(tree, pt) if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, @@ -805,7 +801,7 @@ class CheckCaptures extends Recheck, SymTransformer: .showing(i"adapt universal $actual vs $expected = $result", capt) else actual - val debugSuccesses = false + private inline val debugSuccesses = false /** Massage `actual` and `expected` types before checking conformance. * Massaging is done by the methods following this one: diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 52f9f1ee5056..de8ffab27e24 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -311,12 +311,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if sym.isClass then t else t.info match case TypeAlias(alias) => - val transformed = - if sym.isStatic || true then this(alias) - else transformExplicitType(alias)( - using ctx.withOwner(sym.owner)) + val transformed = this(alias) if transformed ne alias then transformed else t - //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") + //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") case _ => recur(t) case t @ AppliedType(tycon: TypeProxy, args) if true => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9191dd45c4a3..2e67e3991d47 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2182,7 +2182,7 @@ object Types { 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? @@ -2193,7 +2193,7 @@ object Types { /** 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 @@ -2927,7 +2927,7 @@ 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 def localRootOwner(using Context): Symbol = From 6a21e5fe3c92deb6fb50a6e2dbc792a4ad2ade90 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 16:45:01 +0200 Subject: [PATCH 066/117] Drop LooseRootCapturing --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 4 - .../dotty/tools/dotc/cc/CheckCaptures.scala | 5 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 184 ++++++++---------- 3 files changed, 86 insertions(+), 107 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 10ca60821095..4586a1fed8b9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -132,8 +132,6 @@ sealed abstract class CaptureSet extends Showable: * - x is the same as y, * - x is a this reference and y refers to a field of x * - x and y are local roots and y is an enclosing root of x - * - the LooseRootChecking property is asserted, and either `x` is `cap` - * or `x` is a local root and y is `cap`. */ extension (x: CaptureRef)(using Context) private def subsumes(y: CaptureRef) = @@ -142,8 +140,6 @@ sealed abstract class CaptureSet extends Showable: || y.match case y: TermRef => y.prefix eq x case _ => false - || (x.isUniversalRootCapability || y.isRootCapability && x.isRootCapability) - && ctx.property(LooseRootChecking).isDefined /** x <:< cap, cap[x] <:< cap * cap[y] <:< cap[x] if y encloses x diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 927116eace3f..be7341c817ff 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -19,7 +19,7 @@ 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 @@ -1081,8 +1081,7 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case t: Template => - checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t))( - using ctx.withProperty(LooseRootChecking, Some(()))) + checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index de8ffab27e24..b200f415ca67 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -25,27 +25,12 @@ trait SetupAPI: def postCheck()(using Context): Unit object Setup: - def newScheme(using Context) = ctx.settings.YccNew.value // if new impl is conditional - - private val IsDuringSetupKey = new Property.Key[Unit] - - def isDuringSetup(using Context): Boolean = ctx.property(IsDuringSetupKey).isDefined - case class Box(t: Type) extends UncachedGroundType, TermType: override def fallbackToText(printer: Printer): Text = Str("Box(") ~ printer.toText(t) ~ ")" def derivedBox(t1: Type): Type = if t1 eq t then this else Box(t1) - trait DepFunSpecializedTypeMap(using Context) extends TypeMap: - override def mapOver(tp: Type) = tp match - case defn.RefinedFunctionOf(rinfo: MethodOrPoly) => - val rinfo1 = this(rinfo) - if rinfo1 ne rinfo then rinfo1.toFunctionType(alwaysDependent = true) - else tp - case _ => - super.mapOver(tp) - /** 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 @@ -116,7 +101,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def mappedInfo = if toBeUpdated.contains(sym) then symd.info else transformExplicitType(symd.info) - // TODO if sym is class && level owner: add a capture root if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) else if isPreCC(sym) then @@ -173,91 +157,92 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * * Polytype bounds are only cleaned using step 1, but not otherwise transformed. */ - private def mapInferred(refine: Boolean)(using Context): 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 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.RefiningVar(NoSymbol, getter))) - .showing(i"add capture refinement $tp --> $result", ccSetup) - else - core - } - case _ => tp - case _ => tp + 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 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.RefiningVar(NoSymbol, getter))) + .showing(i"add capture refinement $tp --> $result", ccSetup) + 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 - 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", ccSetup) - else if (tycon1 eq tycon) && (args1 eq args0) && (res1 eq res0) then - 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 - tp.derivedAppliedType(tycon1, args1 :+ res1) - else - 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 - // !!! TODO we should also map roots to rootvars here - tp.derivedLambdaType( - paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), - resType = this(tp.resType)) - case Box(tp1) => - box(this(tp1)) - case _ => - mapOver(tp) - addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) - end apply - end mapInferred + 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", ccSetup) + 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 => 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 + // !!! TODO we should also map roots to rootvars here + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), + resType = this(tp.resType)) + case Box(tp1) => + box(this(tp1)) + case _ => + mapOver(tp) + addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) + end apply + end mapInferred - private def transformInferredType(tp: Type)(using Context): Type = mapInferred(refine = true)(tp) + end transformInferredType private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = val expandAliases = new DeepTypeMap: @@ -312,6 +297,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else t.info match case TypeAlias(alias) => val transformed = this(alias) + // TODO: Do we need an eager expansion, presumably, we need that only for normalizeCaptures if transformed ne alias then transformed else t //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") case _ => @@ -568,7 +554,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def complete(denot: SymDenotation)(using Context) = // infos of other methods are determined from their definitions which // are checked on demand - assert(!isDuringSetup, i"$sym") assert(ctx.phase == thisPhase.next, i"$sym") ccSetup.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() @@ -711,8 +696,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => CaptureSet.Var(owner)) def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = - setupTraverser(recheckDef).traverse(tree)( - using ctx.withPhase(thisPhase).withProperty(IsDuringSetupKey, Some(()))) + setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) // ------ Checks to run after main capture checking -------------------------- From 2981ae7cc9a9746097d6d58dd0b225d20bd5503e Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 16:57:50 +0200 Subject: [PATCH 067/117] Simplify TypeComparer --- .../dotty/tools/dotc/core/TypeComparer.scala | 40 +++---------------- .../src/dotty/tools/dotc/core/TypeOps.scala | 6 +-- .../src/dotty/tools/dotc/core/Types.scala | 1 + .../dotty/tools/dotc/typer/RefChecks.scala | 1 - 4 files changed, 9 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 7568cdf92f3e..d71cc5c2d49e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -68,11 +68,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private var myInstance: TypeComparer = this def currentInstance: TypeComparer = myInstance - /** All capturing types in the original `tp1` enclosing the currently - * compared type. - */ - private var enclosingCapturing1: List[AnnotatedType] = Nil - /** Is a subtype check in progress? In that case we may not * permanently instantiate type variables, because the corresponding * constraint might still be retracted and the instantiation should @@ -553,11 +548,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if tp1.isBoxedCapturing && !parent1.isBoxedCapturing then tp2.unboxed else tp2 - try - enclosingCapturing1 = tp1 :: enclosingCapturing1 - recur(parent1, tp2a) - finally - enclosingCapturing1 = enclosingCapturing1.tail + recur(parent1, tp2a) else thirdTry compareCapturing case tp1: AnnotatedType if !tp1.isRefining => @@ -685,7 +676,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if defn.isFunctionType(tp2) then if tp2.derivesFrom(defn.PolyFunctionClass) then - return isSubInfo(tp1.ccMember(nme.apply).info, tp2.refinedInfo) + return isSubInfo(tp1.member(nme.apply).info, tp2.refinedInfo) else tp1w.widenDealias match case tp1: RefinedType => @@ -2038,7 +2029,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * rebase both itself and the member info of `tp` on a freshly created skolem type. */ def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = - trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.ccMember(name).info}", subtyping) { + trace(i"hasMatchingMember($tp1 . $name :? ${tp2.refinedInfo}), mbr: ${tp1.member(name).info}", subtyping) { // If the member is an abstract type and the prefix is a path, compare the member itself // instead of its bounds. This case is needed situations like: @@ -2119,30 +2110,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || (tp1.isStable && m.symbol.isStableMember && isSubType(TermRef(tp1, m.symbol), tp2.refinedInfo)) end qualifies - tp1.ccMember(name).hasAltWithInline(qualifies) + tp1.member(name).hasAltWithInline(qualifies) } - extension (qual: Type) - /** Add all directly enclosing capture sets to `qual` and select `name` on the - * resulting type. A capture set is directly enclosing if there is an enclosing - * capturing type with the set and all types between `qual` and that type - * are RefinedTypes or CapturingTypes. - */ - def ccMember(name: Name): Denotation = - def isEnclosing(tp: Type): Boolean = tp match - case RefinedType(parent, _, _) => isEnclosing(parent) - case CapturingType(parent, _) => isEnclosing(parent) - case _ => tp eq qual - - def addCaptures(tp: Type, encls: List[AnnotatedType]): Type = encls match - case (ct @ CapturingType(parent, refs)) :: encls1 if isEnclosing(parent) => - addCaptures(CapturingType(tp, refs, ct.isBoxedCapturing), encls1) - case _ => - tp - - addCaptures(qual, enclosingCapturing1).member(name) - end ccMember - final def ensureStableSingleton(tp: Type): SingletonType = tp.stripTypeVar match { case tp: SingletonType if tp.isStable => tp case tp: ValueType => SkolemType(tp) @@ -3418,7 +3388,7 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) { } override def hasMatchingMember(name: Name, tp1: Type, tp2: RefinedType): Boolean = - traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.ccMember(name).info)}") { + traceIndented(s"hasMatchingMember(${show(tp1)} . $name, ${show(tp2.refinedInfo)}), member = ${show(tp1.member(name).info)}") { super.hasMatchingMember(name, tp1, tp2) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 50d0dde0e056..0cc2cf22941f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -19,7 +19,7 @@ import typer.ForceDegree import typer.Inferencing._ import typer.IfBottom import reporting.TestingReporter -import cc.{CapturingType, derivedCapturingType, CaptureSet, isBoxed, isBoxedCapturing, isLevelOwner, localRoot} +import cc.{CapturingType, derivedCapturingType, CaptureSet, isBoxed, isBoxedCapturing} import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -99,8 +99,8 @@ object TypeOps: tp match { case tp: NamedType => val sym = tp.symbol - if sym.isStatic && !sym.maybeOwner.seesOpaques then tp - else if tp.prefix `eq` NoPrefix then 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 2e67e3991d47..8d0ecdb9ea9a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2931,6 +2931,7 @@ object Types { name == nme.CAPTURE_ROOT && symbol == defn.captureRoot 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 diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 54e75216367c..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.{localRoot, isCaptureChecking, isLevelOwner} object RefChecks { import tpd._ From 9f82e69a2f85b9c4780cf3dd09fed17deabdd224 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 15 Oct 2023 17:07:42 +0200 Subject: [PATCH 068/117] Drop RefiningVar --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 17 ---------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 12 ------- .../src/dotty/tools/dotc/core/Types.scala | 33 ++++++++----------- 4 files changed, 14 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 4586a1fed8b9..37737f35f39d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -395,13 +395,6 @@ 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 @@ -618,14 +611,6 @@ object CaptureSet: 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): @@ -1006,8 +991,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 => diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b200f415ca67..5b59eb9ef332 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -175,7 +175,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val getterType = mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias RefinedType(core, getter.name, - CapturingType(getterType, CaptureSet.RefiningVar(NoSymbol, getter))) + CapturingType(getterType, CaptureSet.Var(ctx.owner))) .showing(i"add capture refinement $tp --> $result", ccSetup) else core diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d71cc5c2d49e..126e1da0a626 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -682,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) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8d0ecdb9ea9a..e6aa87182ade 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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) = From 202445df58111b2438250481ab3a033953e41054 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 15:47:29 +0200 Subject: [PATCH 069/117] Captureset refactoring Fall back to underlying when we fail to add a single element instead of outermost when the whole subcapture fails. This fixed a deep-seated bug where one attempt would fail but leave an element in a set which would then make a second fallback attempt spuriously succeed. The error was detected in levels.scala where the third error case did not show up. Now it does. --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 225 +++++++++++------- tests/neg-custom-args/captures/levels.check | 10 + tests/neg-custom-args/captures/levels.scala | 2 +- .../pos-custom-args/captures/iterators.scala | 7 + 4 files changed, 151 insertions(+), 93 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 37737f35f39d..b6c6952bd091 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -97,12 +97,58 @@ sealed abstract class CaptureSet extends Showable: * @return CompareResult.OK if elements were added, or a conflicting * capture set that prevents addition otherwise. */ - protected def addNewElems(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = + protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = (CompareResult.OK /: newElems): (r, elem) => - r.andAlso(addNewElem(elem, origin)) + r.andAlso(tryInclude(elem, origin)) - /** Add a single element, with the same meaning as in `addNewElems` */ - protected def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult + /** 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 tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + if accountsFor(elem) then CompareResult.OK + else addNewElem(elem) + + /** 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 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 @@ -111,7 +157,7 @@ sealed abstract class CaptureSet extends Showable: protected def addAsDependentTo(cs: CaptureSet)(using Context): this.type = 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. @@ -121,13 +167,13 @@ sealed abstract class CaptureSet extends Showable: 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) + else tryInclude(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) - + else tryInclude(elem, 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 @@ -208,21 +254,13 @@ 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 then - ccState.levelError = ccState.levelError.orElse(result.levelError) - if !elem.isRootCapability && summon[VarState] != FrozenState then - result = result.orElse(elem.captureSetOfInfo.subCaptures(that)) - if result.isOK then - recur(elems1) - else - varState.rollBack() - result - case Nil => - addDependent(that) - recur(elems.toList) + 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 @@ -400,7 +438,7 @@ object CaptureSet: def isConst = true def isAlwaysEmpty = elems.isEmpty - def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = CompareResult.Fail(this :: Nil) def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -422,7 +460,7 @@ object CaptureSet: */ object Fluid extends Const(emptySet): override def isAlwaysEmpty = false - override def addNewElem(elem: CaptureRef, 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 = "" @@ -489,24 +527,23 @@ object CaptureSet: def resetDeps()(using state: VarState): Unit = deps = state.deps(this) - final def addNewElem(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = + final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = if isConst || !recordElemsState() then CompareResult.Fail(this :: Nil) // fail if variable is solved or given VarState is frozen else if !levelOK(elem) then - val res = CompareResult.LevelError(this, elem) - if elem.isRootCapability then res - else res.orElse(addNewElems(elem.captureSetOfInfo.elems, origin)) + CompareResult.LevelError(this, elem) else //assert(id != 19 || !elem.isLocalRootCapability, elem.asInstanceOf[TermRef].localRootOwner) elems += elem - if elem.isUniversalRootCapability then rootAddedHandler() + if elem.isUniversalRootCapability then + rootAddedHandler() newElemAddedHandler(elem) // assert(id != 5 || elems.size != 3, this) - val res = (CompareResult.OK /: deps) { (r, dep) => + val res = (CompareResult.OK /: deps): (r, dep) => r.andAlso(dep.tryInclude(elem, this)) - }.addToTrace(this) - if !res.isOK then elems -= elem - res + res.orElse: + elems -= elem + res.addToTrace(this) private def levelOK(elem: CaptureRef)(using Context): Boolean = if elem.isUniversalRootCapability then !noUniversal @@ -569,7 +606,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 */ @@ -652,39 +689,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 :: Nil) - } - .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 :: Nil) - 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 @@ -707,15 +748,17 @@ object CaptureSet: (val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) 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) - } + addNewElem(elem).andAlso: + source.tryInclude(bimap.backward(elem), this) + .showing(i"propagating new elem $elem backward from $this to $source", capt) /** For a BiTypeMap, supertypes of the mapped type also constrain * the source via the inverse type mapping and vice versa. That is, if @@ -737,18 +780,17 @@ object CaptureSet: (val source: Var, p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) 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 :: Nil) - } + 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 @@ -772,14 +814,13 @@ object CaptureSet: 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 @@ -882,7 +923,7 @@ object CaptureSet: if alt.isOK then alt else this - inline def addToTrace(cs: CaptureSet) = this match + inline def addToTrace(cs: CaptureSet): CompareResult = this match case Fail(trace) => Fail(cs :: trace) case _ => this end CompareResult diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index b5b0384c82d7..4698dfa23afe 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -10,3 +10,13 @@ | 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 in method scope + | cannot be included in outer capture set ? 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 5e1687670547..f8bd6e77c449 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -19,5 +19,5 @@ def test2(cap1: CC^) = def scope(cap3: CC^) = def g(x: String): String = if cap3 == cap3 then "" else "a" - r.setV(g) // should be error + r.setV(g) // 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^ From fc20b42d8210585fb714c3b8523e5b76425fba52 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 18:34:54 +0200 Subject: [PATCH 070/117] Failing test --- .../neg-custom-args/captures/box-unsoundness.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/neg-custom-args/captures/box-unsoundness.scala diff --git a/tests/neg-custom-args/captures/box-unsoundness.scala b/tests/neg-custom-args/captures/box-unsoundness.scala new file mode 100644 index 000000000000..0955667dab73 --- /dev/null +++ b/tests/neg-custom-args/captures/box-unsoundness.scala @@ -0,0 +1,13 @@ +//@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: (CanIO^ => Unit) -> Unit = use[CanIO^](io) // + val _: (CanIO^ => Unit) -> Unit = f // error + + val g1 = () => f(x => x.use()) // error + + val a1 = f(x => x.use()) + val a2 = () => f(x => x.use()) + val g2: () -> Unit = a2 // error + // was UNSOUND: g uses the capability io but has an empty capture set \ No newline at end of file From 035786d91a16c09f07558ae6067d4b01daa81940 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 19:21:52 +0200 Subject: [PATCH 071/117] Add dropped test --- tests/neg-custom-args/captures/unbox.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/neg-custom-args/captures/unbox.scala diff --git a/tests/neg-custom-args/captures/unbox.scala b/tests/neg-custom-args/captures/unbox.scala new file mode 100644 index 000000000000..33702a954068 --- /dev/null +++ b/tests/neg-custom-args/captures/unbox.scala @@ -0,0 +1,6 @@ +import language.`3.2` +type Proc = () => Unit + +val xs: List[Proc] = ??? + +val x = xs.head // error From f6056aedfa1b97bbe85e4b7fc3f9236173747e04 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 21:24:35 +0200 Subject: [PATCH 072/117] Drop other disabled tests --- .../captures/box-unsoundness.scala.disabled | 6 ---- .../captures/unbox.scala.disabled | 6 ---- .../captures/outer-roots.scala.disabled | 26 ----------------- .../captures/refs.scala.disabled | 29 ------------------- 4 files changed, 67 deletions(-) delete mode 100644 tests/neg-custom-args/captures/box-unsoundness.scala.disabled delete mode 100644 tests/neg-custom-args/captures/unbox.scala.disabled delete mode 100644 tests/pos-custom-args/captures/outer-roots.scala.disabled delete mode 100644 tests/pos-custom-args/captures/refs.scala.disabled diff --git a/tests/neg-custom-args/captures/box-unsoundness.scala.disabled b/tests/neg-custom-args/captures/box-unsoundness.scala.disabled deleted file mode 100644 index 213409365d46..000000000000 --- a/tests/neg-custom-args/captures/box-unsoundness.scala.disabled +++ /dev/null @@ -1,6 +0,0 @@ -@annotation.capability class CanIO { def use(): Unit = () } -def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x) -def test(io: CanIO): Unit = - val f = use[CanIO](io) // - val g: () -> Unit = () => f(x => x.use()) // error - // was UNSOUND: g uses the capability io but has an empty capture set \ No newline at end of file diff --git a/tests/neg-custom-args/captures/unbox.scala.disabled b/tests/neg-custom-args/captures/unbox.scala.disabled deleted file mode 100644 index 33702a954068..000000000000 --- a/tests/neg-custom-args/captures/unbox.scala.disabled +++ /dev/null @@ -1,6 +0,0 @@ -import language.`3.2` -type Proc = () => Unit - -val xs: List[Proc] = ??? - -val x = xs.head // error diff --git a/tests/pos-custom-args/captures/outer-roots.scala.disabled b/tests/pos-custom-args/captures/outer-roots.scala.disabled deleted file mode 100644 index 75b27e89667c..000000000000 --- a/tests/pos-custom-args/captures/outer-roots.scala.disabled +++ /dev/null @@ -1,26 +0,0 @@ -class C -type Cap = C^ - -class A -class B - -class Foo(x: Cap): - - def foo: A ->{cap[Foo]} Unit = ??? - - class Bar(y: Cap): - - def bar: B ->{cap[Bar]} Unit = ??? - - def f(a: A ->{cap[Foo]} Unit, b: B ->{cap[Bar]} Unit) - : (A ->{a} Unit, B ->{b} Unit) - = (a, b) - -def test(c1: Cap, c2: Cap) = - val x = Foo(c1) - val y = x.Bar(c2) - val xfoo = x.foo - val ybar = y.bar - val z1 = y.f(xfoo, ybar) - val z2 = y.f(x.foo, y.bar) - () diff --git a/tests/pos-custom-args/captures/refs.scala.disabled b/tests/pos-custom-args/captures/refs.scala.disabled deleted file mode 100644 index 372b93421c82..000000000000 --- a/tests/pos-custom-args/captures/refs.scala.disabled +++ /dev/null @@ -1,29 +0,0 @@ -type Proc = () => Unit - -class MonoRef(init: Proc): - type MonoProc = Proc - var x: MonoProc = init - def getX: MonoProc = x - def setX(x: MonoProc): Unit = this.x = x - -def test(p: Proc) = - val x = MonoRef(p) - x.setX(p) - val y = x.getX - val yc1: Proc = y - val yc2: () ->{x} Unit = y - val yc3: () ->{cap[test]} Unit = y - -class MonoRef2(init: () => Unit): - var x: () ->{cap[MonoRef2]} Unit = init - def getX: () ->{cap[MonoRef2]} Unit = x - def setX(x: () ->{cap[MonoRef2]} Unit): Unit = this.x = x - -def test2(p: Proc) = - val x = MonoRef2(p) - x.setX(p) - val y = x.getX - val yc1: Proc = y - val yc2: () ->{x} Unit = y - val yc3: () ->{cap[test2]} Unit = y - From 88b328c7203f9eabe1d06369264e9cd3334ef3c5 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 21:46:09 +0200 Subject: [PATCH 073/117] Merge AssignProto and LhProto They serve the same purpose; keep the name LhsProto. --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 5 +---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 57629b9ad24c..4086ccac1851 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -708,9 +708,6 @@ object ProtoTypes { @sharable object AnyTypeConstructorProto extends UncachedGroundType with MatchAlways: override def toString = "AnyTypeConstructorProto" - @sharable object LhsProto extends UncachedGroundType with MatchAlways: - override def toString = "LhsProto" - extension (pt: Type) def isExtensionApplyProto: Boolean = pt match case PolyProto(targs, res) => res.isExtensionApplyProto @@ -971,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/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 From 2ae29fbb2e9fc9df3b12b422c149d9ab9eec96a2 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 16 Oct 2023 21:59:13 +0200 Subject: [PATCH 074/117] Fix box adaptation --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 12 +++++++++++- .../captures/box-adapt-contra.scala | 18 ++++++++++++++++++ .../captures/box-unsoundness.scala | 10 +++++----- 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 tests/neg-custom-args/captures/box-adapt-contra.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index be7341c817ff..f4747ff6b187 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -991,13 +991,23 @@ class CheckCaptures extends Recheck, SymTransformer: } // Capture set of the term after adaptation - val cs1 = cs ++ leaked + val cs1 = if covariant then cs ++ leaked else cs + + def checkLeaked: Boolean = + covariant || leaked.subCaptures(cs1, frozen = false).isOK || { + 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) + false + } // Compute the adapted type def adaptedType(resultBoxed: Boolean) = if (styp1 eq styp) && leaked.isAlwaysEmpty && boxed == resultBoxed then actual else styp1.capturing(if alwaysConst then CaptureSet(cs1.elems) else cs1).forceBoxStatus(resultBoxed) + checkLeaked + if needsAdaptation then val criticalSet = // the set which is not allowed to have `cap` if covariant then cs1 // can't box with `cap` 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 0955667dab73..d1331f16df1f 100644 --- a/tests/neg-custom-args/captures/box-unsoundness.scala +++ b/tests/neg-custom-args/captures/box-unsoundness.scala @@ -2,12 +2,12 @@ class CanIO { def use(): Unit = () } def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x) def test(io: CanIO^): Unit = - val f: (CanIO^ => Unit) -> Unit = use[CanIO^](io) // - val _: (CanIO^ => Unit) -> Unit = f // error + val f: (CanIO^ => Unit) -> Unit = use[CanIO^](io) // error + val _: (CanIO^ => Unit) -> Unit = f - val g1 = () => f(x => x.use()) // error + val g1 = () => f(x => x.use()) val a1 = f(x => x.use()) val a2 = () => f(x => x.use()) - val g2: () -> Unit = a2 // error - // was UNSOUND: g uses the capability io but has an empty capture set \ No newline at end of file + val g2: () -> Unit = a2 + // was UNSOUND: g uses the capability io but has an empty capture set From 4ffbda98a0609165b342d9b46ff16b5b5a2427ff Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 22:36:44 +0200 Subject: [PATCH 075/117] Polish box adaptation for better legibility --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index f4747ff6b187..fc4275c6b426 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -991,23 +991,20 @@ class CheckCaptures extends Recheck, SymTransformer: } // Capture set of the term after adaptation - val cs1 = if covariant then cs ++ leaked else cs - - def checkLeaked: Boolean = - covariant || leaked.subCaptures(cs1, frozen = false).isOK || { - 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) - false - } + 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) = if (styp1 eq styp) && leaked.isAlwaysEmpty && boxed == resultBoxed then actual else styp1.capturing(if alwaysConst then CaptureSet(cs1.elems) else cs1).forceBoxStatus(resultBoxed) - checkLeaked - if needsAdaptation then val criticalSet = // the set which is not allowed to have `cap` if covariant then cs1 // can't box with `cap` From 54aea78c558873a63996ca5b6c504ba910529a62 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Oct 2023 20:41:02 +0200 Subject: [PATCH 076/117] Don't dealias TypeRefs when transforming explicit types --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 9 ++++-- .../dotty/tools/dotc/cc/CheckCaptures.scala | 9 +----- compiler/src/dotty/tools/dotc/cc/Setup.scala | 32 +++++++++---------- .../captures/boundschecks2.scala | 2 +- .../neg-custom-args/captures/outer-var.check | 2 +- .../neg-custom-args/captures/outer-var.scala | 4 +-- tests/neg-custom-args/captures/vars.check | 6 ++-- 7 files changed, 29 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 0648cf704228..ad0185427356 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -16,6 +16,9 @@ import collection.mutable private val Captures: Key[CaptureSet] = Key() +/** RetainingType will be boxed when it gets turned into a capturing type */ +private val NeedsBox: Key[Unit] = Key() + object ccConfig: /** Switch whether unpickled function types and byname types should be mapped to @@ -234,7 +237,7 @@ extension (tp: Type) case _ => false - def isCapabilityClassRef(using Context) = tp match + def isCapabilityClassRef(using Context) = tp.dealiasKeepAnnots match case _: TypeRef | _: AppliedType => tp.typeSymbol.hasAnnotation(defn.CapabilityAnnot) case _ => false @@ -255,7 +258,7 @@ extension (cls: ClassSymbol) defn.pureBaseClasses.contains(bc) || bc.givenSelfType.dealiasKeepAnnots.match case CapturingType(_, refs) => refs.isAlwaysEmpty - case RetainingType(_, refs) => refs.isEmpty // TODO: Better: test at phase cc instead? + case RetainingType(_, refs) => refs.isEmpty case selfType => selfType.exists && selfType.captureSet.isAlwaysEmpty else None @@ -372,4 +375,4 @@ extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match case ann: CaptureAnnotation => ann.boxed - case _ => false + case ann => ann.tree.hasAttachment(NeedsBox) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index fc4275c6b426..0f62b2256ce4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -156,7 +156,7 @@ object CheckCaptures: else recur(encl.owner.enclosingMethodOrClass) recur(encl) def traverse(t: Type) = - t match + t.dealiasKeepAnnots match case t: TypeRef => capt.println(i"disallow $t, $tp, $what, ${t.symbol.is(Sealed)}") t.info match @@ -724,13 +724,6 @@ class CheckCaptures extends Recheck, SymTransformer: 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 diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 5b59eb9ef332..2e6655c6a7fd 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -121,9 +121,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * 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 + ann.tree.putAttachment(NeedsBox, ()) + tp + else tp.derivedAnnotatedType(box(parent), ann) case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) => val res = args.last val boxedRes = recur(res) @@ -229,7 +234,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: resType = this(tp.resType)) case tp: TypeLambda => // Don't recurse into parameter bounds, just cleanup any stray retains annotations - // !!! TODO we should also map roots to rootvars here tp.derivedLambdaType( paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), resType = this(tp.resType)) @@ -291,18 +295,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val t2 = expandCapabilityClass(t) if t2 ne t then return t2 t match - case t: TypeRef => - val sym = t.symbol - if sym.isClass then t - else t.info match - case TypeAlias(alias) => - val transformed = this(alias) - // TODO: Do we need an eager expansion, presumably, we need that only for normalizeCaptures - if transformed ne alias then transformed else t - //.showing(i"EXPAND $t with ${t.info} to $result in ${t.symbol.owner}/${ctx.owner}") - case _ => - recur(t) - case t @ AppliedType(tycon: TypeProxy, args) if true => + case t @ AppliedType(tycon: TypeProxy, args) => tycon.underlying match case TypeAlias(aliasTycon) => val args1 = @@ -327,7 +320,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for tpt <- tptToCheck do checkQualifiedRoots(ann.tree) checkWellformedLater(parent1, ann.tree, tpt) - CapturingType(parent1, ann.tree.toCaptureSet) + CapturingType(parent1, ann.tree.toCaptureSet, boxed = ann.tree.hasAttachment(NeedsBox)) else t.derivedAnnotatedType(this(parent), ann) case Box(t1) => @@ -632,6 +625,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: needsVariable(parent) && 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 _ => 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/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 8231a04c503c..ef89bae47a70 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -28,7 +28,7 @@ | Found: () ->{q} Unit | Required: () ->{p} Unit | - | Note that reference (q : () => Unit), defined in method inner + | 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` diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index a680af68198f..c59efefb51c4 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -3,9 +3,9 @@ type Cap = CC^ type Proc = () => Unit -def test(p: Proc) = +def test(p: Proc, q: () => Unit) = var x: () ->{cap[test]} Unit = p - var y = p + var y = p // OK, y has type () ->{p} Proc def inner(q: Proc) = x = q // error diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 84eadb00d7a7..988a787173d1 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -1,9 +1,9 @@ -- 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 {cap1} of variable a + | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a | - | Note that reference (cap3 : CC^), defined in method scope + | 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 @@ -11,7 +11,7 @@ | Found: (x: String) ->{cap3} String | Required: (x$0: String) ->{cap1} String | - | Note that reference (cap3 : CC^), defined in method scope + | 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` From ac8971ed86813a4bc6d1b7340090deee52e75778 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 17 Oct 2023 23:29:03 +0200 Subject: [PATCH 077/117] Move all disallowRootCapabilitiesIn calls to checkCaptures That way, we avoid having to call the function potentially before capture types are formed. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 26 ++++++++++++++++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 13 ---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 0f62b2256ce4..04dad9159cba 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -144,7 +144,7 @@ object CheckCaptures: * 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 disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = + 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 @@ -172,9 +172,9 @@ object CheckCaptures: traverse(hi) case _ => traverseChildren(t) - case AnnotatedType(tp, ann) if ann.symbol == defn.UncheckedCapturesAnnot => + case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot => () - case _ => + case t => if variance >= 0 then t.captureSet.disallowRootCapability: () => def part = if t eq tp then "" else i"the part $t of " @@ -534,6 +534,20 @@ class CheckCaptures extends Recheck, SymTransformer: else ownType end instantiate + override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = + if 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") @@ -574,7 +588,11 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try if sym.is(Module) then sym.info // Modules are checked by checking the module class - else checkInferredResult(super.recheckValDef(tree, sym), tree) + 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 diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 2e6655c6a7fd..c9cb1b5015cf 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -433,9 +433,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) - if sym.is(Mutable) && !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then - CheckCaptures.disallowRootCapabilitiesIn(tpt.knownType, sym, - i"mutable $sym", "have type", "", sym.srcPos) ccSetup.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) @@ -444,16 +441,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: for case arg: TypeTree <- args do transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed - if allowUniversalInBoxed then - val polyType = fn.tpe.widen.asInstanceOf[TypeLambda] - for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do - if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then - def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else "" - CheckCaptures.disallowRootCapabilitiesIn(arg.knownType, 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) - case tree: TypeDef if tree.symbol.isClass => inContext(ctx.withOwner(tree.symbol)): traverseChildren(tree) From d29d5e3870a946be795e75a239ead222be55da69 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 18 Oct 2023 12:59:09 +0200 Subject: [PATCH 078/117] Simplify expandAliases --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 5 +- compiler/src/dotty/tools/dotc/cc/Setup.scala | 87 +++++++------------ .../captures/box-adapt-boxing.scala | 6 +- tests/neg-custom-args/captures/i15772.check | 4 +- tests/neg-custom-args/captures/try.check | 4 +- 5 files changed, 39 insertions(+), 67 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index ad0185427356..8181462d617c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -16,9 +16,6 @@ import collection.mutable private val Captures: Key[CaptureSet] = Key() -/** RetainingType will be boxed when it gets turned into a capturing type */ -private val NeedsBox: Key[Unit] = Key() - object ccConfig: /** Switch whether unpickled function types and byname types should be mapped to @@ -375,4 +372,4 @@ extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match case ann: CaptureAnnotation => ann.boxed - case ann => ann.tree.hasAttachment(NeedsBox) + case _ => false diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index c9cb1b5015cf..8e8bf373ddac 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -25,11 +25,6 @@ trait SetupAPI: def postCheck()(using Context): Unit object Setup: - case class Box(t: Type) extends UncachedGroundType, TermType: - override def fallbackToText(printer: Printer): Text = - Str("Box(") ~ printer.toText(t) ~ ")" - def derivedBox(t1: Type): Type = - if t1 eq t then this else Box(t1) /** Recognizer for `res $throws exc`, returning `(res, exc)` in case of success */ object throwsAlias: @@ -125,9 +120,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tp @ CapturingType(parent, refs) => if tp.isBoxed then tp else tp.boxed case tp @ AnnotatedType(parent, ann) => - if ann.symbol == defn.RetainsAnnot then - ann.tree.putAttachment(NeedsBox, ()) - tp + 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 @@ -237,8 +231,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp.derivedLambdaType( paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), resType = this(tp.resType)) - case Box(tp1) => - box(this(tp1)) case _ => mapOver(tp) addVar(addCaptureRefinements(normalizeCaptures(tp1)), ctx.owner) @@ -259,22 +251,23 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * expands to * (erased x$0: CanThrow[E1]) ?-> (erased x$1: CanThrow[E1]) ?->{x$0} T */ - private def expandThrowsAlias(tp: Type, encl: List[MethodType] = Nil): 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)( + 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 => 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 + 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 = @@ -290,48 +283,30 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def recur(t: Type): Type = normalizeCaptures(mapOver(t)) 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 @ AppliedType(tycon: TypeProxy, args) => - tycon.underlying match - case TypeAlias(aliasTycon) => - val args1 = - if defn.isFunctionClass(t.dealias.typeSymbol) then args - else args.map(Box(_)) - val alias = aliasTycon.applyIfParameterized(args1) - val transformed = this(alias) - if transformed ne alias then - //println(i"APP ALIAS $t / $alias / $transformed") - transformed - else - //println(i"NO ALIAS $t / $alias / $transformed") - recur(t) - case _ => - recur(t) 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 - val parent1 = this(parent) for tpt <- tptToCheck do checkQualifiedRoots(ann.tree) checkWellformedLater(parent1, ann.tree, tpt) - CapturingType(parent1, ann.tree.toCaptureSet, boxed = ann.tree.hasAttachment(NeedsBox)) + CapturingType(parent1, ann.tree.toCaptureSet) else - t.derivedAnnotatedType(this(parent), ann) - case Box(t1) => - box(this(t1)) + 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 _ => - recur(t) + 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? @@ -342,11 +317,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Transform type of type tree, and remember the transformed type as the type the tree */ private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit = if !tree.hasRememberedType then - val tp = if boxed then Box(tree.tpe) else tree.tpe - tree.rememberType( + val transformed = if tree.isInstanceOf[InferredTypeTree] && !exact - then transformInferredType(tp) - else transformExplicitType(tp, tptToCheck = Some(tree))) + 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. diff --git a/tests/neg-custom-args/captures/box-adapt-boxing.scala b/tests/neg-custom-args/captures/box-adapt-boxing.scala index b6eb59aef593..0052828dbabb 100644 --- a/tests/neg-custom-args/captures/box-adapt-boxing.scala +++ b/tests/neg-custom-args/captures/box-adapt-boxing.scala @@ -17,7 +17,7 @@ def main(io: Cap^, fs: Cap^): Unit = { type Op0[X] = Box[X] -> Unit type Op1[X] = Unit -> Box[X] val f: Unit -> (Cap^{io}) -> Unit = ??? - val test: Op1[Op0[Cap^{io}]^{io}]^{} = f // error ??? not sure this is correct + val test: Op1[Op0[Cap^{io}]^{io}]^{} = f // expected: {} Unit -> box {io} (box {io} Cap) -> Unit // actual: Unit -> ({io} Cap) -> Unit // @@ -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 h: Op[Id[Cap^{io}]^{io}] = f // error ??? not sure this is correct + val g: Op[Id[Cap^{io}]^{fs}] = f + val h: Op[Id[Cap^{io}]^{io}] = f } } diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 03c5173c4326..bba37a99b569 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -19,14 +19,14 @@ 27 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ | Found: (C{val arg: C^}^{c} => Unit) ->{c} Unit - | Required: (C^ => Unit) -> Unit + | Required: Observe[C^] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ | Found: (C{val arg: C^}^ => Unit) ->? Unit - | Required: (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 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index c76c753487c7..c28bb03b7ab7 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -8,8 +8,8 @@ -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ - | (x : CT[Exception]^) 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]) => From c3b2f8ac0af98790cc8222a3b959d3d8bdb2021b Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 18 Oct 2023 14:07:44 +0200 Subject: [PATCH 079/117] Drop user-defined local roots --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 13 +-------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 4 +-- ...usingLogFile-alt.scala => usingFile.scala} | 2 +- .../captures/usingLogFile.check | 29 +++++++++++-------- .../captures/usingLogFile.scala | 14 ++------- 6 files changed, 23 insertions(+), 41 deletions(-) rename tests/neg-custom-args/captures/{usingLogFile-alt.scala => usingFile.scala} (83%) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8181462d617c..aaa702ddd9ad 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -339,24 +339,13 @@ extension (sym: Symbol) else recur(sym.owner) recur(sym) - /** The parameter with type caps.Cap in the leading term parameter section, - * or NoSymbol, if none exists. - */ - def definedLocalRoot(using Context): Symbol = - sym.paramSymss.dropWhile(psyms => psyms.nonEmpty && psyms.head.isType) match - case psyms :: _ => psyms.find(_.info.typeSymbol == defn.Caps_Cap).getOrElse(NoSymbol) - case _ => NoSymbol - /** The local root corresponding to sym's level owner */ def localRoot(using Context): Symbol = val owner = sym.levelOwner assert(owner.exists) def newRoot = newSymbol(if owner.isClass then newLocalDummy(owner) else owner, nme.LOCAL_CAPTURE_ROOT, Synthetic, defn.Caps_Cap.typeRef) - def lclRoot = - if owner.isTerm then owner.definedLocalRoot.orElse(newRoot) - else newRoot - ccState.localRoots.getOrElseUpdate(owner, lclRoot) + ccState.localRoots.getOrElseUpdate(owner, newRoot) def maxNested(other: Symbol, onConflict: (Symbol, Symbol) => Context ?=> Symbol)(using Context): Symbol = if !sym.exists || other.isContainedIn(sym) then other diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 8e8bf373ddac..6553d4b9255a 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -559,7 +559,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: 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 _ => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e6aa87182ade..7957b979d7d1 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2927,9 +2927,7 @@ object Types { // 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 if info.isRef(defn.Caps_Cap) && owner.isTerm then normOwner - else NoSymbol + if name == nme.LOCAL_CAPTURE_ROOT then normOwner else NoSymbol override def normalizedRef(using Context): CaptureRef = if isTrackableRef then symbol.termRef else this 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.check b/tests/neg-custom-args/captures/usingLogFile.check index 34b4feb97a8a..85d09977014a 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -30,18 +30,23 @@ | | where: x$0 is a reference to a value parameter | x$0² is a reference to a value parameter --- 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 +-- 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 | ^^^^^^^^^ - | escaping local reference local.type --- Error: tests/neg-custom-args/captures/usingLogFile.scala:70:16 ------------------------------------------------------ -70 | val later = usingFile("logfile", // error !!! but should be ok, since we can widen `l` to `file` instead of to `cap` + | reference (caps.cap : caps.Cap) is not included in the allowed capture set {x$0, x$0²} + | + | Note that the universal capability `cap` + | cannot be included in capture set {x$0, x$0} + | + | where: x$0 is a reference to a value parameter + | x$0² is a reference to a value parameter +-- 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` | ^^^^^^^^^ - | reference (_$1 : java.io.OutputStream^{local}) is not included in the allowed capture set {x$0, local} + | reference (_$1 : java.io.OutputStream^) is not included in the allowed capture set {x$0, x$0²} + | + | Note that reference (_$1 : java.io.OutputStream^), defined in method $anonfun + | cannot be included in outer capture set {x$0, x$0} which is associated with method test | - | Note that reference (_$1 : java.io.OutputStream^{local}), defined in method $anonfun - | cannot be included in outer capture set {x$0, local} which is associated with method test + | 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.scala b/tests/neg-custom-args/captures/usingLogFile.scala index 9fefc25512d1..8246cfd9377e 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -37,20 +37,10 @@ object Test2: 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,7 +52,7 @@ 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) From a6624de1e9c7feb608f53efe62444e7a06ad6569 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 18 Oct 2023 17:44:26 +0200 Subject: [PATCH 080/117] Polishing and change printing options --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 2 +- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 34 ++++--- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 96 ++++++++----------- .../dotty/tools/dotc/cc/CapturingType.scala | 2 +- .../dotty/tools/dotc/cc/CheckCaptures.scala | 50 +++++++--- compiler/src/dotty/tools/dotc/cc/Setup.scala | 23 +++-- .../dotty/tools/dotc/config/Printers.scala | 10 +- .../tools/dotc/config/ScalaSettings.scala | 6 +- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../dotty/tools/dotc/transform/Recheck.scala | 14 +-- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- 11 files changed, 124 insertions(+), 117 deletions(-) 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/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index aaa702ddd9ad..60753e323a12 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -7,7 +7,7 @@ import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* import ast.{tpd, untpd} import Decorators.*, NameOps.* import config.SourceVersion -import config.Printers.{capt, ccSetup} +import config.Printers.capt import util.Property.Key import tpd.* import StdNames.nme @@ -24,10 +24,13 @@ object ccConfig: */ private[cc] val adaptUnpickledFunctionTypes = false + /** 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`) /** Are we at checkCaptures phase? */ def isCaptureChecking(using Context): Boolean = @@ -251,13 +254,12 @@ extension (tp: Type) extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = - if cls.isClass then cls.asClass.baseClasses.find: bc => + cls.baseClasses.find: bc => defn.pureBaseClasses.contains(bc) || bc.givenSelfType.dealiasKeepAnnots.match case CapturingType(_, refs) => refs.isAlwaysEmpty case RetainingType(_, refs) => refs.isEmpty case selfType => selfType.exists && selfType.captureSet.isAlwaysEmpty - else None extension (sym: Symbol) @@ -303,19 +305,15 @@ extension (sym: Symbol) && sym != defn.Caps_unsafeBox && sym != defn.Caps_unsafeUnbox - def isTrackedSomewhere(using Context): Boolean = - val search = new TypeAccumulator[Boolean]: - def apply(found: Boolean, tp: Type) = - def isTrackedHere = variance >= 0 && !tp.captureSet.isAlwaysEmpty - found || isTrackedHere || foldOver(found, tp) - search(false, sym.info) - - // TODO Also include vals (right now they are manually entered in levelOwners by Setup) + /** Can this symbol possibly own a local root? + * TODO: Disallow anonymous functions? + */ def isLevelOwner(using Context): Boolean = sym.isClass - || sym.is(Method, butNot = Accessor)// && !sym.isAnonymousFunction // TODO enable? + || sym.is(Method, butNot = Accessor) - /** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists. + /** 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 = @@ -347,11 +345,17 @@ extension (sym: Symbol) 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 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 !other.exists || other.isContainedIn(sym) then sym else if !sym.exists || sym.isContainedIn(other) then other diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index b6c6952bd091..1e5e8e593ebc 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, ccSetup} +import config.Printers.capt 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 @@ -84,23 +84,13 @@ sealed abstract class CaptureSet extends Showable: final def containsRoot(using Context) = elems.exists(_.isRootCapability) + /** 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 - /** 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. - */ - protected final def tryInclude(newElems: Refs, origin: CaptureSet)(using Context, VarState): CompareResult = - (CompareResult.OK /: newElems): (r, elem) => - r.andAlso(tryInclude(elem, origin)) - /** 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. @@ -124,6 +114,11 @@ sealed abstract class CaptureSet extends Showable: 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. * @@ -132,12 +127,14 @@ sealed abstract class CaptureSet extends Showable: * 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 + 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 = @@ -146,7 +143,7 @@ sealed abstract class CaptureSet extends Showable: /** Add a specific element, assuming it is not already accounted for, * and omitting any mapping or filtering, without possibility to backtrack - * to underlying capture set + * to the underlying capture set. */ protected def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult @@ -157,29 +154,14 @@ sealed abstract class CaptureSet extends Showable: protected def addAsDependentTo(cs: CaptureSet)(using Context): this.type = 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 tryInclude(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 tryInclude(elem, 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 - */ 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) @@ -193,8 +175,8 @@ sealed abstract class CaptureSet extends Showable: */ private def isSuperRootOf(y: CaptureRef): Boolean = x match case x: TermRef => - if x.isUniversalRootCapability then true - else if x.isLocalRootCapability && !y.isUniversalRootCapability then + x.isUniversalRootCapability + || x.isLocalRootCapability && !y.isUniversalRootCapability && { val xowner = x.localRootOwner y match case y: TermRef => @@ -203,7 +185,7 @@ sealed abstract class CaptureSet extends Showable: xowner.isContainedIn(y.cls) case _ => false - else false + } case _ => false end extension @@ -548,14 +530,14 @@ object CaptureSet: private def levelOK(elem: CaptureRef)(using Context): Boolean = if elem.isUniversalRootCapability then !noUniversal else !levelLimit.exists - || elem.match - case elem: TermRef => - var sym = elem.symbol - if sym.isLevelOwner then sym = sym.owner - levelLimit.isContainedIn(sym.levelOwner) - case elem: ThisType => - levelLimit.isContainedIn(elem.cls.levelOwner) - case _ => true + || elem.match + case elem: TermRef => + var sym = elem.symbol + if sym.isLevelOwner then sym = sym.owner + levelLimit.isContainedIn(sym.levelOwner) + case elem: ThisType => + levelLimit.isContainedIn(elem.cls.levelOwner) + case _ => true def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult = if (cs eq this) || cs.isUniversal || isConst then @@ -1015,10 +997,8 @@ object CaptureSet: /** 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) + case ref: TermRef if ref.isRootCapability => ref.singletonCaptureSet + case _ => ofType(ref.underlying, followResult = true) /** Capture set of a type */ def ofType(tp: Type, followResult: Boolean)(using Context): CaptureSet = diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 2dde12f705aa..2a5cb91b45d3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -29,7 +29,7 @@ object CapturingType: /** Smart constructor that * - drops empty capture sets * - drops a capability class expansion if it is further refined with another capturing type - * - fuses compatible capturiong types. + * - 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. */ diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 04dad9159cba..927c58438244 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -6,7 +6,7 @@ import core.* import Phases.*, DenotTransformers.*, SymDenotations.* import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* import Types.*, StdNames.*, Denotations.* -import config.Printers.{capt, recheckr, ccSetup} +import config.Printers.{capt, recheckr, noPrinter} import config.{Config, Feature} import ast.{tpd, untpd, Trees} import Trees.* @@ -146,6 +146,7 @@ object CheckCaptures: */ 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) @@ -155,6 +156,7 @@ object CheckCaptures: 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 => @@ -185,6 +187,7 @@ object CheckCaptures: pos) traverseChildren(t) check.traverse(tp) + end disallowRootCapabilitiesIn /** Attachment key for bodies of closures, provided they are values */ val ClosureBodyValue = Property.Key[Unit] @@ -479,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. @@ -535,7 +538,7 @@ class CheckCaptures extends Recheck, SymTransformer: end instantiate override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = - if allowUniversalInBoxed then + if ccConfig.allowUniversalInBoxed then val TypeApply(fn, args) = tree val polyType = atPhase(thisPhase.prev): fn.tpe.widen.asInstanceOf[TypeLambda] @@ -577,9 +580,7 @@ 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 @@ -616,6 +617,12 @@ class CheckCaptures extends Recheck, SymTransformer: 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 @@ -696,7 +703,7 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckTry(tree: Try, pt: Type)(using Context): Type = val tp = super.recheckTry(tree, pt) - if allowUniversalInBoxed && Feature.enabled(Feature.saferExceptions) then + 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.", @@ -733,7 +740,12 @@ 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 @@ -758,7 +770,7 @@ class CheckCaptures extends Recheck, SymTransformer: checkNotUniversal(parent) case _ => val adapted = - if allowUniversalInBoxed then + if ccConfig.allowUniversalInBoxed then adaptUniversal(tpe, pt, tree) else if needsUniversalCheck then checkNotUniversal(tpe) @@ -777,6 +789,11 @@ 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. + /** 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]: @@ -802,6 +819,11 @@ class CheckCaptures extends Recheck, SymTransformer: foldOver(s, t) acc(NoSymbol, tree).orElse(ctx.owner).localRoot + /** 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) @@ -1020,7 +1042,7 @@ class CheckCaptures extends Recheck, SymTransformer: 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`. @@ -1028,7 +1050,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( @@ -1103,14 +1125,16 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => traverseChildren(t) - /** Check a ValDef or DefDef as an action performed in a completer. + /** 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 = curEnv.outersIterator - .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) + 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 = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 6553d4b9255a..2c678f3d6785 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -8,7 +8,7 @@ import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* import Types.*, StdNames.* import Annotations.Annotation import config.Feature -import config.Printers.{capt, ccSetup} +import config.Printers.capt import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} @@ -175,7 +175,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias RefinedType(core, getter.name, CapturingType(getterType, CaptureSet.Var(ctx.owner))) - .showing(i"add capture refinement $tp --> $result", ccSetup) + .showing(i"add capture refinement $tp --> $result", capt) else core } @@ -211,7 +211,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if isTopLevel then depFun(args1, res1, isContextual = defn.isContextFunctionClass(tycon1.classSymbol)) - .showing(i"add function refinement $tp ($tycon1, $args1, $res1) (${tp.dealias}) --> $result", ccSetup) + .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 @@ -310,7 +310,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end expandAliases val tp1 = expandAliases(tp) // TODO: Do we still need to follow aliases? - if tp1 ne tp then ccSetup.println(i"expanded in ${ctx.owner}: $tp --> $tp1") + if tp1 ne tp then capt.println(i"expanded in ${ctx.owner}: $tp --> $tp1") tp1 end transformExplicitType @@ -330,7 +330,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: */ 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 => @@ -357,7 +356,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: recur(to, from) case _ => mapOver(t) - def inverse = thisMap + def inverse = SubstParams.this end SubstParams /** Update info of `sym` for CheckCaptures phase only */ @@ -376,7 +375,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = transformTT(tpt, - boxed = !allowUniversalInBoxed && sym.is(Mutable, butNot = Method), + 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 @@ -408,7 +407,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) - ccSetup.println(i"mapped $tree = ${tpt.knownType}") + capt.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) case tree @ TypeApply(fn, args) => @@ -494,7 +493,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if sym.exists && signatureChanges then val newInfo = integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) - .showing(i"update info $sym: ${sym.info} = $result", ccSetup) + .showing(i"update info $sym: ${sym.info} = $result", capt) if newInfo ne sym.info then val updatedInfo = if sym.isAnonymousFunction @@ -510,7 +509,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // infos of other methods are determined from their definitions which // are checked on demand assert(ctx.phase == thisPhase.next, i"$sym") - ccSetup.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") + 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) @@ -530,7 +529,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: ps.mapConserve(transformExplicitType(_)) val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType) updateInfo(cls, newInfo) - ccSetup.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $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 @@ -564,7 +563,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: superTypeIsImpure(tp.tp1) && superTypeIsImpure(tp.tp2) case _ => false - }.showing(i"super type is impure $tp = $result", ccSetup) + }.showing(i"super type is impure $tp = $result", capt) /** Should a capture set variable be added on type `tp`? */ def needsVariable(tp: Type)(using Context): Boolean = { diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 526c98eb20b8..f07645cfe75d 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -14,9 +14,13 @@ object Printers { val default = new Printer - val capt = noPrinter - val ccSetupOn = new Printer - def ccSetup(using Context): Printer = if ctx.settings.YccTest.value then ccSetupOn else 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 val constr = noPrinter val core = noPrinter diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index ca5fac207f3f..9f75f6afa028 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -414,9 +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, debug info for captured references") - val YccTest: Setting[Boolean] = BooleanSetting("-Ycc-test", "Used in conjunction with captureChecking language import, debug info for captured references") - val YccPrintSetup: Setting[Boolean] = BooleanSetting("-Ycc-print-setup", "") + 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/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 0cc2cf22941f..1166e287ed27 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -11,7 +11,7 @@ import StdNames._ import collection.mutable import ast.tpd._ import reporting.trace -import config.Printers.{typr, ccSetup} +import config.Printers.typr import config.Feature import transform.SymUtils.* import typer.ProtoTypes._ diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index a7ecba54de19..9833b3cf177f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -17,7 +17,7 @@ import typer.TypeAssigner.seqLitType import typer.ConstFold import typer.ErrorReporting.{Addenda, NothingToAdd} import NamerOps.methodType -import config.Printers.{recheckr, ccSetup, noPrinter} +import config.Printers.recheckr import util.Property import StdNames.nme import reporting.trace @@ -536,14 +536,10 @@ abstract class Recheck extends Phase, SymTransformer: tpe1 def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = - def op = - try recheckFinish(recheckStart(tree, pt), tree, pt) - catch case ex: Exception => - println(i"error while rechecking $tree") - throw ex - if ccSetup eq noPrinter then op - else trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): - op + 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 diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 301fb0f55691..e9265031221b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -521,7 +521,7 @@ object Checking { val illegal = sym.flags & ClassOnlyFlags if sym.is(TypeParam) && illegal == Sealed - && Feature.ccEnabled && cc.allowUniversalInBoxed + && Feature.ccEnabled && cc.ccConfig.allowUniversalInBoxed then () // OK else fail(em"only classes can be ${illegal.flagsString}") if (sym.is(AbsOverride) && !sym.owner.is(Trait)) From e266d21853336def81a9da4fbbaa3c9c2497a4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Oct 2023 10:36:55 +0200 Subject: [PATCH 081/117] Fix #18658: Handle varargs of generic types in `JSExportsGen`. When extracting the type of a varargs parameter, we have to go back to before erasure. However, that gives us a non-erased type inside as well. We need to re-erase that type to get something sensible for the back-end. --- .../tools/dotc/transform/sjs/JSSymUtils.scala | 2 +- .../jsinterop/NonNativeJSTypeTestScala3.scala | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/sjs-junit/test/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala3.scala diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/JSSymUtils.scala b/compiler/src/dotty/tools/dotc/transform/sjs/JSSymUtils.scala index 115d41dd3d46..ae6635bce622 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/JSSymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/JSSymUtils.scala @@ -185,7 +185,7 @@ object JSSymUtils { val list = for ((name, info) <- paramNamesAndTypes) yield { val v = - if (info.isRepeatedParam) Some(info.repeatedToSingle.widenDealias) + if (info.isRepeatedParam) Some(TypeErasure.erasure(info.repeatedToSingle)) else None name -> v } diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala3.scala new file mode 100644 index 000000000000..ceb29d39b6a0 --- /dev/null +++ b/tests/sjs-junit/test/org/scalajs/testsuite/jsinterop/NonNativeJSTypeTestScala3.scala @@ -0,0 +1,78 @@ +package org.scalajs.testsuite.jsinterop + +import org.junit.Assert.* +import org.junit.Test + +import scala.scalajs.js +import scala.scalajs.js.annotation.* + +class NonNativeJSTypeTestScala3 { + import NonNativeJSTypeTestScala3.* + + @Test + def overloadWithVarargOfGenericType(): Unit = { + class OverloadWithVarargOfGenericType extends js.Object { + def overloaded(x: Int): Int = x + def overloaded(xs: (Int, Int)*): Int = xs.size + } + + val obj = new OverloadWithVarargOfGenericType + assertEquals(5, obj.overloaded(5)) + assertEquals(1, obj.overloaded((5, 6))) + assertEquals(2, obj.overloaded((1, 2), (3, 4))) + } + + @Test + def overloadWithVarargOfValueClass(): Unit = { + class OverloadWithVarargOfValueClass extends js.Object { + def overloaded(x: Int): Int = x + def overloaded(xs: VC*): Int = xs.size + } + + val obj = new OverloadWithVarargOfValueClass + assertEquals(5, obj.overloaded(5)) + assertEquals(1, obj.overloaded(new VC(5))) + assertEquals(2, obj.overloaded(new VC(5), new VC(6))) + } + + @Test + def overloadWithVarargOfGenericValueClass(): Unit = { + class OverloadWithVarargOfGenericValueClass extends js.Object { + def overloaded(x: Int): Int = x + def overloaded(xs: GenVC[Int]*): Int = xs.size + } + + val obj = new OverloadWithVarargOfGenericValueClass + assertEquals(5, obj.overloaded(5)) + assertEquals(1, obj.overloaded(new GenVC(5))) + assertEquals(2, obj.overloaded(new GenVC(5), new GenVC(6))) + } + + @Test + def overloadWithVarargOfOpaqueTypeAlias(): Unit = { + import OpaqueContainer.* + + class OverloadWithVarargOfOpaqueTypeAlias extends js.Object { + def overloaded(x: String): Int = x.toInt + def overloaded(xs: OpaqueInt*): Int = xs.size + } + + val obj = new OverloadWithVarargOfOpaqueTypeAlias + assertEquals(5, obj.overloaded("5")) + assertEquals(1, obj.overloaded(fromInt(5))) + assertEquals(2, obj.overloaded(fromInt(5), fromInt(6))) + } +} + +object NonNativeJSTypeTestScala3 { + final class VC(val x: Int) extends AnyVal + + final class GenVC[T](val x: T) extends AnyVal + + object OpaqueContainer { + opaque type OpaqueInt = Int + + def fromInt(x: Int): OpaqueInt = x + def toInt(x: OpaqueInt): Int = x + } +} From baac1ba8897aad50bec4b0e9764b9961b9e53229 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 16 Oct 2023 13:00:51 +0200 Subject: [PATCH 082/117] bugfix: add multiline comment completion --- .../dotty/tools/pc/completions/Completions.scala | 9 +++++++++ .../completions/MultilineCommentCompletion.scala | 16 ++++++++++++++++ .../pc/tests/completion/CompletionSuite.scala | 14 ++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 presentation-compiler/src/main/dotty/tools/pc/completions/MultilineCommentCompletion.scala diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala index 64bbbb848289..aa3fa59e518e 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala @@ -286,6 +286,15 @@ class Completions( path match case ScalaCliCompletions(dependency) => (ScalaCliCompletions.contribute(dependency), true) + + case _ + if MultilineCommentCompletion.isMultilineCommentCompletion( + pos, + text, + ) => + val values = MultilineCommentCompletion.contribute(config) + (values, true) + case _ if ScaladocCompletions.isScaladocCompletion(pos, text) => val values = ScaladocCompletions.contribute(pos, text, config) (values, true) diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/MultilineCommentCompletion.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/MultilineCommentCompletion.scala new file mode 100644 index 000000000000..46a23446a7f1 --- /dev/null +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/MultilineCommentCompletion.scala @@ -0,0 +1,16 @@ +package dotty.tools.pc.completions + +import scala.meta.pc.PresentationCompilerConfig + +import dotty.tools.dotc.util.SourcePosition + +object MultilineCommentCompletion: + + def contribute(config: PresentationCompilerConfig): List[CompletionValue] = + val newText = if config.isCompletionSnippetsEnabled then " $0 */" else " */" + List(CompletionValue.document("/* */", newText, "Multiline Comment")) + + def isMultilineCommentCompletion(pos: SourcePosition, text: String): Boolean = + pos.point >= 2 && + text.charAt(pos.point - 2) == '/' && + text.charAt(pos.point - 1) == '*' diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 7ca7f9a19b04..213dd7157293 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1486,3 +1486,17 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin, ) + @Test def `multiline-comment` = + checkEdit( + """|package a + |object O: + | /*@@ + | def f = 1 + |""".stripMargin, + """|package a + |object O: + | /* $0 */ + | def f = 1 + |""".stripMargin, + ) + From 101758e2478168ee59a5ddfe6a12e8d18ad90242 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 27 Sep 2023 13:57:16 +0100 Subject: [PATCH 083/117] Fix exhaustivity due to separate TypeVar lambdas --- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 6 +++--- tests/pos/i14224.1.scala | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i14224.1.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 730a193fa22c..06b593006acf 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -476,8 +476,8 @@ object SpaceEngine { erase(parent, inArray, isValue, isTyped) case tref: TypeRef if tref.symbol.isPatternBound => - if inArray then tref.underlying - else if isValue then tref.superType + if inArray then erase(tref.underlying, inArray, isValue, isTyped) + else if isValue then erase(tref.superType, inArray, isValue, isTyped) else WildcardType case _ => tp @@ -531,7 +531,7 @@ object SpaceEngine { val mt: MethodType = unapp.widen match { case mt: MethodType => mt case pt: PolyType => - val tvars = pt.paramInfos.map(newTypeVar(_)) + val tvars = constrained(pt, EmptyTree)._2.tpes val mt = pt.instantiate(tvars).asInstanceOf[MethodType] scrutineeTp <:< mt.paramInfos(0) // force type inference to infer a narrower type: could be singleton diff --git a/tests/pos/i14224.1.scala b/tests/pos/i14224.1.scala new file mode 100644 index 000000000000..c0eaa2eedbcd --- /dev/null +++ b/tests/pos/i14224.1.scala @@ -0,0 +1,11 @@ +//> using options -Werror + +// Derived from the extensive test in the gist in i14224 +// Minimising to the false positive in SealedTrait1.either + +sealed trait Foo[A, A1 <: A] +final case class Bar[A, A1 <: A](value: A1) extends Foo[A, A1] + +class Main: + def test[A, A1 <: A](foo: Foo[A, A1]): A1 = foo match + case Bar(v) => v From e840311dcda2b319d8f52b8499901da685c26a33 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 28 Sep 2023 11:30:32 +0100 Subject: [PATCH 084/117] Rework ProtoType's constrained API Remove the single use overload, replace with a much more used alternative Also return TypeVars instead of TypeTrees, so we don't have to unwrap the useless wrapper a bunch of times, and instead we wrap the few times we really do want to. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 5 ++-- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 3 ++ .../dotc/transform/SyntheticMembers.scala | 9 ++---- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- .../tools/dotc/transform/patmat/Space.scala | 2 +- .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 30 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- compiler/test/dotty/tools/SignatureTest.scala | 4 +-- .../tools/dotc/core/ConstraintsTest.scala | 19 +++++------- 11 files changed, 37 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 2a66eda068c9..9678571e0106 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -770,7 +770,7 @@ object Trees { /** A type tree that represents an existing or inferred type */ case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile) extends DenotingTree[T] with TypTree[T] { - type ThisTree[+T <: Untyped] = TypeTree[T] + type ThisTree[+T <: Untyped] <: TypeTree[T] override def isEmpty: Boolean = !hasType override def toString: String = s"TypeTree${if (hasType) s"[$typeOpt]" else ""}" @@ -794,7 +794,8 @@ object Trees { * - as a (result-)type of an inferred ValDef or DefDef. * Every TypeVar is created as the type of one InferredTypeTree. */ - class InferredTypeTree[+T <: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T] + class InferredTypeTree[+T <: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]: + type ThisTree[+T <: Untyped] <: InferredTypeTree[T] /** ref.type */ case class SingletonTypeTree[+T <: Untyped] private[ast] (ref: Tree[T])(implicit @constructorOnly src: SourceFile) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 126e1da0a626..6000f504a582 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -3240,7 +3240,7 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) { def matchCase(cas: Type): MatchResult = trace(i"$scrut match ${MatchTypeTrace.caseText(cas)}", matchTypes, show = true) { val cas1 = cas match { case cas: HKTypeLambda => - caseLambda = constrained(cas) + caseLambda = constrained(cas, ast.tpd.EmptyTree)._1 caseLambda.resultType case _ => cas diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7957b979d7d1..eb878b430183 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4952,6 +4952,9 @@ object Types { if (inst.exists) inst else origin } + def wrapInTypeTree(owningTree: Tree)(using Context): InferredTypeTree = + new InferredTypeTree().withSpan(owningTree.span).withType(this) + override def computeHash(bs: Binders): Int = identityHash(bs) override def equals(that: Any): Boolean = this.eq(that.asInstanceOf[AnyRef]) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 20e138eb1e26..826787e153f4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -530,12 +530,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) { (rawRef, rawInfo) baseInfo match case tl: PolyType => - val (tl1, tpts) = constrained(tl, untpd.EmptyTree, alwaysAddTypeVars = true) - val targs = - for (tpt <- tpts) yield - tpt.tpe match { - case tvar: TypeVar => tvar.instantiate(fromBelow = false) - } + val tvars = constrained(tl) + val targs = for tvar <- tvars yield + tvar.instantiate(fromBelow = false) (baseRef.appliedTo(targs), extractParams(tl.instantiate(targs))) case methTpe => (baseRef, extractParams(methTpe)) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index f682b54ae731..2c552eec6d12 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -82,7 +82,7 @@ object TypeTestsCasts { case tp: TypeProxy => underlyingLambda(tp.superType) } val typeLambda = underlyingLambda(tycon) - val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) + val tvars = constrained(typeLambda) val P1 = tycon.appliedTo(tvars) debug.println("before " + ctx.typerState.constraint.show) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 06b593006acf..503f10522468 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -531,7 +531,7 @@ object SpaceEngine { val mt: MethodType = unapp.widen match { case mt: MethodType => mt case pt: PolyType => - val tvars = constrained(pt, EmptyTree)._2.tpes + val tvars = constrained(pt) val mt = pt.instantiate(tvars).asInstanceOf[MethodType] scrutineeTp <:< mt.paramInfos(0) // force type inference to infer a narrower type: could be singleton diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 618a6cec1a3e..89368d948448 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -317,7 +317,7 @@ object Inferencing { def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match case tl: TypeLambda => val (tl1, tvars) = constrained(tl, tree) - var tree1 = AppliedTypeTree(tree.withType(tl1), tvars) + val tree1 = AppliedTypeTree(tree.withType(tl1), tvars.map(_.wrapInTypeTree(tree))) tree1.tpe <:< pt if isFullyDefined(tree1.tpe, force = ForceDegree.failBottom) then tree1 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4086ccac1851..6a3f0d0ea73b 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -726,7 +726,7 @@ object ProtoTypes { tl: TypeLambda, owningTree: untpd.Tree, alwaysAddTypeVars: Boolean, nestingLevel: Int = ctx.nestingLevel - ): (TypeLambda, List[TypeTree]) = { + ): (TypeLambda, List[TypeVar]) = { val state = ctx.typerState val addTypeVars = alwaysAddTypeVars || !owningTree.isEmpty if (tl.isInstanceOf[PolyType]) @@ -734,33 +734,31 @@ object ProtoTypes { s"inconsistent: no typevars were added to committable constraint ${state.constraint}") // hk type lambdas can be added to constraints without typevars during match reduction - def newTypeVars(tl: TypeLambda): List[TypeTree] = - for (paramRef <- tl.paramRefs) - yield { - val tt = InferredTypeTree().withSpan(owningTree.span) + def newTypeVars(tl: TypeLambda): List[TypeVar] = + for paramRef <- tl.paramRefs + yield val tvar = TypeVar(paramRef, state, nestingLevel) state.ownedVars += tvar - tt.withType(tvar) - } + tvar val added = state.constraint.ensureFresh(tl) - val tvars = if (addTypeVars) newTypeVars(added) else Nil - TypeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]]) + val tvars = if addTypeVars then newTypeVars(added) else Nil + TypeComparer.addToConstraint(added, tvars) (added, tvars) } - def constrained(tl: TypeLambda, owningTree: untpd.Tree)(using Context): (TypeLambda, List[TypeTree]) = + def constrained(tl: TypeLambda, owningTree: untpd.Tree)(using Context): (TypeLambda, List[TypeVar]) = constrained(tl, owningTree, alwaysAddTypeVars = tl.isInstanceOf[PolyType] && ctx.typerState.isCommittable) - /** Same as `constrained(tl, EmptyTree)`, but returns just the created type lambda */ - def constrained(tl: TypeLambda)(using Context): TypeLambda = - constrained(tl, EmptyTree)._1 + /** Same as `constrained(tl, EmptyTree, alwaysAddTypeVars = true)`, but returns just the created type vars. */ + def constrained(tl: TypeLambda)(using Context): List[TypeVar] = + constrained(tl, EmptyTree, alwaysAddTypeVars = true)._2 /** Instantiate `tl` with fresh type variables added to the constraint. */ def instantiateWithTypeVars(tl: TypeLambda)(using Context): Type = - val targs = constrained(tl, ast.tpd.EmptyTree, alwaysAddTypeVars = true)._2 - tl.instantiate(targs.tpes) + val tvars = constrained(tl) + tl.instantiate(tvars) /** A fresh type variable added to the current constraint. * @param bounds The initial bounds of the variable @@ -779,7 +777,7 @@ object ProtoTypes { pt => bounds :: Nil, pt => represents.orElse(defn.AnyType)) constrained(poly, untpd.EmptyTree, alwaysAddTypeVars = true, nestingLevel) - ._2.head.tpe.asInstanceOf[TypeVar] + ._2.head /** If `param` was created using `newTypeVar(..., represents = X)`, returns X. * This is used in: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index da1d567810c8..557ec63d2f42 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4347,7 +4347,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer var typeArgs = tree match case Select(qual, nme.CONSTRUCTOR) => qual.tpe.widenDealias.argTypesLo.map(TypeTree(_)) case _ => Nil - if typeArgs.isEmpty then typeArgs = constrained(poly, tree)._2 + if typeArgs.isEmpty then typeArgs = constrained(poly, tree)._2.map(_.wrapInTypeTree(tree)) convertNewGenericArray(readapt(tree.appliedToTypeTrees(typeArgs))) case wtp => val isStructuralCall = wtp.isValueType && isStructuralTermSelectOrApply(tree) diff --git a/compiler/test/dotty/tools/SignatureTest.scala b/compiler/test/dotty/tools/SignatureTest.scala index fdbb1e8f3760..587d7098a0a7 100644 --- a/compiler/test/dotty/tools/SignatureTest.scala +++ b/compiler/test/dotty/tools/SignatureTest.scala @@ -63,7 +63,7 @@ class SignatureTest: | def tuple2(x: Foo *: (T | Tuple) & Foo): Unit = {} |""".stripMargin): val cls = requiredClass("A") - val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda], untpd.EmptyTree, alwaysAddTypeVars = true)._2.head.tpe + val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda]).head tvar <:< defn.TupleTypeRef val prefix = cls.typeRef.appliedTo(tvar) @@ -89,7 +89,7 @@ class SignatureTest: | def and(x: T & Foo): Unit = {} |""".stripMargin): val cls = requiredClass("A") - val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda], untpd.EmptyTree, alwaysAddTypeVars = true)._2.head.tpe + val tvar = constrained(cls.requiredMethod(nme.CONSTRUCTOR).info.asInstanceOf[TypeLambda]).head val prefix = cls.typeRef.appliedTo(tvar) val ref = prefix.select(cls.requiredMethod("and")).asInstanceOf[TermRef] diff --git a/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala b/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala index 9ae3fda8c6b9..4ca8e243dc0c 100644 --- a/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala +++ b/compiler/test/dotty/tools/dotc/core/ConstraintsTest.scala @@ -19,8 +19,7 @@ class ConstraintsTest: @Test def mergeParamsTransitivity: Unit = inCompilerContext(TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S, T, R]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t, r) = tvars.tpes + val List(s, t, r) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) val innerCtx = ctx.fresh.setExploreTyperState() inContext(innerCtx) { @@ -38,8 +37,7 @@ class ConstraintsTest: @Test def mergeBoundsTransitivity: Unit = inCompilerContext(TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S, T]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) val innerCtx = ctx.fresh.setExploreTyperState() inContext(innerCtx) { @@ -57,10 +55,9 @@ class ConstraintsTest: @Test def validBoundsInit: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S >: T <: T | Int, T <: String]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) - val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.asInstanceOf[TypeVar].origin): @unchecked + val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.origin): @unchecked assert(lo =:= defn.NothingType, i"Unexpected lower bound $lo for $t: ${ctx.typerState.constraint}") assert(hi =:= defn.StringType, i"Unexpected upper bound $hi for $t: ${ctx.typerState.constraint}") // used to be Any } @@ -68,12 +65,11 @@ class ConstraintsTest: @Test def validBoundsUnify: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait A { def foo[S >: T <: T | Int, T <: String | Int]: Any }") { - val tvars = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val List(s, t) = tvars.tpes + val List(s, t) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) s <:< t - val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.asInstanceOf[TypeVar].origin): @unchecked + val TypeBounds(lo, hi) = ctx.typerState.constraint.entry(t.origin): @unchecked assert(lo =:= defn.NothingType, i"Unexpected lower bound $lo for $t: ${ctx.typerState.constraint}") assert(hi =:= (defn.StringType | defn.IntType), i"Unexpected upper bound $hi for $t: ${ctx.typerState.constraint}") } @@ -81,8 +77,7 @@ class ConstraintsTest: @Test def validBoundsReplace: Unit = inCompilerContext( TestConfiguration.basicClasspath, scalaSources = "trait X; trait A { def foo[S <: U | X, T, U]: Any }") { - val tvarTrees = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda], EmptyTree, alwaysAddTypeVars = true)._2 - val tvars @ List(s, t, u) = tvarTrees.tpes.asInstanceOf[List[TypeVar]] + val tvars @ List(s, t, u) = constrained(requiredClass("A").typeRef.select("foo".toTermName).info.asInstanceOf[TypeLambda]) s =:= t t =:= u From 37cc60f6bd94ed4ce6e74aa1c95aa2fadfffdf3a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 3 Oct 2023 17:05:41 +0100 Subject: [PATCH 085/117] Consider extension methods in Space isSameUnapply --- .../tools/dotc/transform/patmat/Space.scala | 8 ++++-- tests/pos/i18601.scala | 19 ++++++++++++++ tests/pos/i18601b.scala | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i18601.scala create mode 100644 tests/pos/i18601b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 503f10522468..88c09eb4a521 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -516,10 +516,14 @@ object SpaceEngine { * We assume that unapply methods are pure, but the same method may * be called with different prefixes, thus behaving differently. */ - def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean = + def isSameUnapply(tp1: TermRef, tp2: TermRef)(using Context): Boolean = trace(i"isSameUnapply($tp1, $tp2)") { + def isStable(tp: TermRef) = + !tp.symbol.is(ExtensionMethod) // The "prefix" of an extension method may be, but the receiver isn't, so exclude + && tp.prefix.isStable // always assume two TypeTest[S, T].unapply are the same if they are equal in types - (tp1.prefix.isStable && tp2.prefix.isStable || tp1.symbol == defn.TypeTest_unapply) + (isStable(tp1) && isStable(tp2) || tp1.symbol == defn.TypeTest_unapply) && tp1 =:= tp2 + } /** Return term parameter types of the extractor `unapp`. * Parameter types of the case class type `tp`. Adapted from `unapplyPlan` in patternMatcher */ diff --git a/tests/pos/i18601.scala b/tests/pos/i18601.scala new file mode 100644 index 000000000000..63468e2d8c32 --- /dev/null +++ b/tests/pos/i18601.scala @@ -0,0 +1,19 @@ +//> using options -Werror +extension (sc: StringContext) + def m: StringContext = sc + def unapply(string: String): Option[String] = + val pattern = sc.parts.head + if string.length == pattern.length then Some(string) else None + +class Test: + def parse(x: PartialFunction[String, String]) = x + + val pf = parse { + case m"x$s" => s + case m"xx$s" => s // was: unreachable + } + + // proof that the second case isn't unreachable (matches "ab") + def t1 = pf.applyOrElse("a", _ => ".") // "a" + def t2 = pf.applyOrElse("ab", _ => ".") // "ab" + def t3 = pf.applyOrElse("abc", _ => ".") // "." diff --git a/tests/pos/i18601b.scala b/tests/pos/i18601b.scala new file mode 100644 index 000000000000..5646b909bd67 --- /dev/null +++ b/tests/pos/i18601b.scala @@ -0,0 +1,26 @@ +//> using options -Werror + +// like pos/i18601 +// but with a dedicated SC class +// that made the false positive redundancy warning go away + +extension (sc: StringContext) + def m: SC = SC(sc) + +class SC(sc: StringContext): + def unapply(string: String): Option[String] = + val pattern = sc.parts.head + if string.length == pattern.length then Some(string) else None + +class Test: + def parse(x: PartialFunction[String, String]) = x + + val pf = parse { + case m"x$s" => s + case m"xx$s" => s // was: not unreachable (as a counter-example) + } + + // proof that the second case isn't unreachable (matches "ab") + def t1 = pf.applyOrElse("a", _ => ".") // "a" + def t2 = pf.applyOrElse("ab", _ => ".") // "ab" + def t3 = pf.applyOrElse("abc", _ => ".") // "." From 1122bcce9a66773da53c36b61a29e719d3290aba Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Mon, 9 Oct 2023 16:42:47 -0400 Subject: [PATCH 086/117] Support implicit arguments before extractor method --- .../src/dotty/tools/dotc/transform/init/Objects.scala | 10 +++++++--- tests/init-global/neg/unapply-implicit-arg.scala | 11 +++++++++++ tests/init-global/neg/unapplySeq-implicit-arg.scala | 11 +++++++++++ tests/init-global/pos/unapply-implicit-arg-pos.scala | 11 +++++++++++ .../init-global/pos/unapplySeq-implicit-arg-pos.scala | 11 +++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/init-global/neg/unapply-implicit-arg.scala create mode 100644 tests/init-global/neg/unapplySeq-implicit-arg.scala create mode 100644 tests/init-global/pos/unapply-implicit-arg-pos.scala create mode 100644 tests/init-global/pos/unapplySeq-implicit-arg-pos.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 2f6b47ac100b..4367df9b9ac8 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1305,9 +1305,13 @@ object Objects: case select: Select => eval(select.qualifier, thisV, klass) - val implicitValues = evalArgs(implicits.map(Arg.apply), thisV, klass) - // TODO: implicit values may appear before and/or after the scrutinee parameter. - val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: implicitValues, funRef.prefix, superType = NoType, needResolve = true) + def implicitValuesBeforeScrutinee(fun: Tree): Contextual[List[ArgInfo]] = fun match + case Apply(f, implicitArgs) => + implicitValuesBeforeScrutinee(f) ++ evalArgs(implicitArgs.map(Arg.apply), thisV, klass) + case _ => List() + + val implicitValuesAfterScrtinee = evalArgs(implicits.map(Arg.apply), thisV, klass) + val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: (implicitValuesBeforeScrutinee(fun) ++ implicitValuesAfterScrtinee), funRef.prefix, superType = NoType, needResolve = true) if fun.symbol.name == nme.unapplySeq then var resultTp = unapplyResTp diff --git a/tests/init-global/neg/unapply-implicit-arg.scala b/tests/init-global/neg/unapply-implicit-arg.scala new file mode 100644 index 000000000000..5f2545c59e0b --- /dev/null +++ b/tests/init-global/neg/unapply-implicit-arg.scala @@ -0,0 +1,11 @@ +class Foo + +object Bar { + def unapply(using Foo)(pair: (Int, Int))(using Foo): Option[Int] = + if pair._1 == 0 then Some(pair._1) else Some(pair._2) + given Foo = new Foo + val i1: Int = 0 + val i2: Int = (i1, i2) match // error + case Bar(i) => i + case _ => 0 +} \ No newline at end of file diff --git a/tests/init-global/neg/unapplySeq-implicit-arg.scala b/tests/init-global/neg/unapplySeq-implicit-arg.scala new file mode 100644 index 000000000000..600323b114b6 --- /dev/null +++ b/tests/init-global/neg/unapplySeq-implicit-arg.scala @@ -0,0 +1,11 @@ +class Foo + +object Bar { + def unapplySeq(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Seq[Int]] = + if pair._1 == 0 then Some(Seq(pair._1)) else Some(Seq(pair._2)) + given Foo = new Foo + val i1: Int = 0 + val i2: Int = (i1, i2) match // error + case Bar(i) => i + case _ => 0 +} diff --git a/tests/init-global/pos/unapply-implicit-arg-pos.scala b/tests/init-global/pos/unapply-implicit-arg-pos.scala new file mode 100644 index 000000000000..2b954256ab1a --- /dev/null +++ b/tests/init-global/pos/unapply-implicit-arg-pos.scala @@ -0,0 +1,11 @@ +class Foo + +object Bar { + def unapply(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Int] = + if pair._1 == 0 then Some(pair._1) else Some(pair._2) + given Foo = new Foo + val i1: Int = 0 + val i2: Int = (i1, i1) match + case Bar(i) => i + case _ => 0 +} \ No newline at end of file diff --git a/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala b/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala new file mode 100644 index 000000000000..76b5502caf09 --- /dev/null +++ b/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala @@ -0,0 +1,11 @@ +class Foo + +object Bar { + def unapplySeq(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Seq[Int]] = + if pair._1 == 0 then Some(Seq(pair._1)) else Some(Seq(pair._2)) + given Foo = new Foo + val i1: Int = 0 + val i2: Int = (i1, i1) match + case Bar(i) => i + case _ => 0 +} From fcec9130ee569dcc48ad897355f3bc0ec3f7ac83 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Fri, 13 Oct 2023 16:54:52 -0400 Subject: [PATCH 087/117] Correct argument order in evalPattern and add test --- .../dotty/tools/dotc/transform/init/Objects.scala | 2 +- tests/init-global/neg/unapply-implicit-arg.scala | 13 ++++++++----- tests/init-global/neg/unapply-implicit-arg2.scala | 14 ++++++++++++++ tests/init-global/neg/unapply-implicit-arg3.scala | 14 ++++++++++++++ .../init-global/neg/unapplySeq-implicit-arg.scala | 13 ++++++++----- .../init-global/neg/unapplySeq-implicit-arg2.scala | 10 ++++++++++ .../init-global/neg/unapplySeq-implicit-arg3.scala | 12 ++++++++++++ .../init-global/pos/unapply-implicit-arg-pos.scala | 13 ++++++++----- .../pos/unapplySeq-implicit-arg-pos.scala | 13 ++++++++----- 9 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 tests/init-global/neg/unapply-implicit-arg2.scala create mode 100644 tests/init-global/neg/unapply-implicit-arg3.scala create mode 100644 tests/init-global/neg/unapplySeq-implicit-arg2.scala create mode 100644 tests/init-global/neg/unapplySeq-implicit-arg3.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 4367df9b9ac8..e43107109610 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1311,7 +1311,7 @@ object Objects: case _ => List() val implicitValuesAfterScrtinee = evalArgs(implicits.map(Arg.apply), thisV, klass) - val unapplyRes = call(receiver, funRef.symbol, TraceValue(scrutinee, summon[Trace]) :: (implicitValuesBeforeScrutinee(fun) ++ implicitValuesAfterScrtinee), funRef.prefix, superType = NoType, needResolve = true) + val unapplyRes = call(receiver, funRef.symbol, implicitValuesBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitValuesAfterScrtinee), funRef.prefix, superType = NoType, needResolve = true) if fun.symbol.name == nme.unapplySeq then var resultTp = unapplyResTp diff --git a/tests/init-global/neg/unapply-implicit-arg.scala b/tests/init-global/neg/unapply-implicit-arg.scala index 5f2545c59e0b..bf41fbbf9412 100644 --- a/tests/init-global/neg/unapply-implicit-arg.scala +++ b/tests/init-global/neg/unapply-implicit-arg.scala @@ -1,11 +1,14 @@ -class Foo - object Bar { - def unapply(using Foo)(pair: (Int, Int))(using Foo): Option[Int] = - if pair._1 == 0 then Some(pair._1) else Some(pair._2) + class Foo { + def m1(i: Int) = i+1 + def m2(i: Int) = i+2 + } + def unapply(using f1: Foo)(i: Int): Option[Int] = + if i == 0 then Some(f1.m1(i)) else Some(f1.m2(i)) + given Foo = new Foo val i1: Int = 0 - val i2: Int = (i1, i2) match // error + val i2: Int = i2 match // error case Bar(i) => i case _ => 0 } \ No newline at end of file diff --git a/tests/init-global/neg/unapply-implicit-arg2.scala b/tests/init-global/neg/unapply-implicit-arg2.scala new file mode 100644 index 000000000000..c0a16faac377 --- /dev/null +++ b/tests/init-global/neg/unapply-implicit-arg2.scala @@ -0,0 +1,14 @@ +object Bar { + class Foo { + def m1(i: Int) = i+1 + def m2(i: Int) = i+2 + } + def unapply(using f1: Foo)(i: Int): Option[Int] = + if i == 0 then Some(f1.m1(i1)) else Some(f1.m2(i2)) // error + + given Foo = new Foo + val i1: Int = 0 + val i2: Int = i1 match + case Bar(i) => i + case _ => 0 +} diff --git a/tests/init-global/neg/unapply-implicit-arg3.scala b/tests/init-global/neg/unapply-implicit-arg3.scala new file mode 100644 index 000000000000..efa348f6cfdb --- /dev/null +++ b/tests/init-global/neg/unapply-implicit-arg3.scala @@ -0,0 +1,14 @@ +object Bar { + class Foo { + def m1(i: Int) = i + i1 + def m2(i: Int) = i + i2 // error + } + def unapply(using f1: Foo)(i: Int): Option[Int] = + if i == 0 then Some(f1.m1(i)) else Some(f1.m2(i)) + + given Foo = new Foo + val i1: Int = 0 + val i2: Int = i1 match + case Bar(i) => i + case _ => 0 +} diff --git a/tests/init-global/neg/unapplySeq-implicit-arg.scala b/tests/init-global/neg/unapplySeq-implicit-arg.scala index 600323b114b6..e58635a3090f 100644 --- a/tests/init-global/neg/unapplySeq-implicit-arg.scala +++ b/tests/init-global/neg/unapplySeq-implicit-arg.scala @@ -1,11 +1,14 @@ -class Foo - object Bar { - def unapplySeq(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Seq[Int]] = - if pair._1 == 0 then Some(Seq(pair._1)) else Some(Seq(pair._2)) + class Foo { + def m1(seq: Seq[Int]) = 1 +: seq + def m2(seq: Seq[Int]) = 2 +: seq + } + def unapplySeq(using f1: Foo)(seqi: Seq[Int]): Option[Seq[Int]] = + if seqi(0) == 0 then Some(f1.m1(seqi)) else Some(f1.m2(seqi)) + given Foo = new Foo val i1: Int = 0 - val i2: Int = (i1, i2) match // error + val i2: Int = Seq(i2) match // error case Bar(i) => i case _ => 0 } diff --git a/tests/init-global/neg/unapplySeq-implicit-arg2.scala b/tests/init-global/neg/unapplySeq-implicit-arg2.scala new file mode 100644 index 000000000000..35f5105b84d2 --- /dev/null +++ b/tests/init-global/neg/unapplySeq-implicit-arg2.scala @@ -0,0 +1,10 @@ +object Bar { + class Foo + def unapplySeq(using f1: Foo)(using f2: Foo)(seqi: Seq[Int])(using Foo): Option[Seq[Int]] = + Some(i1 +: seqi) // error + given Foo = new Foo + val i1: Int = Seq(0) match { + case Bar(i) => i + case _ => 0 + } +} diff --git a/tests/init-global/neg/unapplySeq-implicit-arg3.scala b/tests/init-global/neg/unapplySeq-implicit-arg3.scala new file mode 100644 index 000000000000..2b5cdd327e57 --- /dev/null +++ b/tests/init-global/neg/unapplySeq-implicit-arg3.scala @@ -0,0 +1,12 @@ +object Bar { + class Foo { + def m(seq: Seq[Int]) = i1 +: seq // error + } + def unapplySeq(using f1: Foo)(seqi: Seq[Int])(using Foo): Option[Seq[Int]] = + Some(f1.m(seqi)) + given Foo = new Foo + val i1: Int = Seq(0) match { + case Bar(i, _) => i + case _ => 0 + } +} diff --git a/tests/init-global/pos/unapply-implicit-arg-pos.scala b/tests/init-global/pos/unapply-implicit-arg-pos.scala index 2b954256ab1a..5573a2210160 100644 --- a/tests/init-global/pos/unapply-implicit-arg-pos.scala +++ b/tests/init-global/pos/unapply-implicit-arg-pos.scala @@ -1,11 +1,14 @@ -class Foo - object Bar { - def unapply(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Int] = - if pair._1 == 0 then Some(pair._1) else Some(pair._2) + class Foo { + def m1(i: Int) = i + i1 + def m2(i: Int) = i + 2 + } + def unapply(using f1: Foo)(using f2: Foo)(i: Int)(using f3: Foo): Option[Int] = + if i == 0 then Some(f1.m1(i1) + f3.m1(i1)) else Some(f2.m2(i) + f3.m2(i)) + given Foo = new Foo val i1: Int = 0 - val i2: Int = (i1, i1) match + val i2: Int = i1 match case Bar(i) => i case _ => 0 } \ No newline at end of file diff --git a/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala b/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala index 76b5502caf09..08e69d4ff3bc 100644 --- a/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala +++ b/tests/init-global/pos/unapplySeq-implicit-arg-pos.scala @@ -1,11 +1,14 @@ -class Foo - object Bar { - def unapplySeq(using Foo)(using Foo)(pair: (Int, Int))(using Foo): Option[Seq[Int]] = - if pair._1 == 0 then Some(Seq(pair._1)) else Some(Seq(pair._2)) + class Foo { + def m1(seq: Seq[Int]) = 0 +: seq + def m2(seq: Seq[Int]) = i1 +: seq + } + def unapplySeq(using f1: Foo)(using f2: Foo)(seqi: Seq[Int])(using f3: Foo): Option[Seq[Int]] = + if seqi(0) == 0 then Some(f1.m1(seqi)) else Some(f2.m2(seqi)) + given Foo = new Foo val i1: Int = 0 - val i2: Int = (i1, i1) match + val i2: Int = Seq(i1) match case Bar(i) => i case _ => 0 } From 5683b9ba8750744c2bf5b4dd5a8c1656ea444f4f Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Sat, 14 Oct 2023 19:08:00 -0400 Subject: [PATCH 088/117] Address comments --- .../src/dotty/tools/dotc/transform/init/Objects.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index e43107109610..1d688e8c2888 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1305,13 +1305,14 @@ object Objects: case select: Select => eval(select.qualifier, thisV, klass) - def implicitValuesBeforeScrutinee(fun: Tree): Contextual[List[ArgInfo]] = fun match + def implicitArgsBeforeScrutinee(fun: Tree): Contextual[List[ArgInfo]] = fun match case Apply(f, implicitArgs) => - implicitValuesBeforeScrutinee(f) ++ evalArgs(implicitArgs.map(Arg.apply), thisV, klass) + implicitArgsBeforeScrutinee(f) ++ evalArgs(implicitArgs.map(Arg.apply), thisV, klass) case _ => List() - val implicitValuesAfterScrtinee = evalArgs(implicits.map(Arg.apply), thisV, klass) - val unapplyRes = call(receiver, funRef.symbol, implicitValuesBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitValuesAfterScrtinee), funRef.prefix, superType = NoType, needResolve = true) + val implicitArgsAfterScrtinee = evalArgs(implicits.map(Arg.apply), thisV, klass) + val args = implicitArgsBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitArgsAfterScrtinee) + val unapplyRes = call(receiver, funRef.symbol, args, funRef.prefix, superType = NoType, needResolve = true) if fun.symbol.name == nme.unapplySeq then var resultTp = unapplyResTp From 74b10cb96153e204058d6015bbe603e007dd61ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lhot=C3=A1k?= Date: Mon, 16 Oct 2023 14:52:40 -0400 Subject: [PATCH 089/117] Update compiler/src/dotty/tools/dotc/transform/init/Objects.scala --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 1d688e8c2888..15f64c491a51 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1310,7 +1310,7 @@ object Objects: implicitArgsBeforeScrutinee(f) ++ evalArgs(implicitArgs.map(Arg.apply), thisV, klass) case _ => List() - val implicitArgsAfterScrtinee = evalArgs(implicits.map(Arg.apply), thisV, klass) + val implicitArgsAfterScrutinee = evalArgs(implicits.map(Arg.apply), thisV, klass) val args = implicitArgsBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitArgsAfterScrtinee) val unapplyRes = call(receiver, funRef.symbol, args, funRef.prefix, superType = NoType, needResolve = true) From 0a19e4f210ffc95219e6d0594cb7562164b5a1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lhot=C3=A1k?= Date: Mon, 16 Oct 2023 14:52:46 -0400 Subject: [PATCH 090/117] Update compiler/src/dotty/tools/dotc/transform/init/Objects.scala --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 15f64c491a51..8556ced63008 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -1311,7 +1311,7 @@ object Objects: case _ => List() val implicitArgsAfterScrutinee = evalArgs(implicits.map(Arg.apply), thisV, klass) - val args = implicitArgsBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitArgsAfterScrtinee) + val args = implicitArgsBeforeScrutinee(fun) ++ (TraceValue(scrutinee, summon[Trace]) :: implicitArgsAfterScrutinee) val unapplyRes = call(receiver, funRef.symbol, args, funRef.prefix, superType = NoType, needResolve = true) if fun.symbol.name == nme.unapplySeq then From 593dc5e2e5f9b956594519624a52262bd5c33eca Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 13 Oct 2023 18:47:05 +0200 Subject: [PATCH 091/117] Fix variance loophole for private vars In Scala 2 a setter was created at Typer for private, non-local vars. Variance checking then worked on the setter. But in Scala 3, the setter is only created later, which caused a loophole for variance checking. This PR does actually better than Scala 2 in the following sense: A private variable counts as an invariant occurrence only if it is assigned with a selector different from `this`. Or conversely, a variable containing a covariant type parameter in its type can be read from different objects, but all assignments must be via this. The motivation is that such variables effectively behave like vals for the purposes of variance checking. --- .../tools/dotc/core/SymDenotations.scala | 23 +++++++++---------- .../tools/dotc/transform/PostTyper.scala | 21 ++++++++++++++++- .../tools/dotc/typer/VarianceChecker.scala | 21 +++++++++++------ tests/neg/i18588.check | 4 ++++ tests/neg/i18588.scala | 22 ++++++++++++++++++ 5 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 tests/neg/i18588.check create mode 100644 tests/neg/i18588.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 38de11915095..e59504aa60b3 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -868,6 +868,17 @@ object SymDenotations { final def isNullableClassAfterErasure(using Context): Boolean = isClass && !isValueClass && !is(ModuleClass) && symbol != defn.NothingClass + /** Is `pre` the same as C.this, where C is exactly the owner of this symbol, + * or, if this symbol is protected, a subclass of the owner? + */ + def isCorrectThisType(pre: Type)(using Context): Boolean = pre match + case pre: ThisType => + (pre.cls eq owner) || this.is(Protected) && pre.cls.derivesFrom(owner) + case pre: TermRef => + pre.symbol.moduleClass == owner + case _ => + false + /** Is this definition accessible as a member of tree with type `pre`? * @param pre The type of the tree from which the selection is made * @param superAccess Access is via super @@ -892,18 +903,6 @@ object SymDenotations { (linked ne NoSymbol) && accessWithin(linked) } - /** Is `pre` the same as C.thisThis, where C is exactly the owner of this symbol, - * or, if this symbol is protected, a subclass of the owner? - */ - def isCorrectThisType(pre: Type): Boolean = pre match { - case pre: ThisType => - (pre.cls eq owner) || this.is(Protected) && pre.cls.derivesFrom(owner) - case pre: TermRef => - pre.symbol.moduleClass == owner - case _ => - false - } - /** Is protected access to target symbol permitted? */ def isProtectedAccessOK: Boolean = inline def fail(str: String): false = diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 4f3f92eddb2f..46f8503e9140 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -109,6 +109,13 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => try op finally noCheckNews = saved } + /** The set of all private class variables that are assigned + * when selected with a qualifier other than the `this` of the owning class. + * Such variables can contain only invariant type parameters in + * their types. + */ + private var privateVarsSetNonLocally: Set[Symbol] = Set() + def isCheckable(t: New): Boolean = !inJavaAnnot && !noCheckNews.contains(t) /** Mark parameter accessors that are aliases of like-named parameters @@ -149,6 +156,8 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => private def processMemberDef(tree: Tree)(using Context): tree.type = { val sym = tree.symbol Checking.checkValidOperator(sym) + if sym.isClass then + VarianceChecker.check(tree, privateVarsSetNonLocally) sym.transformAnnotations(transformAnnot) sym.defTree = tree tree @@ -257,6 +266,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => } } + /** Update privateVarsSetNonLocally is symbol is a private variable + * that is selected from something other than `this` when assigned + */ + private def markVarAccess(tree: Tree, qual: Tree)(using Context): Unit = + val sym = tree.symbol + if sym.is(Private, butNot = Local) && !sym.isCorrectThisType(qual.tpe) then + privateVarsSetNonLocally += sym + def checkNoConstructorProxy(tree: Tree)(using Context): Unit = if tree.symbol.is(ConstructorProxy) then report.error(em"constructor proxy ${tree.symbol} cannot be used as a value", tree.srcPos) @@ -397,7 +414,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => registerIfHasMacroAnnotations(tree) val sym = tree.symbol if (sym.isClass) - VarianceChecker.check(tree) annotateExperimental(sym) checkMacroAnnotation(sym) if sym.isOneOf(GivenOrImplicit) then @@ -474,6 +490,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tpe => tpe } ) + case Assign(lhs @ Select(qual, _), _) => + markVarAccess(lhs, qual) + super.transform(tree) case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index a5e0b62094ef..30e426c55da1 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -19,8 +19,8 @@ import printing.Formatting.hl */ object VarianceChecker { case class VarianceError(tvar: Symbol, required: Variance) - def check(tree: tpd.Tree)(using Context): Unit = - VarianceChecker().Traverser.traverse(tree) + def check(tree: tpd.Tree, privateVarsSetNonLocally: collection.Set[Symbol])(using Context): Unit = + VarianceChecker(privateVarsSetNonLocally).Traverser.traverse(tree) /** Check that variances of type lambda correspond to their occurrences in its body. * Note: this is achieved by a mechanism separate from checking class type parameters. @@ -62,7 +62,7 @@ object VarianceChecker { end checkLambda } -class VarianceChecker(using Context) { +class VarianceChecker(privateVarsSetNonLocally: collection.Set[Symbol])(using Context) { import VarianceChecker._ import tpd._ @@ -148,12 +148,19 @@ class VarianceChecker(using Context) { case _ => apply(None, info) - def validateDefinition(base: Symbol): Option[VarianceError] = { - val saved = this.base + def validateDefinition(base: Symbol): Option[VarianceError] = + val savedBase = this.base this.base = base + val savedVariance = variance + def isLocal = + base.isAllOf(PrivateLocal) + || base.is(Private) && !privateVarsSetNonLocally.contains(base) + if base.is(Mutable, butNot = Method) && !isLocal then + variance = 0 try checkInfo(base.info) - finally this.base = saved - } + finally + this.base = savedBase + this.variance = savedVariance } private object Traverser extends TreeTraverser { diff --git a/tests/neg/i18588.check b/tests/neg/i18588.check new file mode 100644 index 000000000000..f08aae89c44c --- /dev/null +++ b/tests/neg/i18588.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i18588.scala:7:14 ---------------------------------------------------------------------------------- +7 | private var cached: A = value // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | covariant type A occurs in invariant position in type A of variable cached diff --git a/tests/neg/i18588.scala b/tests/neg/i18588.scala new file mode 100644 index 000000000000..d57de4f695b8 --- /dev/null +++ b/tests/neg/i18588.scala @@ -0,0 +1,22 @@ +class ROBox[+A](value: A) { + private var cached: A = value + def get: A = ROBox[A](value).cached +} + +class Box[+A](value: A) { + private var cached: A = value // error + def get: A = cached + + def put[AA >: A](value: AA): Unit = { + val box: Box[AA] = this + box.cached = value + } +} + +trait Animal +object Dog extends Animal +object Cat extends Animal + +val dogBox: Box[Dog.type] = new Box(Dog) +val _ = dogBox.put(Cat) +val dog: Dog.type = dogBox.get From 49d112e2605b4cfd99b11aa378128a34367561c9 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 16 Oct 2023 20:20:34 +0200 Subject: [PATCH 092/117] Revised scheme:Mark non-local assigns at Typer We need to mark such assigns in the phase before PostTyper since they can appear in companion objects that come after the variable declaration. --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 4 ++-- .../tools/dotc/transform/PostTyper.scala | 21 +--------------- .../src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++++++++++----- .../tools/dotc/typer/VarianceChecker.scala | 9 +++---- .../internal/AssignedNonLocally.scala | 7 ++++++ tests/neg/i18588.check | 4 ++++ tests/neg/i18588.scala | 20 ++++++++++++++++ 8 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 library/src/scala/annotation/internal/AssignedNonLocally.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d2c141d72f47..6161358d815d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -996,6 +996,7 @@ class Definitions { // Annotation classes @tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions") @tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault") + @tu lazy val AssignedNonLocallyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AssignedNonLocally") @tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty") @tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty") @tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e59504aa60b3..542c14fcb3db 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -871,7 +871,7 @@ object SymDenotations { /** Is `pre` the same as C.this, where C is exactly the owner of this symbol, * or, if this symbol is protected, a subclass of the owner? */ - def isCorrectThisType(pre: Type)(using Context): Boolean = pre match + def isAccessPrivilegedThisType(pre: Type)(using Context): Boolean = pre match case pre: ThisType => (pre.cls eq owner) || this.is(Protected) && pre.cls.derivesFrom(owner) case pre: TermRef => @@ -936,7 +936,7 @@ object SymDenotations { || boundary.isRoot || (accessWithin(boundary) || accessWithinLinked(boundary)) && ( !this.is(Local) - || isCorrectThisType(pre) + || isAccessPrivilegedThisType(pre) || canBeLocal(name, flags) && { resetFlag(Local) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 46f8503e9140..4f3f92eddb2f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -109,13 +109,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => try op finally noCheckNews = saved } - /** The set of all private class variables that are assigned - * when selected with a qualifier other than the `this` of the owning class. - * Such variables can contain only invariant type parameters in - * their types. - */ - private var privateVarsSetNonLocally: Set[Symbol] = Set() - def isCheckable(t: New): Boolean = !inJavaAnnot && !noCheckNews.contains(t) /** Mark parameter accessors that are aliases of like-named parameters @@ -156,8 +149,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => private def processMemberDef(tree: Tree)(using Context): tree.type = { val sym = tree.symbol Checking.checkValidOperator(sym) - if sym.isClass then - VarianceChecker.check(tree, privateVarsSetNonLocally) sym.transformAnnotations(transformAnnot) sym.defTree = tree tree @@ -266,14 +257,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => } } - /** Update privateVarsSetNonLocally is symbol is a private variable - * that is selected from something other than `this` when assigned - */ - private def markVarAccess(tree: Tree, qual: Tree)(using Context): Unit = - val sym = tree.symbol - if sym.is(Private, butNot = Local) && !sym.isCorrectThisType(qual.tpe) then - privateVarsSetNonLocally += sym - def checkNoConstructorProxy(tree: Tree)(using Context): Unit = if tree.symbol.is(ConstructorProxy) then report.error(em"constructor proxy ${tree.symbol} cannot be used as a value", tree.srcPos) @@ -414,6 +397,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => registerIfHasMacroAnnotations(tree) val sym = tree.symbol if (sym.isClass) + VarianceChecker.check(tree) annotateExperimental(sym) checkMacroAnnotation(sym) if sym.isOneOf(GivenOrImplicit) then @@ -490,9 +474,6 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case tpe => tpe } ) - case Assign(lhs @ Select(qual, _), _) => - markVarAccess(lhs, qual) - super.transform(tree) case Typed(Ident(nme.WILDCARD), _) => withMode(Mode.Pattern)(super.transform(tree)) // The added mode signals that bounds in a pattern need not diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 557ec63d2f42..964817f3f3ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1103,6 +1103,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // allow assignments from the primary constructor to class fields ctx.owner.name.is(TraitSetterName) || ctx.owner.isStaticConstructor + /** Mark private variables that are assigned with a prefix other than + * the `this` type of their owner with a `annotation.internal.AssignedNonLocally` + * annotation. The annotation influences the variance check for these + * variables, which is done at PostTyper. It will be removed after the + * variance check. + */ + def rememberNonLocalAssignToPrivate(sym: Symbol) = lhs1 match + case Select(qual, _) + if sym.is(Private, butNot = Local) && !sym.isAccessPrivilegedThisType(qual.tpe) => + sym.addAnnotation(Annotation(defn.AssignedNonLocallyAnnot, lhs1.span)) + case _ => + lhsCore match case Apply(fn, _) if fn.symbol.is(ExtensionMethod) => def toSetter(fn: Tree): untpd.Tree = fn match @@ -1136,15 +1148,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => lhsCore.tpe match { case ref: TermRef => val lhsVal = lhsCore.denot.suchThat(!_.is(Method)) - if (canAssign(lhsVal.symbol)) { - // lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsVal.symbol + val lhsSym = lhsVal.symbol + if canAssign(lhsSym) then + rememberNonLocalAssignToPrivate(lhsSym) + // lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsSym // This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala val lhsBounds = - TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner) + TypeBounds.lower(lhsSym.info).asSeenFrom(ref.prefix, lhsSym.owner) assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound))) .computeAssignNullable() - } - else { + else val pre = ref.prefix val setterName = ref.name.setterName val setter = pre.member(setterName) @@ -1157,7 +1170,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => reassignmentToVal } - } case TryDynamicCallType => typedDynamicAssign(tree, pt) case tpe => diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 30e426c55da1..21fa9eed0df4 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -19,8 +19,8 @@ import printing.Formatting.hl */ object VarianceChecker { case class VarianceError(tvar: Symbol, required: Variance) - def check(tree: tpd.Tree, privateVarsSetNonLocally: collection.Set[Symbol])(using Context): Unit = - VarianceChecker(privateVarsSetNonLocally).Traverser.traverse(tree) + def check(tree: tpd.Tree)(using Context): Unit = + VarianceChecker().Traverser.traverse(tree) /** Check that variances of type lambda correspond to their occurrences in its body. * Note: this is achieved by a mechanism separate from checking class type parameters. @@ -62,7 +62,7 @@ object VarianceChecker { end checkLambda } -class VarianceChecker(privateVarsSetNonLocally: collection.Set[Symbol])(using Context) { +class VarianceChecker(using Context) { import VarianceChecker._ import tpd._ @@ -154,8 +154,9 @@ class VarianceChecker(privateVarsSetNonLocally: collection.Set[Symbol])(using Co val savedVariance = variance def isLocal = base.isAllOf(PrivateLocal) - || base.is(Private) && !privateVarsSetNonLocally.contains(base) + || base.is(Private) && !base.hasAnnotation(defn.AssignedNonLocallyAnnot) if base.is(Mutable, butNot = Method) && !isLocal then + base.removeAnnotation(defn.AssignedNonLocallyAnnot) variance = 0 try checkInfo(base.info) finally diff --git a/library/src/scala/annotation/internal/AssignedNonLocally.scala b/library/src/scala/annotation/internal/AssignedNonLocally.scala new file mode 100644 index 000000000000..8095ba91cfee --- /dev/null +++ b/library/src/scala/annotation/internal/AssignedNonLocally.scala @@ -0,0 +1,7 @@ +package scala.annotation +package internal + +/** An annotation to indicate that a private `var` was assigned with a prefix + * other than the `this` type of its owner. + */ +class AssignedNonLocally() extends Annotation diff --git a/tests/neg/i18588.check b/tests/neg/i18588.check index f08aae89c44c..5f7d6181a93c 100644 --- a/tests/neg/i18588.check +++ b/tests/neg/i18588.check @@ -2,3 +2,7 @@ 7 | private var cached: A = value // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | covariant type A occurs in invariant position in type A of variable cached +-- Error: tests/neg/i18588.scala:17:14 --------------------------------------------------------------------------------- +17 | private var cached: A = value // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | covariant type A occurs in invariant position in type A of variable cached diff --git a/tests/neg/i18588.scala b/tests/neg/i18588.scala index d57de4f695b8..e8c89c1b06f1 100644 --- a/tests/neg/i18588.scala +++ b/tests/neg/i18588.scala @@ -13,6 +13,19 @@ class Box[+A](value: A) { } } +class BoxWithCompanion[+A](value: A) { + private var cached: A = value // error + def get: A = cached +} + +class BoxValid[+A](value: A, orig: A) { + private var cached: A = value // ok + def get: A = cached + + def reset(): Unit = + cached = orig // ok: mutated through this prefix +} + trait Animal object Dog extends Animal object Cat extends Animal @@ -20,3 +33,10 @@ object Cat extends Animal val dogBox: Box[Dog.type] = new Box(Dog) val _ = dogBox.put(Cat) val dog: Dog.type = dogBox.get + + +object BoxWithCompanion { + def put[A](box: BoxWithCompanion[A], value: A): Unit = { + box.cached = value + } +} \ No newline at end of file From f6e2c869e973d8794c600c8544e8a762092e40a9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Oct 2023 15:16:00 +0200 Subject: [PATCH 093/117] Refactor CannotBeAccessed/isAccessibleFrom --- .../tools/dotc/core/SymDenotations.scala | 21 ++++--------------- .../dotty/tools/dotc/reporting/messages.scala | 10 +++++++-- tests/neg/i18686.check | 13 ++++++++++++ tests/neg/i18686.scala | 13 ++++++++++++ 4 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 tests/neg/i18686.check create mode 100644 tests/neg/i18686.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 542c14fcb3db..395164b1dd48 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -888,7 +888,7 @@ object SymDenotations { * As a side effect, drop Local flags of members that are not accessed via the ThisType * of their owner. */ - final def isAccessibleFrom(pre: Type, superAccess: Boolean = false, whyNot: StringBuffer | Null = null)(using Context): Boolean = { + final def isAccessibleFrom(pre: Type, superAccess: Boolean = false)(using Context): Boolean = { /** Are we inside definition of `boundary`? * If this symbol is Java defined, package structure is interpreted to be flat. @@ -905,26 +905,13 @@ object SymDenotations { /** Is protected access to target symbol permitted? */ def isProtectedAccessOK: Boolean = - inline def fail(str: String): false = - if whyNot != null then whyNot.nn.append(str) - false val cls = owner.enclosingSubClass if !cls.exists then - if pre.termSymbol.isPackageObject && accessWithin(pre.termSymbol.owner) then - true - else - val encl = if ctx.owner.isConstructor then ctx.owner.enclosingClass.owner.enclosingClass else ctx.owner.enclosingClass - val location = if owner.is(Final) then owner.showLocated else owner.showLocated + " or one of its subclasses" - fail(i""" - | Protected $this can only be accessed from $location.""") - else if isType || pre.derivesFrom(cls) || isConstructor || owner.is(ModuleClass) then + pre.termSymbol.isPackageObject && accessWithin(pre.termSymbol.owner) + else // allow accesses to types from arbitrary subclasses fixes #4737 // don't perform this check for static members - true - else - val location = if cls.is(Final) then cls.showLocated else cls.showLocated + " or one of its subclasses" - fail(i""" - | Protected $this can only be accessed from $location.""") + isType || pre.derivesFrom(cls) || isConstructor || owner.is(ModuleClass) end isProtectedAccessOK if pre eq NoPrefix then true diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 6d115c0e18a9..db7f00914ddc 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -876,7 +876,7 @@ extends Message(PatternMatchExhaustivityID) { val pathes = List( ActionPatch( - srcPos = endPos, + srcPos = endPos, replacement = uncoveredCases.map(c => indent(s"case $c => ???", startColumn)) .mkString("\n", "\n", "") ), @@ -2986,7 +2986,13 @@ extends ReferenceMsg(CannotBeAccessedID): i"none of the overloaded alternatives named $name can" val where = if (ctx.owner.exists) i" from ${ctx.owner.enclosingClass}" else "" val whyNot = new StringBuffer - alts.foreach(_.isAccessibleFrom(pre, superAccess, whyNot)) + for alt <- alts do + if alt.is(Protected) then + val cls = alt.owner.enclosingSubClass + val owner = if cls.exists then cls else alt.owner + val location = if owner.is(Final) then owner.showLocated else owner.showLocated + " or one of its subclasses" + whyNot.append(i""" + | Protected $alt can only be accessed from $location.""") i"$whatCanNot be accessed as a member of $pre$where.$whyNot" def explain(using Context) = "" diff --git a/tests/neg/i18686.check b/tests/neg/i18686.check new file mode 100644 index 000000000000..cfecb5522248 --- /dev/null +++ b/tests/neg/i18686.check @@ -0,0 +1,13 @@ +-- [E173] Reference Error: tests/neg/i18686.scala:9:16 ----------------------------------------------------------------- +9 | println(Foo.Bar1) // error + | ^^^^^^^^ + | value Bar1 cannot be accessed as a member of Foo.type from object Main. +-- [E173] Reference Error: tests/neg/i18686.scala:10:16 ---------------------------------------------------------------- +10 | println(Foo.Bar2) // error + | ^^^^^^^^ + | value Bar2 cannot be accessed as a member of Foo.type from object Main. +-- [E173] Reference Error: tests/neg/i18686.scala:11:16 ---------------------------------------------------------------- +11 | println(Foo.Bar3) // error + | ^^^^^^^^ + | value Bar3 cannot be accessed as a member of Foo.type from object Main. + | Protected value Bar3 can only be accessed from object Foo. diff --git a/tests/neg/i18686.scala b/tests/neg/i18686.scala new file mode 100644 index 000000000000..d6a45b171394 --- /dev/null +++ b/tests/neg/i18686.scala @@ -0,0 +1,13 @@ +object Foo: + private val Bar1: Int = 3 + private[Foo] val Bar2: Int = 3 + protected val Bar3: Int = 3 +end Foo + +object Main: + def main(args: Array[String]): Unit = + println(Foo.Bar1) // error + println(Foo.Bar2) // error + println(Foo.Bar3) // error + end main +end Main From a69eb54c8644077b5a9de979cb35ab6b42c64aa1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Oct 2023 15:34:18 +0200 Subject: [PATCH 094/117] Explain accessible scope of private members in error message Fixes #18686 --- community-build/community-projects/stdLib213 | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 23 ++++++++---- compiler/test-resources/repl/i1370 | 1 + tests/neg/i12573.check | 2 +- tests/neg/i14432c.check | 1 + tests/neg/i18686.check | 35 ++++++++++++++----- tests/neg/i18686.scala | 11 ++++-- tests/neg/i18686b.check | 28 +++++++++++++++ tests/neg/i18686b.scala | 22 ++++++++++++ tests/neg/i18686c.check | 8 +++++ tests/neg/i18686c.scala | 8 +++++ tests/neg/i7709.check | 16 ++++----- tests/neg/not-accessible.check | 5 +++ 13 files changed, 135 insertions(+), 27 deletions(-) create mode 100644 tests/neg/i18686b.check create mode 100644 tests/neg/i18686b.scala create mode 100644 tests/neg/i18686c.check create mode 100644 tests/neg/i18686c.scala diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 index 88383e58f82c..6243e902928c 160000 --- a/community-build/community-projects/stdLib213 +++ b/community-build/community-projects/stdLib213 @@ -1 +1 @@ -Subproject commit 88383e58f82cd728afc9316081c2350489c39943 +Subproject commit 6243e902928c344fb0e82e21120bb257f08a2af2 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index db7f00914ddc..ab896eabeb6d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2987,12 +2987,23 @@ extends ReferenceMsg(CannotBeAccessedID): val where = if (ctx.owner.exists) i" from ${ctx.owner.enclosingClass}" else "" val whyNot = new StringBuffer for alt <- alts do - if alt.is(Protected) then - val cls = alt.owner.enclosingSubClass - val owner = if cls.exists then cls else alt.owner - val location = if owner.is(Final) then owner.showLocated else owner.showLocated + " or one of its subclasses" - whyNot.append(i""" - | Protected $alt can only be accessed from $location.""") + val cls = alt.owner.enclosingSubClass + val owner = if cls.exists then cls else alt.owner + val location: String = + if alt.is(Protected) then + if alt.privateWithin.exists && alt.privateWithin != owner then + if owner.is(Final) then alt.privateWithin.showLocated + else alt.privateWithin.showLocated + ", or " + owner.showLocated + " or one of its subclasses" + else + if owner.is(Final) then owner.showLocated + else owner.showLocated + " or one of its subclasses" + else + alt.privateWithin.orElse(owner).showLocated + val accessMod = if alt.is(Protected) then "protected" else "private" + val within = if alt.privateWithin.exists then i"[${alt.privateWithin.name}]" + else "" + whyNot.append(i""" + | $accessMod$within $alt can only be accessed from $location.""") i"$whatCanNot be accessed as a member of $pre$where.$whyNot" def explain(using Context) = "" diff --git a/compiler/test-resources/repl/i1370 b/compiler/test-resources/repl/i1370 index 4bd92b4d5f83..e10020bf4891 100644 --- a/compiler/test-resources/repl/i1370 +++ b/compiler/test-resources/repl/i1370 @@ -3,4 +3,5 @@ scala> object Lives { class Private { def foo1: Any = new Private.C1; def foo2: 1 | object Lives { class Private { def foo1: Any = new Private.C1; def foo2: Any = new Private.C2 }; object Private { class C1 private {}; private class C2 {} } } | ^^^^^^^^^^ |constructor C1 cannot be accessed as a member of Lives.Private.C1 from class Private. + | private constructor C1 can only be accessed from class C1 in object Private. 1 error found diff --git a/tests/neg/i12573.check b/tests/neg/i12573.check index 8c744fda685b..50fe36aa2aa9 100644 --- a/tests/neg/i12573.check +++ b/tests/neg/i12573.check @@ -5,4 +5,4 @@ |Extension methods were tried, but the search failed with: | | method getDFType cannot be accessed as a member of DFType.type from the top-level definitions in package . - | Protected method getDFType can only be accessed from object DFType. + | protected method getDFType can only be accessed from object DFType. diff --git a/tests/neg/i14432c.check b/tests/neg/i14432c.check index c0f69a1095d7..2710f0dfb3ed 100644 --- a/tests/neg/i14432c.check +++ b/tests/neg/i14432c.check @@ -2,6 +2,7 @@ 12 |class Bar extends example.Foo(23) { // error: cant access private[example] ctor | ^^^^^^^^^^^ | constructor Foo cannot be accessed as a member of example.Foo from class Bar. + | private[example] constructor Foo can only be accessed from package example. -- [E172] Type Error: tests/neg/i14432c.scala:16:43 -------------------------------------------------------------------- 16 | val mFoo = summon[Mirror.Of[example.Foo]] // error: no mirror | ^ diff --git a/tests/neg/i18686.check b/tests/neg/i18686.check index cfecb5522248..6ed69c515051 100644 --- a/tests/neg/i18686.check +++ b/tests/neg/i18686.check @@ -1,13 +1,30 @@ --- [E173] Reference Error: tests/neg/i18686.scala:9:16 ----------------------------------------------------------------- -9 | println(Foo.Bar1) // error - | ^^^^^^^^ - | value Bar1 cannot be accessed as a member of Foo.type from object Main. --- [E173] Reference Error: tests/neg/i18686.scala:10:16 ---------------------------------------------------------------- -10 | println(Foo.Bar2) // error +-- [E173] Reference Error: tests/neg/i18686.scala:13:16 ---------------------------------------------------------------- +13 | println(Foo.Bar1) // error + | ^^^^^^^^ + | value Bar1 cannot be accessed as a member of Foo.type from object Main. + | private value Bar1 can only be accessed from object Foo. +-- [E173] Reference Error: tests/neg/i18686.scala:14:16 ---------------------------------------------------------------- +14 | println(Foo.Bar2) // error | ^^^^^^^^ | value Bar2 cannot be accessed as a member of Foo.type from object Main. --- [E173] Reference Error: tests/neg/i18686.scala:11:16 ---------------------------------------------------------------- -11 | println(Foo.Bar3) // error + | private[Foo] value Bar2 can only be accessed from object Foo. +-- [E173] Reference Error: tests/neg/i18686.scala:15:16 ---------------------------------------------------------------- +15 | println(Foo.Bar3) // error | ^^^^^^^^ | value Bar3 cannot be accessed as a member of Foo.type from object Main. - | Protected value Bar3 can only be accessed from object Foo. + | protected value Bar3 can only be accessed from object Foo. +-- [E173] Reference Error: tests/neg/i18686.scala:16:16 ---------------------------------------------------------------- +16 | println(Foo.Bar4) // error + | ^^^^^^^^ + | value Bar4 cannot be accessed as a member of Foo.type from object Main. + | protected[Foo] value Bar4 can only be accessed from object Foo. +-- [E173] Reference Error: tests/neg/i18686.scala:17:20 ---------------------------------------------------------------- +17 | println(Foo.Baz.Bar5) // error + | ^^^^^^^^^^^^ + | value Bar5 cannot be accessed as a member of Foo.Baz.type from object Main. + | private[Foo] value Bar5 can only be accessed from object Foo. +-- [E173] Reference Error: tests/neg/i18686.scala:18:20 ---------------------------------------------------------------- +18 | println(Foo.Baz.Bar6) // error + | ^^^^^^^^^^^^ + | value Bar6 cannot be accessed as a member of Foo.Baz.type from object Main. + | protected[Foo] value Bar6 can only be accessed from object Foo. diff --git a/tests/neg/i18686.scala b/tests/neg/i18686.scala index d6a45b171394..88da7b9d802a 100644 --- a/tests/neg/i18686.scala +++ b/tests/neg/i18686.scala @@ -1,7 +1,11 @@ object Foo: - private val Bar1: Int = 3 - private[Foo] val Bar2: Int = 3 + private val Bar1: Int = 1 + private[Foo] val Bar2: Int = 2 protected val Bar3: Int = 3 + protected[Foo] val Bar4: Int = 5 + object Baz: + private[Foo] val Bar5: Int = 5 + protected[Foo] val Bar6: Int = 6 end Foo object Main: @@ -9,5 +13,8 @@ object Main: println(Foo.Bar1) // error println(Foo.Bar2) // error println(Foo.Bar3) // error + println(Foo.Bar4) // error + println(Foo.Baz.Bar5) // error + println(Foo.Baz.Bar6) // error end main end Main diff --git a/tests/neg/i18686b.check b/tests/neg/i18686b.check new file mode 100644 index 000000000000..6394aeedf35a --- /dev/null +++ b/tests/neg/i18686b.check @@ -0,0 +1,28 @@ +-- [E173] Reference Error: tests/neg/i18686b.scala:15:16 --------------------------------------------------------------- +15 | println(foo.Bar1) // error + | ^^^^^^^^ + | value Bar1 cannot be accessed as a member of Foo from object Main. + | private value Bar1 can only be accessed from class Foo. +-- [E173] Reference Error: tests/neg/i18686b.scala:16:16 --------------------------------------------------------------- +16 | println(foo.Bar2) // error + | ^^^^^^^^ + | value Bar2 cannot be accessed as a member of Foo from object Main. + | private[Foo] value Bar2 can only be accessed from class Foo. +-- [E173] Reference Error: tests/neg/i18686b.scala:17:16 --------------------------------------------------------------- +17 | println(foo.Bar3) // error + | ^^^^^^^^ + | value Bar3 cannot be accessed as a member of Foo from object Main. + | protected value Bar3 can only be accessed from class Foo or one of its subclasses. +-- [E173] Reference Error: tests/neg/i18686b.scala:18:16 --------------------------------------------------------------- +18 | println(foo.Bar4) // error + | ^^^^^^^^ + | value Bar4 cannot be accessed as a member of Foo from object Main. + | protected[Foo] value Bar4 can only be accessed from class Foo or one of its subclasses. +-- [E008] Not Found Error: tests/neg/i18686b.scala:19:20 --------------------------------------------------------------- +19 | println(foo.Baz.Bar5) // error + | ^^^^^^^^^^^^ + | value Bar5 is not a member of object Foo#Baz +-- [E008] Not Found Error: tests/neg/i18686b.scala:20:20 --------------------------------------------------------------- +20 | println(foo.Baz.Bar6) // error + | ^^^^^^^^^^^^ + | value Bar6 is not a member of object Foo#Baz diff --git a/tests/neg/i18686b.scala b/tests/neg/i18686b.scala new file mode 100644 index 000000000000..86f8066073c0 --- /dev/null +++ b/tests/neg/i18686b.scala @@ -0,0 +1,22 @@ +class Foo: + private val Bar1: Int = 1 + private[Foo] val Bar2: Int = 2 + protected val Bar3: Int = 3 + protected[Foo] val Bar4: Int = 5 + class Baz: + private[Foo] val Bar5: Int = 5 + protected[Foo] val Bar6: Int = 6 +end Foo + +def foo = new Foo + +object Main: + def main(args: Array[String]): Unit = + println(foo.Bar1) // error + println(foo.Bar2) // error + println(foo.Bar3) // error + println(foo.Bar4) // error + println(foo.Baz.Bar5) // error + println(foo.Baz.Bar6) // error + end main +end Main diff --git a/tests/neg/i18686c.check b/tests/neg/i18686c.check new file mode 100644 index 000000000000..328d9cfd42a6 --- /dev/null +++ b/tests/neg/i18686c.check @@ -0,0 +1,8 @@ +-- [E173] Reference Error: tests/neg/i18686c.scala:8:6 ----------------------------------------------------------------- +8 | foo.foo // error + | ^^^^^^^ + |method foo cannot be accessed as a member of (foo² : Bar.Foo) from the top-level definitions in package . + | protected[Bar] method foo can only be accessed from object Bar, or class Foo in object Bar or one of its subclasses. + | + |where: foo is a method in class Foo + | foo² is a parameter in method test diff --git a/tests/neg/i18686c.scala b/tests/neg/i18686c.scala new file mode 100644 index 000000000000..d120e416ed9f --- /dev/null +++ b/tests/neg/i18686c.scala @@ -0,0 +1,8 @@ +object Bar: + class Foo: + protected[Bar] def foo = 23 + class Qux extends Foo: + val qux = foo + +def test(foo: Bar.Foo) = + foo.foo // error diff --git a/tests/neg/i7709.check b/tests/neg/i7709.check index b3b4e21b9db9..14d2dbaf4cde 100644 --- a/tests/neg/i7709.check +++ b/tests/neg/i7709.check @@ -2,39 +2,39 @@ 5 | class B extends X.Y // error | ^^^ | class Y cannot be accessed as a member of X.type from class B. - | Protected class Y can only be accessed from object X. + | protected class Y can only be accessed from object X. -- [E173] Reference Error: tests/neg/i7709.scala:6:21 ------------------------------------------------------------------ 6 | class B2 extends X.Y: // error | ^^^ | class Y cannot be accessed as a member of X.type from class B2. - | Protected class Y can only be accessed from object X. + | protected class Y can only be accessed from object X. -- [E173] Reference Error: tests/neg/i7709.scala:9:28 ------------------------------------------------------------------ 9 | class B4 extends B3(new X.Y) // error | ^^^ | class Y cannot be accessed as a member of X.type from class B4. - | Protected class Y can only be accessed from object X. + | protected class Y can only be accessed from object X. -- [E173] Reference Error: tests/neg/i7709.scala:11:34 ----------------------------------------------------------------- 11 | def this(n: Int) = this(new X.Y().toString) // error | ^^^ | class Y cannot be accessed as a member of X.type from class B5. - | Protected class Y can only be accessed from object X. + | protected class Y can only be accessed from object X. -- [E173] Reference Error: tests/neg/i7709.scala:13:20 ----------------------------------------------------------------- 13 | class B extends X.Y // error | ^^^ | class Y cannot be accessed as a member of X.type from class B. - | Protected class Y can only be accessed from object X. + | protected class Y can only be accessed from object X. -- [E173] Reference Error: tests/neg/i7709.scala:18:18 ----------------------------------------------------------------- 18 | def y = new xx.Y // error | ^^^^ | class Y cannot be accessed as a member of XX from class C. - | Protected class Y can only be accessed from class XX or one of its subclasses. + | protected class Y can only be accessed from class XX or one of its subclasses. -- [E173] Reference Error: tests/neg/i7709.scala:23:20 ----------------------------------------------------------------- 23 | def y = new xx.Y // error | ^^^^ | class Y cannot be accessed as a member of XX from class D. - | Protected class Y can only be accessed from class XX or one of its subclasses. + | protected class Y can only be accessed from class XX or one of its subclasses. -- [E173] Reference Error: tests/neg/i7709.scala:31:20 ----------------------------------------------------------------- 31 | class Q extends X.Y // error | ^^^ | class Y cannot be accessed as a member of p.X.type from class Q. - | Protected class Y can only be accessed from object X in package p. + | protected class Y can only be accessed from object X in package p. diff --git a/tests/neg/not-accessible.check b/tests/neg/not-accessible.check index 00206d281016..54585460a1d8 100644 --- a/tests/neg/not-accessible.check +++ b/tests/neg/not-accessible.check @@ -2,19 +2,24 @@ 8 | def test(a: A) = a.x // error | ^^^ | value x cannot be accessed as a member of (a : foo.A) from class B. + | private[A] value x can only be accessed from class A in package foo. -- [E173] Reference Error: tests/neg/not-accessible.scala:10:23 -------------------------------------------------------- 10 | def test(a: A) = a.x // error | ^^^ | value x cannot be accessed as a member of (a : foo.A) from object B. + | private[A] value x can only be accessed from class A in package foo. -- [E173] Reference Error: tests/neg/not-accessible.scala:13:23 -------------------------------------------------------- 13 | def test(a: A) = a.x // error | ^^^ | value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package bar. + | private[A] value x can only be accessed from class A in package foo. -- [E173] Reference Error: tests/neg/not-accessible.scala:5:21 --------------------------------------------------------- 5 | def test(a: A) = a.x // error | ^^^ | value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package foo. + | private[A] value x can only be accessed from class A in package foo. -- [E173] Reference Error: tests/neg/not-accessible.scala:15:23 -------------------------------------------------------- 15 |def test(a: foo.A) = a.x // error | ^^^ | value x cannot be accessed as a member of (a : foo.A) from the top-level definitions in package . + | private[A] value x can only be accessed from class A in package foo. From e139711e2e843eddab1107d9bf755bd4575bdee1 Mon Sep 17 00:00:00 2001 From: Pascal Weisenburger Date: Sun, 16 Jul 2023 01:10:30 +0200 Subject: [PATCH 095/117] Make check flags for `newMethod`, `newVal` and `newBind` in Quotes API less restrictive --- compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala | 6 +++--- library/src/scala/quoted/Quotes.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index e27140459a49..95149a108baf 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2915,11 +2915,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def Transparent: Flags = dotc.core.Flags.Transparent // Keep: aligned with Quotes's `newMethod` doc - private[QuotesImpl] def validMethodFlags: Flags = Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | JavaStatic // Flags that could be allowed: Synthetic | ExtensionMethod | Exported | Erased | Infix | Invisible + private[QuotesImpl] def validMethodFlags: Flags = Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | Synthetic | Artifact | ExtensionMethod | Exported | Erased | Infix | Invisible | JavaStatic // Keep: aligned with Quotes's `newVal` doc - private[QuotesImpl] def validValFlags: Flags = Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | JavaStatic // Flags that could be added: Synthetic | Erased | Invisible + private[QuotesImpl] def validValFlags: Flags = Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | Synthetic | Artifact | Erased | Invisible | JavaStatic // Keep: aligned with Quotes's `newBind` doc - private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased + private[QuotesImpl] def validBindFlags: Flags = Case | Implicit | Given | Erased end Flags given FlagsMethods: FlagsMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 3d5896e74f66..a2c9e49a1958 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3816,7 +3816,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param parent The owner of the method * @param name The name of the method * @param tpe The type of the method (MethodType, PolyType, ByNameType) - * @param flags extra flags to with which the symbol should be constructed. `Method` flag will be added. Can be `Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | JavaStatic` + * @param flags extra flags to with which the symbol should be constructed. `Method` flag will be added. Can be `Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | Synthetic | Artifact | ExtensionMethod | Exported | Erased | Infix | Invisible | JavaStatic` * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. */ // Keep: `flags` doc aligned with QuotesImpl's `validMethodFlags` @@ -3833,7 +3833,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param parent The owner of the val/var/lazy val * @param name The name of the val/var/lazy val * @param tpe The type of the val/var/lazy val - * @param flags extra flags to with which the symbol should be constructed. Can be `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | JavaStatic` + * @param flags extra flags to with which the symbol should be constructed. Can be `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | Synthetic | Artifact | Erased | Invisible | JavaStatic` * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. @@ -3849,7 +3849,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @param parent The owner of the binding * @param name The name of the binding - * @param flags extra flags to with which the symbol should be constructed. `Case` flag will be added. Can be `Case` + * @param flags extra flags to with which the symbol should be constructed. `Case` flag will be added. Can be `Case | Implicit | Given | Erased` * @param tpe The type of the binding * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. From 389a4fc83bd07b0532ed838d3ccd5faec755a535 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 3 Aug 2023 15:10:50 +0200 Subject: [PATCH 096/117] Apply suggestions from code review --- compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala | 7 ++++--- library/src/scala/quoted/Quotes.scala | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 95149a108baf..0a6b04fea81e 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2915,11 +2915,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def Transparent: Flags = dotc.core.Flags.Transparent // Keep: aligned with Quotes's `newMethod` doc - private[QuotesImpl] def validMethodFlags: Flags = Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | Synthetic | Artifact | ExtensionMethod | Exported | Erased | Infix | Invisible | JavaStatic + private[QuotesImpl] def validMethodFlags: Flags = Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | JavaStatic | Synthetic | Artifact // Flags that could be allowed: Synthetic | ExtensionMethod | Exported | Erased | Infix | Invisible // Keep: aligned with Quotes's `newVal` doc - private[QuotesImpl] def validValFlags: Flags = Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | Synthetic | Artifact | Erased | Invisible | JavaStatic + private[QuotesImpl] def validValFlags: Flags = Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | JavaStatic | Synthetic | Artifact // Flags that could be added: Synthetic | Erased | Invisible + // Keep: aligned with Quotes's `newBind` doc - private[QuotesImpl] def validBindFlags: Flags = Case | Implicit | Given | Erased + private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased end Flags given FlagsMethods: FlagsMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index a2c9e49a1958..74e726cf4289 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3816,7 +3816,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param parent The owner of the method * @param name The name of the method * @param tpe The type of the method (MethodType, PolyType, ByNameType) - * @param flags extra flags to with which the symbol should be constructed. `Method` flag will be added. Can be `Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | Synthetic | Artifact | ExtensionMethod | Exported | Erased | Infix | Invisible | JavaStatic` + * @param flags extra flags to with which the symbol should be constructed. `Method` flag will be added. Can be `Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | JavaStatic | Synthetic | Artifact` * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. */ // Keep: `flags` doc aligned with QuotesImpl's `validMethodFlags` @@ -3833,7 +3833,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param parent The owner of the val/var/lazy val * @param name The name of the val/var/lazy val * @param tpe The type of the val/var/lazy val - * @param flags extra flags to with which the symbol should be constructed. Can be `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | Synthetic | Artifact | Erased | Invisible | JavaStatic` + * @param flags extra flags to with which the symbol should be constructed. Can be `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | JavaStatic | Synthetic | Artifact` * @param privateWithin the symbol within which this new method symbol should be private. May be noSymbol. * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. @@ -3849,7 +3849,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @param parent The owner of the binding * @param name The name of the binding - * @param flags extra flags to with which the symbol should be constructed. `Case` flag will be added. Can be `Case | Implicit | Given | Erased` + * @param flags extra flags to with which the symbol should be constructed. `Case` flag will be added. Can be `Case` * @param tpe The type of the binding * @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be * direct or indirect children of the reflection context's owner. From 73fb6bf8ed4eae375670a3496b57247ab7ecce40 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Oct 2023 14:40:42 +0200 Subject: [PATCH 097/117] Fix import suggestion error logging This was printing the extra debug information when testing tests/neg-deep-subtype/i1650.scala --- compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 779485936b3b..66d4a803494d 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -196,7 +196,7 @@ trait ImportSuggestions: && { val task = new TimerTask: def run() = - println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") + implicits.println(i"Cancelling test of $ref when making suggestions for error in ${ctx.source}") ctx.run.nn.isCancelled = true val span = ctx.owner.srcPos.span val (expectedType, argument, kind) = pt match From e7e0402b3b604f0ee63e45658ac579d329c2cc17 Mon Sep 17 00:00:00 2001 From: philippus Date: Wed, 18 Oct 2023 07:42:18 +0200 Subject: [PATCH 098/117] Update asm to 9.6 --- compiler/src/dotty/tools/backend/jvm/BackendUtils.scala | 1 + compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- project/Build.scala | 2 +- tests/pos-with-compiler-cc/backend/jvm/BCodeIdiomatic.scala | 3 ++- tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index d781479fdf1e..668dedd6076e 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -36,6 +36,7 @@ class BackendUtils(val postProcessor: PostProcessor) { case "19" => asm.Opcodes.V19 case "20" => asm.Opcodes.V20 case "21" => asm.Opcodes.V21 + case "22" => asm.Opcodes.V22 } lazy val extraProc: Int = { diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 9f75f6afa028..9d62df0aaf15 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -20,7 +20,7 @@ class ScalaSettings extends SettingGroup with AllScalaSettings object ScalaSettings: // Keep synchronized with `classfileVersion` in `BackendUtils` private val minTargetVersion = 8 - private val maxTargetVersion = 21 + private val maxTargetVersion = 22 def supportedTargetVersions: List[String] = (minTargetVersion to maxTargetVersion).toList.map(_.toString) diff --git a/project/Build.scala b/project/Build.scala index d3c57653beb6..c8b53fd36042 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -594,7 +594,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.5.0-scala-1", // used by the backend + "org.scala-lang.modules" % "scala-asm" % "9.6.0-scala-1", // used by the backend Dependencies.compilerInterface, "org.jline" % "jline-reader" % "3.19.0", // used by the REPL "org.jline" % "jline-terminal" % "3.19.0", diff --git a/tests/pos-with-compiler-cc/backend/jvm/BCodeIdiomatic.scala b/tests/pos-with-compiler-cc/backend/jvm/BCodeIdiomatic.scala index 2218adf6e18b..9b8d81bbdbd1 100644 --- a/tests/pos-with-compiler-cc/backend/jvm/BCodeIdiomatic.scala +++ b/tests/pos-with-compiler-cc/backend/jvm/BCodeIdiomatic.scala @@ -55,7 +55,8 @@ trait BCodeIdiomatic extends Pure { case "18" => asm.Opcodes.V18 case "19" => asm.Opcodes.V19 case "20" => asm.Opcodes.V20 - case "21" => asm.Opcodes.V21*/ + case "21" => asm.Opcodes.V21 + case "22" => asm.Opcodes.V22*/ } lazy val majorVersion: Int = (classfileVersion & 0xFF) diff --git a/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala b/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala index 20708b98cc95..b3250eb7b536 100644 --- a/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala +++ b/tests/pos-with-compiler-cc/dotc/config/ScalaSettings.scala @@ -17,7 +17,7 @@ class ScalaSettings extends SettingGroup with AllScalaSettings object ScalaSettings: // Keep synchronized with `classfileVersion` in `BCodeIdiomatic` private val minTargetVersion = 8 - private val maxTargetVersion = 21 + private val maxTargetVersion = 22 def supportedTargetVersions: List[String] = (minTargetVersion to maxTargetVersion).toList.map(_.toString) From b16f8730fd20e0216a1dfe33c3be9da2920ad7e3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 24 Aug 2023 14:43:12 +0200 Subject: [PATCH 099/117] Elide companion defs to a `object` extending `AnyVal` Co-authored-by: Matt Bovel --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/reporting/messages.scala | 13 +++++++++---- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 ++ tests/neg/i18274.check | 9 +++++++++ tests/neg/i18274.scala | 3 +++ 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/neg/i18274.check create mode 100644 tests/neg/i18274.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0baa694d6a9f..c38ba7ce4740 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -820,7 +820,7 @@ object desugar { } else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum) companionDefs(anyRef, companionMembers) - else if (isValueClass) + else if isValueClass && !isObject then companionDefs(anyRef, Nil) else Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index ab896eabeb6d..fd6fed05b4d0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1686,10 +1686,15 @@ class CannotExtendAnyVal(sym: Symbol)(using Context) extends SyntaxMsg(CannotExtendAnyValID) { def msg(using Context) = i"""$sym cannot extend ${hl("AnyVal")}""" def explain(using Context) = - i"""Only classes (not traits) are allowed to extend ${hl("AnyVal")}, but traits may extend - |${hl("Any")} to become ${Green("\"universal traits\"")} which may only have ${hl("def")} members. - |Universal traits can be mixed into classes that extend ${hl("AnyVal")}. - |""" + if sym.is(Trait) then + i"""Only classes (not traits) are allowed to extend ${hl("AnyVal")}, but traits may extend + |${hl("Any")} to become ${Green("\"universal traits\"")} which may only have ${hl("def")} members. + |Universal traits can be mixed into classes that extend ${hl("AnyVal")}. + |""" + else if sym.is(Module) then + i"""Only classes (not objects) are allowed to extend ${hl("AnyVal")}. + |""" + else "" } class CannotExtendJavaEnum(sym: Symbol)(using Context) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e9265031221b..6e99651e3fa3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -717,6 +717,8 @@ object Checking { if (isDerivedValueClass(clazz)) { if (clazz.is(Trait)) report.error(CannotExtendAnyVal(clazz), clazz.srcPos) + if clazz.is(Module) then + report.error(CannotExtendAnyVal(clazz), clazz.srcPos) if (clazz.is(Abstract)) report.error(ValueClassesMayNotBeAbstract(clazz), clazz.srcPos) if (!clazz.isStatic) diff --git a/tests/neg/i18274.check b/tests/neg/i18274.check new file mode 100644 index 000000000000..2535d641451c --- /dev/null +++ b/tests/neg/i18274.check @@ -0,0 +1,9 @@ +-- [E068] Syntax Error: tests/neg/i18274.scala:3:7 --------------------------------------------------------------------- +3 |object Foo extends AnyVal // error + | ^ + | object Foo cannot extend AnyVal + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Only classes (not objects) are allowed to extend AnyVal. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i18274.scala b/tests/neg/i18274.scala new file mode 100644 index 000000000000..19c27b969d98 --- /dev/null +++ b/tests/neg/i18274.scala @@ -0,0 +1,3 @@ +//> using options -explain + +object Foo extends AnyVal // error From 2554e8825e132de816ca7bfb225dcb7a44f1f749 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Sep 2023 17:51:19 +0200 Subject: [PATCH 100/117] Add attributes section to TASTy The `Attributes` section contains a list of UTF-8 string that represent the attributes. --- .../dotc/core/tasty/AttributePickler.scala | 25 ++++++++++++++++++ .../dotc/core/tasty/AttributeUnpickler.scala | 26 +++++++++++++++++++ .../dotc/core/tasty/DottyUnpickler.scala | 21 ++++++++++----- .../tools/dotc/core/tasty/ScratchData.scala | 2 ++ .../tools/dotc/core/tasty/TastyPrinter.scala | 26 +++++++++++++++---- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++- .../dotty/tools/dotc/transform/Pickler.scala | 3 +++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 4 +++ 8 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/AttributePickler.scala create mode 100644 compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/AttributePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/AttributePickler.scala new file mode 100644 index 000000000000..e948d8bb5fef --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/tasty/AttributePickler.scala @@ -0,0 +1,25 @@ +package dotty.tools.dotc.core.tasty + +import dotty.tools.dotc.ast.{tpd, untpd} + +import dotty.tools.tasty.TastyBuffer +import dotty.tools.tasty.TastyFormat.AttributesSection + +import java.nio.charset.StandardCharsets + +object AttributePickler: + + def pickleAttributes( + attributes: List[String], + pickler: TastyPickler, + buf: TastyBuffer): Unit = + if attributes != Nil then + pickler.newSection(AttributesSection, buf) + for attribute <- attributes do + val bytes = attribute.getBytes(StandardCharsets.UTF_8).nn + val length = bytes.length + buf.writeNat(length) + buf.writeBytes(bytes, length) + end pickleAttributes + +end AttributePickler diff --git a/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala new file mode 100644 index 000000000000..5c9b89bf3b0a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala @@ -0,0 +1,26 @@ +package dotty.tools.dotc +package core.tasty + +import scala.language.unsafeNulls + +import dotty.tools.tasty.{TastyReader, TastyBuffer} + +import java.nio.charset.StandardCharsets + +class AttributeUnpickler(reader: TastyReader): + import reader._ + + private[tasty] lazy val attributes: List[String] = { + val attributesBuilder = List.newBuilder[String] + while (!isAtEnd) { + val length = readNat() + if (length > 0) { + val bytes = readBytes(length) + val attribute = new String(bytes, StandardCharsets.UTF_8) + attributesBuilder += attribute + } + } + attributesBuilder.result() + } + +end AttributeUnpickler diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index b35c5c9f1acc..f5cb66c0decf 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -13,17 +13,17 @@ import Names.SimpleName import TreeUnpickler.UnpickleMode import dotty.tools.tasty.TastyReader -import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection} +import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection} object DottyUnpickler { /** Exception thrown if classfile is corrupted */ class BadSignature(msg: String) extends RuntimeException(msg) - class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler]) + class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler], attributeUnpickler: Option[AttributeUnpickler]) extends SectionUnpickler[TreeUnpickler](ASTsSection) { def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler = - new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler) + new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler, attributeUnpickler) } class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler](PositionsSection) { @@ -35,6 +35,10 @@ object DottyUnpickler { def unpickle(reader: TastyReader, nameAtRef: NameTable): CommentUnpickler = new CommentUnpickler(reader) } + class AttributesSectionUnpickler extends SectionUnpickler[AttributeUnpickler](AttributesSection) { + def unpickle(reader: TastyReader, nameAtRef: NameTable): AttributeUnpickler = + new AttributeUnpickler(reader) + } } /** A class for unpickling Tasty trees and symbols. @@ -48,7 +52,8 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe val unpickler: TastyUnpickler = new TastyUnpickler(bytes) private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler) private val commentUnpicklerOpt = unpickler.unpickle(new CommentsSectionUnpickler) - private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)).get + private val attributeUnpicklerOpt = unpickler.unpickle(new AttributesSectionUnpickler) + private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get /** Enter all toplevel classes and objects into their scopes * @param roots a set of SymDenotations that should be overwritten by unpickling @@ -56,8 +61,12 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe def enter(roots: Set[SymDenotation])(using Context): Unit = treeUnpickler.enter(roots) - protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler = - new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt) + protected def treeSectionUnpickler( + posUnpicklerOpt: Option[PositionUnpickler], + commentUnpicklerOpt: Option[CommentUnpickler], + attributeUnpicklerOpt: Option[AttributeUnpickler] + ): TreeSectionUnpickler = + new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt) protected def computeRootTrees(using Context): List[Tree] = treeUnpickler.unpickle(mode) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/ScratchData.scala b/compiler/src/dotty/tools/dotc/core/tasty/ScratchData.scala index b36c78a77ac6..74cc6aaae533 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/ScratchData.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/ScratchData.scala @@ -10,6 +10,7 @@ class ScratchData: val pickledIndices = new mutable.BitSet val commentBuffer = new TastyBuffer(5000) + val attributeBuffer = new TastyBuffer(128) def reset() = assert(delta ne delta1) @@ -17,4 +18,5 @@ class ScratchData: positionBuffer.reset() pickledIndices.clear() commentBuffer.reset() + attributeBuffer.reset() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 5876b69edfde..7f15831709b5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -9,7 +9,7 @@ import Contexts._, Decorators._ import Names.Name import TastyUnpickler._ import util.Spans.offsetToInt -import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection} +import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection} import java.nio.file.{Files, Paths} import dotty.tools.io.{JarArchive, Path} @@ -84,14 +84,16 @@ class TastyPrinter(bytes: Array[Byte]) { case Some(s) => sb.append(s) case _ => } - sb.append("\n\n") unpickle(new PositionSectionUnpickler) match { - case Some(s) => sb.append(s) + case Some(s) => sb.append("\n\n").append(s) case _ => } - sb.append("\n\n") unpickle(new CommentSectionUnpickler) match { - case Some(s) => sb.append(s) + case Some(s) => sb.append("\n\n").append(s) + case _ => + } + unpickle(new AttributesSectionUnpickler) match { + case Some(s) => sb.append("\n\n").append(s) case _ => } sb.result @@ -222,6 +224,20 @@ class TastyPrinter(bytes: Array[Byte]) { } } + class AttributesSectionUnpickler extends SectionUnpickler[String](AttributesSection) { + + private val sb: StringBuilder = new StringBuilder + + def unpickle(reader: TastyReader, tastyName: NameTable): String = { + sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}") + val attributes = new AttributeUnpickler(reader).attributes + sb.append(s" attributes bytes:\n") + for attribute <- attributes do + sb.append(" ").append(attribute).append("\n") + sb.result + } + } + protected def nameStr(str: String): String = str protected def treeStr(str: String): String = str protected def lengthStr(str: String): String = str diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7c0f1ac5518b..8a0723c2a7fb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -52,11 +52,13 @@ import scala.annotation.internal.sharable * @param reader the reader from which to unpickle * @param posUnpicklerOpt the unpickler for positions, if it exists * @param commentUnpicklerOpt the unpickler for comments, if it exists + * @param attributeUnpicklerOpt the unpickler for attributes, if it exists */ class TreeUnpickler(reader: TastyReader, nameAtRef: NameTable, posUnpicklerOpt: Option[PositionUnpickler], - commentUnpicklerOpt: Option[CommentUnpickler]) { + commentUnpicklerOpt: Option[CommentUnpickler], + attributeUnpicklerOpt: Option[AttributeUnpickler]) { import TreeUnpickler._ import tpd._ diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 252bb6daeae5..8976cd97c7a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -108,6 +108,9 @@ class Pickler extends Phase { pickler, treePkl.buf.addrOfTree, treePkl.docString, tree, scratch.commentBuffer) + val attributes = Nil // TODO add attributes here + AttributePickler.pickleAttributes(attributes, pickler, scratch.attributeBuffer) + val pickled = pickler.assembleParts() def rawBytes = // not needed right now, but useful to print raw format. diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index d91295f06af5..4c37379a1e9c 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -267,6 +267,9 @@ Standard Section: "Comments" Comment* ```none Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates. ``` + +Standard Section: "Attributes" UTF8* + **************************************************************************************/ object TastyFormat { @@ -361,6 +364,7 @@ object TastyFormat { final val ASTsSection = "ASTs" final val PositionsSection = "Positions" final val CommentsSection = "Comments" + final val AttributesSection = "Attributes" /** Tags used to serialize names, should update [[TastyFormat$.nameTagToString]] if a new constant is added */ class NameTags { From 92ed512d100e070cce014179d92be39fb2718e09 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 27 Sep 2023 15:21:53 +0200 Subject: [PATCH 101/117] Use `Scala2x` flags for unpickled Scala 2 stdlib TASTy Also introduce `Scala2Tasty` flag to differentiate between Scala 2 symbols that come from class files and Scala 2 symbols that come from TASTy. At this point, the only Scala 2 TASTy is the one from the standard library. To know that a TASTy contains the definitions of the standard library, we add the `Scala2StandardLibrary` attribute to the TASTy file. We mark all unpickled classes from those TASTy files with `Scala2x | Scala2Tasty`. --- compiler/src/dotty/tools/dotc/core/Flags.scala | 4 ++-- .../dotty/tools/dotc/core/tasty/AttributeUnpickler.scala | 4 +++- .../src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 6 +++++- .../src/dotty/tools/dotc/transform/ExtensionMethods.scala | 2 +- compiler/src/dotty/tools/dotc/transform/Pickler.scala | 5 ++++- scala2-library-tasty-tests/src/Main.scala | 7 +++++++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 ++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index fa57c503d61b..3f398010e7ea 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -308,8 +308,8 @@ object Flags { */ val (_, StableRealizable @ _, _) = newFlags(24, "") - /** A case parameter accessor */ - val (_, CaseAccessor @ _, _) = newFlags(25, "") + /** A case parameter accessor / an unpickled Scala 2 TASTy (only for Scala 2 stdlib) */ + val (_, CaseAccessor @ _, Scala2Tasty @ _) = newFlags(25, "", "") /** A Scala 2x super accessor / an unpickled Scala 2.x class */ val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "", "") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala index 5c9b89bf3b0a..6df2cfd0f73d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/AttributeUnpickler.scala @@ -3,7 +3,7 @@ package core.tasty import scala.language.unsafeNulls -import dotty.tools.tasty.{TastyReader, TastyBuffer} +import dotty.tools.tasty.{TastyFormat, TastyReader, TastyBuffer} import java.nio.charset.StandardCharsets @@ -23,4 +23,6 @@ class AttributeUnpickler(reader: TastyReader): attributesBuilder.result() } + def scala2Stdlib: Boolean = attributes.contains(TastyFormat.Scala2StandardLibraryAttribute) + end AttributeUnpickler diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 8a0723c2a7fb..b740c7b3a708 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -99,6 +99,9 @@ class TreeUnpickler(reader: TastyReader, /** Was unpickled class compiled with capture checks? */ private var withCaptureChecks: Boolean = false + private val unpicklingScala2Lib = + attributeUnpicklerOpt.exists(_.scala2Stdlib) + private def registerSym(addr: Addr, sym: Symbol) = symAtAddr(addr) = sym @@ -610,7 +613,8 @@ class TreeUnpickler(reader: TastyReader, val rhsStart = currentAddr val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() - val (givenFlags, annotFns, privateWithin) = readModifiers(end) + val (givenFlags0, annotFns, privateWithin) = readModifiers(end) + val givenFlags = if isClass && unpicklingScala2Lib then givenFlags0 | Scala2x | Scala2Tasty else givenFlags0 pickling.println(i"creating symbol $name at $start with flags ${givenFlags.flagsString}, isAbsType = $isAbsType, $ttag") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 19124357a0bd..262f8d6bdce0 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -77,7 +77,7 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete // Create extension methods, except if the class comes from Scala 2 // because it adds extension methods before pickling. - if (!(valueClass.is(Scala2x))) + if !valueClass.is(Scala2x, butNot = Scala2Tasty) then for (decl <- valueClass.classInfo.decls) if isMethodWithExtension(decl) then enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol)) diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 8976cd97c7a6..c6da8aca8e41 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -16,6 +16,7 @@ import reporting.{ThrowingReporter, Profile, Message} import collection.mutable import util.concurrent.{Executor, Future} import compiletime.uninitialized +import dotty.tools.tasty.TastyFormat.Scala2StandardLibraryAttribute object Pickler { val name: String = "pickler" @@ -108,7 +109,9 @@ class Pickler extends Phase { pickler, treePkl.buf.addrOfTree, treePkl.docString, tree, scratch.commentBuffer) - val attributes = Nil // TODO add attributes here + val attributes = + if ctx.settings.Yscala2Stdlib.value then List(Scala2StandardLibraryAttribute) + else Nil AttributePickler.pickleAttributes(attributes, pickler, scratch.attributeBuffer) val pickled = pickler.assembleParts() diff --git a/scala2-library-tasty-tests/src/Main.scala b/scala2-library-tasty-tests/src/Main.scala index b579baf6513d..b33219271201 100644 --- a/scala2-library-tasty-tests/src/Main.scala +++ b/scala2-library-tasty-tests/src/Main.scala @@ -17,6 +17,7 @@ object HelloWorld: testScala2ObjectParents() testScala2CaseClassUnderscoreMembers() testScalaNumberUnderlying() + testArrayOps() scala.collection.mutable.UnrolledBufferTest.test() } @@ -68,3 +69,9 @@ object HelloWorld: val _: Object = MyNumber2(BigInt(1)).underlying val _: Object = (MyNumber2(BigInt(1)): ScalaNumber).underlying } + + def testArrayOps() = { + new collection.ArrayOps[String](Array[String]("foo")).exists(x => true) + } + +end HelloWorld diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 4c37379a1e9c..ae8b4dfd8f2d 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -366,6 +366,8 @@ object TastyFormat { final val CommentsSection = "Comments" final val AttributesSection = "Attributes" + final val Scala2StandardLibraryAttribute = "Scala2StandardLibrary" + /** Tags used to serialize names, should update [[TastyFormat$.nameTagToString]] if a new constant is added */ class NameTags { final val UTF8 = 1 // A simple name in UTF8 encoding. From 866a33cdb8b29f8a003b38d5e8bcb3636bb6f4cd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Sep 2023 11:37:19 +0200 Subject: [PATCH 102/117] Use Scala 2 library TASTy in compilation tests --- compiler/test/dotty/Properties.scala | 3 +++ compiler/test/dotty/tools/vulpix/TestConfiguration.scala | 2 ++ project/Build.scala | 4 +++- scala2-library-tasty-tests/src/Main.scala | 2 ++ .../StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala | 7 +++++++ tests/run/testStdlibTasty.scala | 2 ++ 6 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala create mode 100644 tests/run/testStdlibTasty.scala diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index cc47303d5468..00d512de757c 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -82,6 +82,9 @@ object Properties { /** scala-library jar */ def scalaLibrary: String = sys.props("dotty.tests.classes.scalaLibrary") + /** scala-library TASTy jar */ + def scalaLibraryTasty: String = sys.props("dotty.tests.tasties.scalaLibrary") + /** scala-asm jar */ def scalaAsm: String = sys.props("dotty.tests.classes.scalaAsm") diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 21094799d8a9..d029cbd2be1e 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -26,11 +26,13 @@ object TestConfiguration { ) val basicClasspath = mkClasspath(List( + Properties.scalaLibraryTasty, Properties.scalaLibrary, Properties.dottyLibrary )) val withCompilerClasspath = mkClasspath(List( + Properties.scalaLibraryTasty, Properties.scalaLibrary, Properties.scalaAsm, Properties.jlineTerminal, diff --git a/project/Build.scala b/project/Build.scala index c8b53fd36042..03a748761da1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -641,6 +641,7 @@ object Build { "-Ddotty.tests.classes.tastyCore=" + jars("tasty-core"), "-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"), "-Ddotty.tests.classes.scalaLibrary=" + findArtifactPath(externalDeps, "scala-library"), + "-Ddotty.tests.tasties.scalaLibrary=" + jars("stdlib-bootstrapped-tasty"), "-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"), "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), @@ -829,7 +830,8 @@ object Build { // running the compiler, we should always have the bootstrapped // library on the compiler classpath since the non-bootstrapped one // may not be binary-compatible. - "scala3-library" -> (`scala3-library-bootstrapped` / Compile / packageBin).value + "scala3-library" -> (`scala3-library-bootstrapped` / Compile / packageBin).value, + "stdlib-bootstrapped-tasty" -> (`stdlib-bootstrapped-tasty` / Compile / packageBin).value, ).mapValues(_.getAbsolutePath) } }.value, diff --git a/scala2-library-tasty-tests/src/Main.scala b/scala2-library-tasty-tests/src/Main.scala index b33219271201..3f2345687f49 100644 --- a/scala2-library-tasty-tests/src/Main.scala +++ b/scala2-library-tasty-tests/src/Main.scala @@ -19,6 +19,8 @@ object HelloWorld: testScalaNumberUnderlying() testArrayOps() scala.collection.mutable.UnrolledBufferTest.test() + + assert(scala.StdlibBootstrappedDummy.thisMethodMustNotBePublished == "This method must not be published in a stable release of the library.") } def testScala2UnapplySignatures() = { diff --git a/stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala b/stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala new file mode 100644 index 000000000000..221013adbac6 --- /dev/null +++ b/stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala @@ -0,0 +1,7 @@ +package scala.StdlibBootstrappedDummy + +// This method must not be published in a stable release of the library. +// This method can be removed once we add other methods to the stdlib bootstrapped +// and are able to test it using those methods. +inline def thisMethodMustNotBePublished: String = + "This method must not be published in a stable release of the library." diff --git a/tests/run/testStdlibTasty.scala b/tests/run/testStdlibTasty.scala new file mode 100644 index 000000000000..5947df9ec542 --- /dev/null +++ b/tests/run/testStdlibTasty.scala @@ -0,0 +1,2 @@ +@main def Test: Unit = + assert(scala.StdlibBootstrappedDummy.thisMethodMustNotBePublished == "This method must not be published in a stable release of the library.") From 991909e435b915c7e502bee46b9039f061b7d45f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 25 Sep 2023 11:45:30 +0200 Subject: [PATCH 103/117] Workaround --- compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala index 3cf74ee3fdb3..0cf5b60468dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala +++ b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala @@ -25,6 +25,7 @@ class YCheckPositions extends Phase { val checker = new TreeTraverser { private var sources: List[SourceFile] = ctx.source :: Nil def traverse(tree: tpd.Tree)(using Context): Unit = { + if tree.source.toString != "library/src/scala/runtime/stdLibPatches/Predef.scala" then // FIXME: remove this workaround // Check current context is correct assert(ctx.source == sources.head) From 07f1bfd989923b3015cce447f9b0f94eaff2e256 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 27 Sep 2023 16:14:47 +0200 Subject: [PATCH 104/117] Update checkfiles --- tests/neg/constructor-proxy-shadowing.check | 4 +- tests/neg/f-interpolator-neg.check | 256 +++++++++++++++++- tests/neg/i16601.check | 6 + tests/neg/i16601a.check | 10 + tests/neg/i8752.check | 2 +- tests/run-macros/tasty-extractors-1.check | 6 +- tests/run-macros/tasty-extractors-2.check | 2 +- tests/run-macros/tasty-extractors-types.check | 6 +- tests/run/typeclass-derivation3.check | 6 +- 9 files changed, 281 insertions(+), 17 deletions(-) diff --git a/tests/neg/constructor-proxy-shadowing.check b/tests/neg/constructor-proxy-shadowing.check index f45b9b3205c3..091d1ed14c1e 100644 --- a/tests/neg/constructor-proxy-shadowing.check +++ b/tests/neg/constructor-proxy-shadowing.check @@ -52,7 +52,7 @@ 17 |val x = Seq(3) // error: shadowing | ^^^ | Reference to constructor proxy for class Seq - | shadows outer reference to getter Seq in package scala + | shadows outer reference to value Seq in package scala | | The instance needs to be created with an explicit `new`. |-------------------------------------------------------------------------------------------------------------------- @@ -66,7 +66,7 @@ | | new Seq(...) | - | Or it could mean calling the apply method of getter Seq in package scala as in + | Or it could mean calling the apply method of value Seq in package scala as in | | Seq.apply(...) | diff --git a/tests/neg/f-interpolator-neg.check b/tests/neg/f-interpolator-neg.check index ea8df052589e..a272022ef4c0 100644 --- a/tests/neg/f-interpolator-neg.check +++ b/tests/neg/f-interpolator-neg.check @@ -1,3 +1,255 @@ +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:4:25 --------------------------------------------- +4 | new StringContext().f() // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:5:52 --------------------------------------------- +5 | new StringContext("", " is ", "%2d years old").f(s) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:6:52 --------------------------------------------- +6 | new StringContext("", " is ", "%2d years old").f(s, d, d) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:11:4 --------------------------------------------- +11 | f"$s%b" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:12:4 --------------------------------------------- +12 | f"$s%c" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:13:4 --------------------------------------------- +13 | f"$f%c" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:14:4 --------------------------------------------- +14 | f"$s%x" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:15:4 --------------------------------------------- +15 | f"$b%d" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:16:4 --------------------------------------------- +16 | f"$s%d" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:17:4 --------------------------------------------- +17 | f"$f%o" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:18:4 --------------------------------------------- +18 | f"$s%e" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:19:4 --------------------------------------------- +19 | f"$b%f" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:24:4 --------------------------------------------- +24 | f"$s%+ 0,(s" // error + | ^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:25:4 --------------------------------------------- +25 | f"$c%#+ 0,(c" // error + | ^^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:26:4 --------------------------------------------- +26 | f"$d%#d" // error + | ^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:27:4 --------------------------------------------- +27 | f"$d%,x" // error + | ^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:28:4 --------------------------------------------- +28 | f"$d%+ (x" // error + | ^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:29:4 --------------------------------------------- +29 | f"$f%,(a" // error + | ^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:30:4 --------------------------------------------- +30 | f"$t%#+ 0,(tT" // error + | ^^^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:31:4 --------------------------------------------- +31 | f"%-#+ 0,(n" // error + | ^^^^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:36:4 --------------------------------------------- +36 | f"$c%.2c" // error + | ^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:37:4 --------------------------------------------- +37 | f"$d%.2d" // error + | ^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:38:4 --------------------------------------------- +38 | f"%.2%" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:39:4 --------------------------------------------- +39 | f"%.2n" // error + | ^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:40:4 --------------------------------------------- +40 | f"$f%.2a" // error + | ^^^^^^^^^ + | A pure expression does nothing in statement position; you may be omitting necessary parentheses + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:45:4 --------------------------------------------- +45 | f"%>IterableOps[X,C,C[X]] ] // error // error | ^ - | Type argument C does not have the same kind as its bound [_$$1] + | Type argument C does not have the same kind as its bound [_$1] diff --git a/tests/run-macros/tasty-extractors-1.check b/tests/run-macros/tasty-extractors-1.check index bebfa3d79dbc..c0acf803be37 100644 --- a/tests/run-macros/tasty-extractors-1.check +++ b/tests/run-macros/tasty-extractors-1.check @@ -17,7 +17,7 @@ Inlined(None, Nil, Literal(StringConstant("abc"))) ConstantType(StringConstant("abc")) Inlined(None, Nil, Apply(Ident("println"), List(Literal(StringConstant("abc"))))) -TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") +TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Unit") Inlined(None, Nil, Typed(Literal(IntConstant(8)), TypeIdent("Int"))) TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int") @@ -80,10 +80,10 @@ Inlined(None, Nil, Apply(Select(New(TypeIdent("Object")), ""), Nil)) TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Object") Inlined(None, Nil, Apply(Select(Ident("Int"), "box"), List(NamedArg("x", Literal(IntConstant(9)))))) -TypeRef(ThisType(TypeRef(NoPrefix(), "lang")), "Integer") +TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "java"), "lang"), "Integer") Inlined(None, Nil, Apply(TypeApply(Select(Ident("Ordering"), "apply"), List(TypeIdent("Int"))), List(Ident("Int")))) -AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "math")), "Ordering"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) +AppliedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "math"), "Ordering"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) Inlined(None, Nil, Block(List(ValDef("a", TypeIdent("Int"), Some(Literal(IntConstant(3))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") diff --git a/tests/run-macros/tasty-extractors-2.check b/tests/run-macros/tasty-extractors-2.check index 5dd6af8d8b04..917db7aaac5c 100644 --- a/tests/run-macros/tasty-extractors-2.check +++ b/tests/run-macros/tasty-extractors-2.check @@ -14,7 +14,7 @@ Inlined(None, Nil, Typed(Literal(IntConstant(1)), TypeIdent("Int"))) TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int") Inlined(None, Nil, Typed(Ident("Nil"), Applied(TypeIdent("List"), List(TypeIdent("Int"))))) -AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "immutable")), "List"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) +AppliedType(TypeRef(TermRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "collection"), "immutable"), "List"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) Inlined(None, Nil, Typed(Apply(Select(New(TypeIdent("Baz")), ""), Nil), Applied(TypeIdent("&"), List(TypeIdent("Foo"), TypeIdent("Bar"))))) AndType(TypeRef(ThisType(TypeRef(NoPrefix(), "")), "Foo"), TypeRef(ThisType(TypeRef(NoPrefix(), "")), "Bar")) diff --git a/tests/run-macros/tasty-extractors-types.check b/tests/run-macros/tasty-extractors-types.check index b98c7b2536fe..5961a52eecd1 100644 --- a/tests/run-macros/tasty-extractors-types.check +++ b/tests/run-macros/tasty-extractors-types.check @@ -2,11 +2,11 @@ Inferred() TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int") Inferred() -AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "immutable")), "List"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"))) +AppliedType(TypeRef(TermRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "collection"), "immutable"), "List"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"))) Inferred() -AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "immutable")), "Map"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) +AppliedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "collection")), "immutable"), "Map"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"), TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "")), "scala"), "Int"))) Inferred() -AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "immutable")), "Map"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"), TypeRef(NoPrefix(), "I"))) +AppliedType(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "collection")), "immutable"), "Map"), List(TypeRef(TermRef(ThisType(TypeRef(NoPrefix(), "scala")), "Predef"), "String"), TypeRef(NoPrefix(), "I"))) diff --git a/tests/run/typeclass-derivation3.check b/tests/run/typeclass-derivation3.check index 12bb24628b0c..34b8a6b81f7e 100644 --- a/tests/run/typeclass-derivation3.check +++ b/tests/run/typeclass-derivation3.check @@ -9,6 +9,6 @@ Cons(hd = Cons(hd = 11, tl = Cons(hd = 22, tl = Cons(hd = 33, tl = Nil))), tl = Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil)) Cons(hd = Left(x = 1), tl = Cons(hd = Right(x = Pair(x = 2, y = 3)), tl = Nil)) true -::(head = 1, next$access$1 = ::(head = 2, next$access$1 = ::(head = 3, next$access$1 = Nil()))) -::(head = ::(head = 1, next$access$1 = Nil()), next$access$1 = ::(head = ::(head = 2, next$access$1 = ::(head = 3, next$access$1 = Nil())), next$access$1 = Nil())) -::(head = Nil(), next$access$1 = ::(head = ::(head = 1, next$access$1 = Nil()), next$access$1 = ::(head = ::(head = 2, next$access$1 = ::(head = 3, next$access$1 = Nil())), next$access$1 = Nil()))) +::(head = 1, next = ::(head = 2, next = ::(head = 3, next = Nil()))) +::(head = ::(head = 1, next = Nil()), next = ::(head = ::(head = 2, next = ::(head = 3, next = Nil())), next = Nil())) +::(head = Nil(), next = ::(head = ::(head = 1, next = Nil()), next = ::(head = ::(head = 2, next = ::(head = 3, next = Nil())), next = Nil()))) From e459d887f0ab298b318de9d8b052ceee0767aa3b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 27 Sep 2023 16:57:21 +0200 Subject: [PATCH 105/117] Fix inline patterns not reducing --- compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala index 2dbfd9117f48..9197f2f52f06 100644 --- a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -329,7 +329,7 @@ class InlineReducer(inliner: Inliner)(using Context): val paramCls = paramType.classSymbol if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { val caseAccessors = - if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + if (paramCls.is(Scala2x, butNot = Scala2Tasty)) paramCls.caseAccessors.filter(_.is(Method)) else paramCls.asClass.paramAccessors val selectors = for (accessor <- caseAccessors) From 604656e435563fb3e657a259cf4fa05dec4a7c23 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Sep 2023 10:50:20 +0200 Subject: [PATCH 106/117] FIXME: show generated code of regression --- .../backend/jvm/DottyBytecodeTests.scala | 96 ++++++++++++------- project/Build.scala | 5 +- 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index 70d3c72500b2..ea195c0529ab 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1049,44 +1049,54 @@ class DottyBytecodeTests extends DottyBytecodeTest { val m1Meth = getMethod(fooClass, "m1") assertSameCode(m1Meth, List( + // FIXME regression accessing `::`, `Option` and `Tuple2` members. VarOp(ALOAD, 1), VarOp(ASTORE, 2), VarOp(ALOAD, 2), TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"), - Jump(IFEQ, Label(19)), + Jump(IFEQ, Label(28)), + Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), + Invoke(INVOKEVIRTUAL, "scala/package$", "$colon$colon", "()Lscala/collection/immutable/$colon$colon$;", false), VarOp(ALOAD, 2), TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon$", "unapply", "(Lscala/collection/immutable/$colon$colon;)Lscala/Option;", false), VarOp(ASTORE, 3), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false), - VarOp(ASTORE, 4), + Invoke(INVOKEVIRTUAL, "scala/Option", "isEmpty", "()Z", false), + Jump(IFNE, Label(28)), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false), + Invoke(INVOKEVIRTUAL, "scala/Option", "get", "()Ljava/lang/Object;", false), + TypeOp(CHECKCAST, "scala/Tuple2"), + VarOp(ASTORE, 4), + VarOp(ALOAD, 4), + Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_1", "()Ljava/lang/Object;", false), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), VarOp(ISTORE, 5), VarOp(ALOAD, 4), + Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_2", "()Ljava/lang/Object;", false), + TypeOp(CHECKCAST, "scala/collection/immutable/List"), VarOp(ASTORE, 6), VarOp(ILOAD, 5), Op(IRETURN), - Label(19), + Label(28), Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), Invoke(INVOKEVIRTUAL, "scala/package$", "Nil", "()Lscala/collection/immutable/Nil$;", false), VarOp(ALOAD, 2), VarOp(ASTORE, 7), Op(DUP), - Jump(IFNONNULL, Label(31)), + Jump(IFNONNULL, Label(40)), Op(POP), VarOp(ALOAD, 7), - Jump(IFNULL, Label(36)), - Jump(GOTO, Label(40)), - Label(31), + Jump(IFNULL, Label(45)), + Jump(GOTO, Label(49)), + Label(40), VarOp(ALOAD, 7), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), - Jump(IFEQ, Label(40)), - Label(36), + Jump(IFEQ, Label(49)), + Label(45), IntOp(BIPUSH, 20), Op(IRETURN), - Label(40), + Label(49), TypeOp(NEW, "scala/MatchError"), Op(DUP), VarOp(ALOAD, 2), @@ -1099,45 +1109,57 @@ class DottyBytecodeTests extends DottyBytecodeTest { val m2Meth = getMethod(fooClass, "m2") assertSameCode(m2Meth, List( + // FIXME regression accessing `::`, `Option` and `Tuple2` members. VarOp(ALOAD, 1), VarOp(ASTORE, 2), VarOp(ALOAD, 2), TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"), - Jump(IFEQ, Label(42)), + Jump(IFEQ, Label(53)), + Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), + Invoke(INVOKEVIRTUAL, "scala/package$", "$colon$colon", "()Lscala/collection/immutable/$colon$colon$;", false), VarOp(ALOAD, 2), TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon$", "unapply", "(Lscala/collection/immutable/$colon$colon;)Lscala/Option;", false), VarOp(ASTORE, 3), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false), - Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), - VarOp(ISTORE, 4), + Invoke(INVOKEVIRTUAL, "scala/Option", "isEmpty", "()Z", false), + Jump(IFNE, Label(53)), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false), - VarOp(ASTORE, 5), + Invoke(INVOKEVIRTUAL, "scala/Option", "get", "()Ljava/lang/Object;", false), + TypeOp(CHECKCAST, "scala/Tuple2"), + VarOp(ASTORE, 4), + VarOp(ALOAD, 4), + Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_1", "()Ljava/lang/Object;", false), + Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), + VarOp(ISTORE, 5), + VarOp(ALOAD, 4), + Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_2", "()Ljava/lang/Object;", false), + TypeOp(CHECKCAST, "scala/collection/immutable/List"), + VarOp(ASTORE, 6), Op(ICONST_1), - VarOp(ILOAD, 4), - Jump(IF_ICMPNE, Label(19)), - Jump(GOTO, Label(28)), - Label(19), + VarOp(ILOAD, 5), + Jump(IF_ICMPNE, Label(30)), + Jump(GOTO, Label(39)), + Label(30), Op(ICONST_2), - VarOp(ILOAD, 4), - Jump(IF_ICMPNE, Label(25)), - Jump(GOTO, Label(28)), - Label(25), - Jump(GOTO, Label(34)), - Label(28), - VarOp(ALOAD, 5), - VarOp(ASTORE, 6), + VarOp(ILOAD, 5), + Jump(IF_ICMPNE, Label(36)), + Jump(GOTO, Label(39)), + Label(36), + Jump(GOTO, Label(45)), + Label(39), + VarOp(ALOAD, 6), + VarOp(ASTORE, 7), IntOp(BIPUSH, 10), Op(IRETURN), - Label(34), - VarOp(ILOAD, 4), - VarOp(ISTORE, 7), - VarOp(ALOAD, 5), - VarOp(ASTORE, 8), - VarOp(ILOAD, 7), + Label(45), + VarOp(ILOAD, 5), + VarOp(ISTORE, 8), + VarOp(ALOAD, 6), + VarOp(ASTORE, 9), + VarOp(ILOAD, 8), Op(IRETURN), - Label(42), + Label(53), IntOp(BIPUSH, 20), Op(IRETURN), )) diff --git a/project/Build.scala b/project/Build.scala index 03a748761da1..c93af2abb822 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -641,7 +641,7 @@ object Build { "-Ddotty.tests.classes.tastyCore=" + jars("tasty-core"), "-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"), "-Ddotty.tests.classes.scalaLibrary=" + findArtifactPath(externalDeps, "scala-library"), - "-Ddotty.tests.tasties.scalaLibrary=" + jars("stdlib-bootstrapped-tasty"), + "-Ddotty.tests.tasties.scalaLibrary=" + jars("scala2-library-tasty"), "-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"), "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), @@ -831,7 +831,7 @@ object Build { // library on the compiler classpath since the non-bootstrapped one // may not be binary-compatible. "scala3-library" -> (`scala3-library-bootstrapped` / Compile / packageBin).value, - "stdlib-bootstrapped-tasty" -> (`stdlib-bootstrapped-tasty` / Compile / packageBin).value, + "scala2-library-tasty" -> (`scala2-library-tasty` / Compile / packageBin).value, ).mapValues(_.getAbsolutePath) } }.value, @@ -1161,6 +1161,7 @@ object Build { settings(commonBootstrappedSettings). settings( javaOptions := (`scala3-compiler-bootstrapped` / javaOptions).value, + scalacOptions += "-Ycheck:all", Test / javaOptions += "-Ddotty.scala.library=" + (`scala2-library-bootstrapped` / Compile / packageBin).value.getAbsolutePath, Compile / compile / fullClasspath ~= { _.filterNot(file => file.data.getName == s"scala-library-$stdlibBootstrappedVersion.jar") From 4814ceefcd3a9d07b351d1192cec3241f534662d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Sep 2023 11:09:43 +0200 Subject: [PATCH 107/117] State problematic paths explicitly --- .../src/dotty/tools/dotc/transform/YCheckPositions.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala index 0cf5b60468dd..d89ff5cd9051 100644 --- a/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala +++ b/compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala @@ -25,8 +25,10 @@ class YCheckPositions extends Phase { val checker = new TreeTraverser { private var sources: List[SourceFile] = ctx.source :: Nil def traverse(tree: tpd.Tree)(using Context): Unit = { - if tree.source.toString != "library/src/scala/runtime/stdLibPatches/Predef.scala" then // FIXME: remove this workaround - + // FIXME: remove this workaround + if !(tree.source.toString == "library/src/scala/runtime/stdLibPatches/Predef.scala" + && sources.head.toString.endsWith("src_managed/main/scala-library-src/scala/Predef.scala")) // TODO should this be library/src/scala/runtime/stdLibPatches/Predef.scala or library/src/scala/Predef.scala? + then // Check current context is correct assert(ctx.source == sources.head) if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && !tree.isInstanceOf[Inlined] && ctx.typerState.isGlobalCommittable) From 723286d760d4c64f7441b883b0d88ac49b7ee42e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 28 Sep 2023 11:14:46 +0200 Subject: [PATCH 108/117] Patch exhaustivity tests The order of `Some` and `None` seem to have reversed. Not sure why and if we should care about it. The following seems to be a valid error but with the wrong error message: ``` -- Error: tests/patmat/t4408.scala:3:16 ---------------------------------------- 3 | case Nil => Unit | ^^^^ |Reference to object Unit in package scala should not have survived, |it should have been processed and eliminated during expansion of an enclosing macro or term erasure. ``` --- tests/patmat/i13003.check | 4 ++-- tests/patmat/patmat-ortype.check | 2 +- tests/patmat/t4408.scala | 2 +- tests/patmat/t7746.check | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/patmat/i13003.check b/tests/patmat/i13003.check index 19fb6a466549..459a7b881817 100644 --- a/tests/patmat/i13003.check +++ b/tests/patmat/i13003.check @@ -1,4 +1,4 @@ 4: Pattern Match Exhaustivity: One(Two(None)) 7: Pattern Match Exhaustivity: Two(None) -10: Pattern Match Exhaustivity: None, Some(None) -13: Pattern Match Exhaustivity: None, Some(None), Some(Some(None)) +10: Pattern Match Exhaustivity: Some(None), None +13: Pattern Match Exhaustivity: Some(Some(None)), Some(None), None diff --git a/tests/patmat/patmat-ortype.check b/tests/patmat/patmat-ortype.check index f36978068485..0bd790437769 100644 --- a/tests/patmat/patmat-ortype.check +++ b/tests/patmat/patmat-ortype.check @@ -1,3 +1,3 @@ 8: Pattern Match Exhaustivity: _: String -18: Pattern Match Exhaustivity: None, Some(_: String) +18: Pattern Match Exhaustivity: Some(_: String), None 36: Pattern Match Exhaustivity: Some(_: String) diff --git a/tests/patmat/t4408.scala b/tests/patmat/t4408.scala index 419b6636971e..a346915adcf6 100644 --- a/tests/patmat/t4408.scala +++ b/tests/patmat/t4408.scala @@ -1,6 +1,6 @@ object Test { def printList(in: List[String]): Unit = in match { - case Nil => Unit + case Nil => () case (s: String) :: Nil => println(s) diff --git a/tests/patmat/t7746.check b/tests/patmat/t7746.check index 327d64b9c2b6..cdba0449f0ab 100644 --- a/tests/patmat/t7746.check +++ b/tests/patmat/t7746.check @@ -1 +1 @@ -2: Pattern Match Exhaustivity: None, Some(_) +2: Pattern Match Exhaustivity: Some(_), None From 2e430020be37b4ddf6738082e8145496b438ceea Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 29 Sep 2023 15:03:18 +0200 Subject: [PATCH 109/117] Only use stdlib-bootstrapped-tasty to bootstrapped tests --- compiler/test/dotty/Properties.scala | 2 +- compiler/test/dotty/tools/vulpix/TestConfiguration.scala | 8 ++++---- project/Build.scala | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index 00d512de757c..c1d757378919 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -83,7 +83,7 @@ object Properties { def scalaLibrary: String = sys.props("dotty.tests.classes.scalaLibrary") /** scala-library TASTy jar */ - def scalaLibraryTasty: String = sys.props("dotty.tests.tasties.scalaLibrary") + def scalaLibraryTasty: Option[String] = sys.props.get("dotty.tests.tasties.scalaLibrary") /** scala-asm jar */ def scalaAsm: String = sys.props("dotty.tests.classes.scalaAsm") diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index d029cbd2be1e..04be00fe921e 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -25,14 +25,14 @@ object TestConfiguration { "-Xverify-signatures" ) - val basicClasspath = mkClasspath(List( - Properties.scalaLibraryTasty, + val basicClasspath = mkClasspath( + Properties.scalaLibraryTasty.toList ::: List( Properties.scalaLibrary, Properties.dottyLibrary )) - val withCompilerClasspath = mkClasspath(List( - Properties.scalaLibraryTasty, + val withCompilerClasspath = mkClasspath( + Properties.scalaLibraryTasty.toList ::: List( Properties.scalaLibrary, Properties.scalaAsm, Properties.jlineTerminal, diff --git a/project/Build.scala b/project/Build.scala index c93af2abb822..88a4981c4f72 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -641,11 +641,11 @@ object Build { "-Ddotty.tests.classes.tastyCore=" + jars("tasty-core"), "-Ddotty.tests.classes.compilerInterface=" + findArtifactPath(externalDeps, "compiler-interface"), "-Ddotty.tests.classes.scalaLibrary=" + findArtifactPath(externalDeps, "scala-library"), - "-Ddotty.tests.tasties.scalaLibrary=" + jars("scala2-library-tasty"), "-Ddotty.tests.classes.scalaAsm=" + findArtifactPath(externalDeps, "scala-asm"), "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), - ) + ) ++ + jars.get("stdlib-bootstrapped-tasty").map("-Ddotty.tests.tasties.scalaLibrary=" + _) }, javaOptions ++= Seq( @@ -831,7 +831,6 @@ object Build { // library on the compiler classpath since the non-bootstrapped one // may not be binary-compatible. "scala3-library" -> (`scala3-library-bootstrapped` / Compile / packageBin).value, - "scala2-library-tasty" -> (`scala2-library-tasty` / Compile / packageBin).value, ).mapValues(_.getAbsolutePath) } }.value, From 2ff221dce37cc37fa65499171f8c8df72dc28b14 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Oct 2023 13:17:25 +0200 Subject: [PATCH 110/117] Fix StdlibBootstrappedDummy path --- .../scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {stdlib-bootstrapped => scala2-library-bootstrapped}/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala (100%) diff --git a/stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala b/scala2-library-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala similarity index 100% rename from stdlib-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala rename to scala2-library-bootstrapped/src/scala/StdlibBootstrappedDummy/StdlibBootstrappedDummy.scala From 7c5311cf2739a39b8719dcf30d7b9cf2dc004fb2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 13 Oct 2023 13:43:00 +0200 Subject: [PATCH 111/117] Fix bootstrapped test dependency on scala2-library-tasty --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 88a4981c4f72..0e9adc708345 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -645,7 +645,7 @@ object Build { "-Ddotty.tests.classes.jlineTerminal=" + findArtifactPath(externalDeps, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findArtifactPath(externalDeps, "jline-reader"), ) ++ - jars.get("stdlib-bootstrapped-tasty").map("-Ddotty.tests.tasties.scalaLibrary=" + _) + jars.get("scala2-library-tasty").map("-Ddotty.tests.tasties.scalaLibrary=" + _) }, javaOptions ++= Seq( From 78bee4eeaaaa57b60184ad8ad55d19c8ee7c1d8b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 16 Oct 2023 15:48:37 +0200 Subject: [PATCH 112/117] Patch identification of synthetic unapply methods * With the tasty library we are not tagging these methods with the Case flag * The case accessors are listed as vals and not methods --- .../tools/dotc/transform/PatternMatcher.scala | 7 +- .../backend/jvm/DottyBytecodeTests.scala | 96 +++++++------------ 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index ac1e1868f26e..79038bf12bb3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -327,10 +327,13 @@ object PatternMatcher { /** Plan for matching the result of an unapply against argument patterns `args` */ def unapplyPlan(unapp: Tree, args: List[Tree]): Plan = { def caseClass = unapp.symbol.owner.linkedClass - lazy val caseAccessors = caseClass.caseAccessors.filter(_.is(Method)) + lazy val caseAccessors = caseClass.caseAccessors.filter(sym => sym.is(Method) || sym.owner.is(Scala2Tasty)) def isSyntheticScala2Unapply(sym: Symbol) = - sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x) + sym.is(Synthetic) && ( + (sym.is(Case) && sym.owner.is(Scala2x)) + || sym.owner.is(Scala2Tasty) + ) def tupleApp(i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method ref(defn.RuntimeTuplesModule) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index ea195c0529ab..70d3c72500b2 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1049,54 +1049,44 @@ class DottyBytecodeTests extends DottyBytecodeTest { val m1Meth = getMethod(fooClass, "m1") assertSameCode(m1Meth, List( - // FIXME regression accessing `::`, `Option` and `Tuple2` members. VarOp(ALOAD, 1), VarOp(ASTORE, 2), VarOp(ALOAD, 2), TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"), - Jump(IFEQ, Label(28)), - Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), - Invoke(INVOKEVIRTUAL, "scala/package$", "$colon$colon", "()Lscala/collection/immutable/$colon$colon$;", false), + Jump(IFEQ, Label(19)), VarOp(ALOAD, 2), TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon$", "unapply", "(Lscala/collection/immutable/$colon$colon;)Lscala/Option;", false), VarOp(ASTORE, 3), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/Option", "isEmpty", "()Z", false), - Jump(IFNE, Label(28)), - VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/Option", "get", "()Ljava/lang/Object;", false), - TypeOp(CHECKCAST, "scala/Tuple2"), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false), VarOp(ASTORE, 4), - VarOp(ALOAD, 4), - Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_1", "()Ljava/lang/Object;", false), + VarOp(ALOAD, 3), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), VarOp(ISTORE, 5), VarOp(ALOAD, 4), - Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_2", "()Ljava/lang/Object;", false), - TypeOp(CHECKCAST, "scala/collection/immutable/List"), VarOp(ASTORE, 6), VarOp(ILOAD, 5), Op(IRETURN), - Label(28), + Label(19), Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), Invoke(INVOKEVIRTUAL, "scala/package$", "Nil", "()Lscala/collection/immutable/Nil$;", false), VarOp(ALOAD, 2), VarOp(ASTORE, 7), Op(DUP), - Jump(IFNONNULL, Label(40)), + Jump(IFNONNULL, Label(31)), Op(POP), VarOp(ALOAD, 7), - Jump(IFNULL, Label(45)), - Jump(GOTO, Label(49)), - Label(40), + Jump(IFNULL, Label(36)), + Jump(GOTO, Label(40)), + Label(31), VarOp(ALOAD, 7), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), - Jump(IFEQ, Label(49)), - Label(45), + Jump(IFEQ, Label(40)), + Label(36), IntOp(BIPUSH, 20), Op(IRETURN), - Label(49), + Label(40), TypeOp(NEW, "scala/MatchError"), Op(DUP), VarOp(ALOAD, 2), @@ -1109,57 +1099,45 @@ class DottyBytecodeTests extends DottyBytecodeTest { val m2Meth = getMethod(fooClass, "m2") assertSameCode(m2Meth, List( - // FIXME regression accessing `::`, `Option` and `Tuple2` members. VarOp(ALOAD, 1), VarOp(ASTORE, 2), VarOp(ALOAD, 2), TypeOp(INSTANCEOF, "scala/collection/immutable/$colon$colon"), - Jump(IFEQ, Label(53)), - Field(GETSTATIC, "scala/package$", "MODULE$", "Lscala/package$;"), - Invoke(INVOKEVIRTUAL, "scala/package$", "$colon$colon", "()Lscala/collection/immutable/$colon$colon$;", false), + Jump(IFEQ, Label(42)), VarOp(ALOAD, 2), TypeOp(CHECKCAST, "scala/collection/immutable/$colon$colon"), - Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon$", "unapply", "(Lscala/collection/immutable/$colon$colon;)Lscala/Option;", false), VarOp(ASTORE, 3), VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/Option", "isEmpty", "()Z", false), - Jump(IFNE, Label(53)), - VarOp(ALOAD, 3), - Invoke(INVOKEVIRTUAL, "scala/Option", "get", "()Ljava/lang/Object;", false), - TypeOp(CHECKCAST, "scala/Tuple2"), - VarOp(ASTORE, 4), - VarOp(ALOAD, 4), - Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_1", "()Ljava/lang/Object;", false), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "head", "()Ljava/lang/Object;", false), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), - VarOp(ISTORE, 5), - VarOp(ALOAD, 4), - Invoke(INVOKEVIRTUAL, "scala/Tuple2", "_2", "()Ljava/lang/Object;", false), - TypeOp(CHECKCAST, "scala/collection/immutable/List"), - VarOp(ASTORE, 6), + VarOp(ISTORE, 4), + VarOp(ALOAD, 3), + Invoke(INVOKEVIRTUAL, "scala/collection/immutable/$colon$colon", "next$access$1", "()Lscala/collection/immutable/List;", false), + VarOp(ASTORE, 5), Op(ICONST_1), - VarOp(ILOAD, 5), - Jump(IF_ICMPNE, Label(30)), - Jump(GOTO, Label(39)), - Label(30), + VarOp(ILOAD, 4), + Jump(IF_ICMPNE, Label(19)), + Jump(GOTO, Label(28)), + Label(19), Op(ICONST_2), - VarOp(ILOAD, 5), - Jump(IF_ICMPNE, Label(36)), - Jump(GOTO, Label(39)), - Label(36), - Jump(GOTO, Label(45)), - Label(39), - VarOp(ALOAD, 6), - VarOp(ASTORE, 7), + VarOp(ILOAD, 4), + Jump(IF_ICMPNE, Label(25)), + Jump(GOTO, Label(28)), + Label(25), + Jump(GOTO, Label(34)), + Label(28), + VarOp(ALOAD, 5), + VarOp(ASTORE, 6), IntOp(BIPUSH, 10), Op(IRETURN), - Label(45), - VarOp(ILOAD, 5), - VarOp(ISTORE, 8), - VarOp(ALOAD, 6), - VarOp(ASTORE, 9), - VarOp(ILOAD, 8), + Label(34), + VarOp(ILOAD, 4), + VarOp(ISTORE, 7), + VarOp(ALOAD, 5), + VarOp(ASTORE, 8), + VarOp(ILOAD, 7), Op(IRETURN), - Label(53), + Label(42), IntOp(BIPUSH, 20), Op(IRETURN), )) From b5f7cfb2bf47499645e6a7eed5e75214064af523 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Oct 2023 11:31:34 +0200 Subject: [PATCH 113/117] Add Case flag to synthetic unapply methods with -Yscala2-stdlib --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 ++++-- .../src/dotty/tools/dotc/transform/PatternMatcher.scala | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c38ba7ce4740..99fb34ce5a7a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -805,13 +805,15 @@ object desugar { else if caseClassInScala2StdLib then scala2LibCompatUnapplyRhs(unapplyParam.name) else Ident(unapplyParam.name) val unapplyResTp = if (arity == 0) Literal(Constant(true)) else TypeTree() - + val unapplyMods = + if ctx.settings.Yscala2Stdlib.value then synthetic | Case + else synthetic DefDef( methName, joinParams(derivedTparams, (unapplyParam :: Nil) :: Nil), unapplyResTp, unapplyRHS - ).withMods(synthetic) + ).withMods(unapplyMods) } val toStringMeth = DefDef(nme.toString_, Nil, TypeTree(), Literal(Constant(className.toString))).withMods(Modifiers(Override | Synthetic)) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 79038bf12bb3..2439d79cfc8c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -330,10 +330,7 @@ object PatternMatcher { lazy val caseAccessors = caseClass.caseAccessors.filter(sym => sym.is(Method) || sym.owner.is(Scala2Tasty)) def isSyntheticScala2Unapply(sym: Symbol) = - sym.is(Synthetic) && ( - (sym.is(Case) && sym.owner.is(Scala2x)) - || sym.owner.is(Scala2Tasty) - ) + sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x) def tupleApp(i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method ref(defn.RuntimeTuplesModule) From 2e884568cb8a5a791203a8e93d8dc78ba1848567 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Oct 2023 11:48:03 +0200 Subject: [PATCH 114/117] Refine unapply case accessor condition --- .../src/dotty/tools/dotc/transform/PatternMatcher.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 2439d79cfc8c..80f01291861f 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -327,7 +327,9 @@ object PatternMatcher { /** Plan for matching the result of an unapply against argument patterns `args` */ def unapplyPlan(unapp: Tree, args: List[Tree]): Plan = { def caseClass = unapp.symbol.owner.linkedClass - lazy val caseAccessors = caseClass.caseAccessors.filter(sym => sym.is(Method) || sym.owner.is(Scala2Tasty)) + lazy val scala2CaseAccessors = + if caseClass.is(Scala2Tasty) then caseClass.caseAccessors + else caseClass.caseAccessors.filter(_.is(Method)) def isSyntheticScala2Unapply(sym: Symbol) = sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x) @@ -337,11 +339,11 @@ object PatternMatcher { .select(defn.RuntimeTuples_apply) .appliedTo(receiver, Literal(Constant(i))) - if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length) + if (isSyntheticScala2Unapply(unapp.symbol) && scala2CaseAccessors.length == args.length) def tupleSel(sym: Symbol) = ref(scrutinee).select(sym) val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe match { case tp: OrType => tp.join case tp => tp }) // widen even hard unions, to see if it's a union of tuples - val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else caseAccessors.map(tupleSel) + val components = if isGenericTuple then scala2CaseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else scala2CaseAccessors.map(tupleSel) matchArgsPlan(components, args, onSuccess) else if (unapp.tpe <:< (defn.BooleanType)) TestPlan(GuardTest, unapp, unapp.span, onSuccess) From 44e21f5337ca1a54d353fceaf5c5cf3282398c8f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 17 Oct 2023 11:53:17 +0200 Subject: [PATCH 115/117] Avoid changing the behavior of the test due to unused varinings --- tests/neg/f-interpolator-neg.check | 256 +---------------------------- tests/neg/f-interpolator-neg.scala | 2 +- 2 files changed, 5 insertions(+), 253 deletions(-) diff --git a/tests/neg/f-interpolator-neg.check b/tests/neg/f-interpolator-neg.check index a272022ef4c0..ea8df052589e 100644 --- a/tests/neg/f-interpolator-neg.check +++ b/tests/neg/f-interpolator-neg.check @@ -1,255 +1,3 @@ --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:4:25 --------------------------------------------- -4 | new StringContext().f() // error - | ^^^^^^^^^^^^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:5:52 --------------------------------------------- -5 | new StringContext("", " is ", "%2d years old").f(s) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:6:52 --------------------------------------------- -6 | new StringContext("", " is ", "%2d years old").f(s, d, d) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:11:4 --------------------------------------------- -11 | f"$s%b" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:12:4 --------------------------------------------- -12 | f"$s%c" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:13:4 --------------------------------------------- -13 | f"$f%c" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:14:4 --------------------------------------------- -14 | f"$s%x" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:15:4 --------------------------------------------- -15 | f"$b%d" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:16:4 --------------------------------------------- -16 | f"$s%d" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:17:4 --------------------------------------------- -17 | f"$f%o" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:18:4 --------------------------------------------- -18 | f"$s%e" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:19:4 --------------------------------------------- -19 | f"$b%f" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:24:4 --------------------------------------------- -24 | f"$s%+ 0,(s" // error - | ^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:25:4 --------------------------------------------- -25 | f"$c%#+ 0,(c" // error - | ^^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:26:4 --------------------------------------------- -26 | f"$d%#d" // error - | ^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:27:4 --------------------------------------------- -27 | f"$d%,x" // error - | ^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:28:4 --------------------------------------------- -28 | f"$d%+ (x" // error - | ^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:29:4 --------------------------------------------- -29 | f"$f%,(a" // error - | ^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:30:4 --------------------------------------------- -30 | f"$t%#+ 0,(tT" // error - | ^^^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:31:4 --------------------------------------------- -31 | f"%-#+ 0,(n" // error - | ^^^^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:36:4 --------------------------------------------- -36 | f"$c%.2c" // error - | ^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:37:4 --------------------------------------------- -37 | f"$d%.2d" // error - | ^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:38:4 --------------------------------------------- -38 | f"%.2%" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:39:4 --------------------------------------------- -39 | f"%.2n" // error - | ^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:40:4 --------------------------------------------- -40 | f"$f%.2a" // error - | ^^^^^^^^^ - | A pure expression does nothing in statement position; you may be omitting necessary parentheses - | - | longer explanation available when compiling with `-explain` --- [E129] Potential Issue Warning: tests/neg/f-interpolator-neg.scala:45:4 --------------------------------------------- -45 | f"% Date: Tue, 17 Oct 2023 12:03:41 +0200 Subject: [PATCH 116/117] Increase inlined recursion in test to ensure the limit is reached --- tests/neg/i13044.check | 8 ++++---- tests/neg/i13044.scala | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/neg/i13044.check b/tests/neg/i13044.check index c5584aadf767..1fbe978a49b8 100644 --- a/tests/neg/i13044.check +++ b/tests/neg/i13044.check @@ -1,5 +1,5 @@ --- Error: tests/neg/i13044.scala:50:40 --------------------------------------------------------------------------------- -50 | implicit def typeSchema: Schema[A] = Schema.gen // error // error +-- Error: tests/neg/i13044.scala:65:40 --------------------------------------------------------------------------------- +65 | implicit def typeSchema: Schema[A] = Schema.gen // error // error | ^^^^^^^^^^ | given instance gen is declared as `inline`, but was not inlined | @@ -71,8 +71,8 @@ 37 | inline given gen[A]: Schema[A] = derived | ^^^^^^^ -------------------------------------------------------------------------------------------------------------------- --- Error: tests/neg/i13044.scala:50:40 --------------------------------------------------------------------------------- -50 | implicit def typeSchema: Schema[A] = Schema.gen // error // error +-- Error: tests/neg/i13044.scala:65:40 --------------------------------------------------------------------------------- +65 | implicit def typeSchema: Schema[A] = Schema.gen // error // error | ^^^^^^^^^^ | method recurse is declared as `inline`, but was not inlined | diff --git a/tests/neg/i13044.scala b/tests/neg/i13044.scala index 081b642c604c..42417a9096f9 100644 --- a/tests/neg/i13044.scala +++ b/tests/neg/i13044.scala @@ -37,7 +37,22 @@ trait SchemaDerivation { inline given gen[A]: Schema[A] = derived } -case class H(i: Int) +case class X15(i: Int) +case class X14(i: X15) +case class X13(i: X14) +case class X12(i: X13) +case class X11(i: X12) +case class X10(i: X11) +case class X9(i: X10) +case class X8(i: X9) +case class X7(i: X8) +case class X6(i: X7) +case class X5(i: X6) +case class X4(i: X5) +case class X3(i: X4) +case class X2(i: X3) +case class X1(i: X2) +case class H(i: X1) case class G(h: H) case class F(g: G) case class E(f: Option[F]) From 8d7a90112fbeebf6f2a53973cfacc41f2dd87b2d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 19 Oct 2023 16:21:00 +0200 Subject: [PATCH 117/117] Patch pure base classes for FunctionN --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 3 ++- tests/neg-custom-args/captures/leaked-curried.check | 6 +++--- tests/neg-custom-args/captures/leaked-curried.scala | 1 + tests/neg-custom-args/captures/outer-var.check | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 60753e323a12..7ce281fb1b4e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -256,7 +256,8 @@ extension (cls: ClassSymbol) def pureBaseClass(using Context): Option[Symbol] = cls.baseClasses.find: bc => defn.pureBaseClasses.contains(bc) - || bc.givenSelfType.dealiasKeepAnnots.match + || bc.is(CaptureChecked) + && bc.givenSelfType.dealiasKeepAnnots.match case CapturingType(_, refs) => refs.isAlwaysEmpty case RetainingType(_, refs) => refs.isEmpty case selfType => selfType.exists && selfType.captureSet.isAlwaysEmpty diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 979eed9b3098..ca2dba16ada1 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,4 +1,4 @@ --- 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^) cannot be referenced here; it is not included in the allowed capture set {} of pure base class trait Pure + |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo diff --git a/tests/neg-custom-args/captures/leaked-curried.scala b/tests/neg-custom-args/captures/leaked-curried.scala index 30ec4f7dbab5..295b8f5d9bb1 100644 --- a/tests/neg-custom-args/captures/leaked-curried.scala +++ b/tests/neg-custom-args/captures/leaked-curried.scala @@ -9,6 +9,7 @@ trait Box: def main(): Unit = val leaked = withCap: (io: Cap^) => class Foo extends Box, Pure: + self => val get: () ->{} () ->{io} Cap^ = () => () => io // error new Foo diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index ef89bae47a70..b7a438f30092 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -36,7 +36,7 @@ 16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Found: scala.collection.mutable.ListBuffer[box () => Unit] - | Required: 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