diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index d0fe07303e41..b4dbb76dc464 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -42,7 +42,8 @@ import dotty.tools.dotc.util.chaining.* import java.util.{Timer, TimerTask} /** A compiler run. Exports various methods to compile source files */ -class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo { +class Run(comp: Compiler, ictx: Context) +extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { /** Default timeout to stop looking for further implicit suggestions, in ms. * This is usually for the first import suggestion; subsequent suggestions @@ -519,6 +520,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint /** Print summary of warnings and errors encountered */ def printSummary(): Unit = { printMaxConstraint() + printMaxPath() val r = runContext.reporter if !r.errorsReported then profile.printSummary() @@ -529,6 +531,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint override def reset(): Unit = { super[ImplicitRunInfo].reset() super[ConstraintRunInfo].reset() + super[CaptureRunInfo].reset() myCtx = null myUnits = Nil myUnitsCached = Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 98d9a0ca85f6..414b27101b7d 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -56,7 +56,7 @@ class TreeTypeMap( /** Replace occurrences of `This(oldOwner)` in some prefix of a type * by the corresponding `This(newOwner)`. */ - private val mapOwnerThis = new TypeMap with cc.CaptureSet.IdempotentCaptRefMap { + private val mapOwnerThis = new TypeMap { private def mapPrefix(from: List[Symbol], to: List[Symbol], tp: Type): Type = from match { case Nil => tp case (cls: ClassSymbol) :: from1 => mapPrefix(from1, to.tail, tp.substThis(cls, to.head.thisType)) diff --git a/compiler/src/dotty/tools/dotc/cc/CCState.scala b/compiler/src/dotty/tools/dotc/cc/CCState.scala new file mode 100644 index 000000000000..c92c9faa6fe6 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/CCState.scala @@ -0,0 +1,165 @@ +package dotty.tools +package dotc +package cc + +import core.* +import CaptureSet.{CompareResult, CompareFailure, VarState} +import collection.mutable +import reporting.Message +import Contexts.Context +import Types.MethodType +import Symbols.Symbol + +/** Capture checking state, which is known to other capture checking components */ +class CCState: + import CCState.* + + // ------ Error diagnostics ----------------------------- + + /** Error reprting notes produces since the last call to `test` */ + var notes: List[ErrorNote] = Nil + + def addNote(note: ErrorNote): Unit = + if !notes.exists(_.getClass == note.getClass) then + notes = note :: notes + + def test(op: => CompareResult): CompareResult = + val saved = notes + notes = Nil + try op match + case res: CompareFailure => res.withNotes(notes) + case res => res + finally notes = saved + + def testOK(op: => Boolean): CompareResult = + test(if op then CompareResult.OK else CompareResult.Fail(Nil)) + + /** Warnings relating to upper approximations of capture sets with + * existentially bound variables. + */ + val approxWarnings: mutable.ListBuffer[Message] = mutable.ListBuffer() + + // ------ Level handling --------------------------- + + private var curLevel: Level = outermostLevel + + /** The level of the current environment. Levels start at 0 and increase for + * each nested function or class. -1 means the level is undefined. + */ + def currentLevel(using Context): Level = curLevel + + /** Perform `op` in the next inner level */ + inline def inNestedLevel[T](inline op: T)(using Context): T = + val saved = curLevel + curLevel = curLevel.nextInner + try op finally curLevel = saved + + /** Perform `op` in the next inner level unless `p` holds. */ + inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T = + val saved = curLevel + if !p then curLevel = curLevel.nextInner + try op finally curLevel = saved + + /** A map recording the level of a symbol */ + private val mySymLevel: mutable.Map[Symbol, Level] = mutable.Map() + + def symLevel(sym: Symbol): Level = mySymLevel.getOrElse(sym, undefinedLevel) + + def recordLevel(sym: Symbol)(using Context): Unit = mySymLevel(sym) = curLevel + + // ------ BiTypeMap adjustment ----------------------- + + private var myMapFutureElems = true + + /** When mapping a capture set with a BiTypeMap, should we create a BiMapped set + * so that future elements can also be mapped, and elements added to the BiMapped + * are back-propagated? Turned off when creating capture set variables for the + * first time, since we then do not want to change the binder to the original type + * without capture sets when back propagating. Error case where this shows: + * pos-customargs/captures/lists.scala, method m2c. + */ + def mapFutureElems(using Context) = myMapFutureElems + + /** Don't map future elements in this `op` */ + inline def withoutMappedFutureElems[T](op: => T)(using Context): T = + val saved = mapFutureElems + myMapFutureElems = false + try op finally myMapFutureElems = saved + + // ------ Iteration count of capture checking run + + private var iterCount = 1 + + def iterationId = iterCount + + def nextIteration[T](op: => T): T = + iterCount += 1 + try op finally iterCount -= 1 + + // ------ Global counters ----------------------- + + /** Next CaptureSet.Var id */ + var varId = 0 + + /** Next root id */ + var rootId = 0 + + // ------ VarState singleton objects ------------ + // See CaptureSet.VarState creation methods for documentation + + object Separate extends VarState.Separating + object HardSeparate extends VarState.Separating + object Unrecorded extends VarState.Unrecorded + object ClosedUnrecorded extends VarState.ClosedUnrecorded + + // ------ Context info accessed from companion object when isCaptureCheckingOrSetup is true + + private var openExistentialScopes: List[MethodType] = Nil + + private var capIsRoot: Boolean = false + +object CCState: + + opaque type Level = Int + + val undefinedLevel: Level = -1 + + val outermostLevel: Level = 0 + + extension (x: Level) + def isDefined: Boolean = x >= 0 + def <= (y: Level) = (x: Int) <= y + def nextInner: Level = if isDefined then x + 1 else x + + /** If we are currently in capture checking or setup, and `mt` is a method + * type that is not a prefix of a curried method, perform `op` assuming + * a fresh enclosing existential scope `mt`, otherwise perform `op` directly. + */ + inline def inNewExistentialScope[T](mt: MethodType)(op: => T)(using Context): T = + if isCaptureCheckingOrSetup then + val ccs = ccState + val saved = ccs.openExistentialScopes + if mt.marksExistentialScope then ccs.openExistentialScopes = mt :: ccs.openExistentialScopes + try op finally ccs.openExistentialScopes = saved + else + op + + /** The currently opened existential scopes */ + def openExistentialScopes(using Context): List[MethodType] = ccState.openExistentialScopes + + /** Run `op` under the assumption that `cap` can subsume all other capabilties + * except Result capabilities. Every use of this method should be scrutinized + * for whether it introduces an unsoundness hole. + */ + inline def withCapAsRoot[T](op: => T)(using Context): T = + if isCaptureCheckingOrSetup then + val ccs = ccState + val saved = ccs.capIsRoot + ccs.capIsRoot = true + try op finally ccs.capIsRoot = saved + else op + + /** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */ + def capIsRoot(using Context): Boolean = ccState.capIsRoot + +end CCState diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 908e3174bfce..4db4d868fd86 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -7,16 +7,10 @@ import Types.*, Symbols.*, Contexts.*, Annotations.*, Flags.* import Names.TermName import ast.{tpd, untpd} import Decorators.*, NameOps.* -import config.SourceVersion import config.Printers.capt import util.Property.Key import tpd.* -import StdNames.nme -import config.Feature -import collection.mutable -import CCState.* -import reporting.Message -import CaptureSet.{VarState, CompareResult, CompareFailure} +import CaptureSet.VarState /** Attachment key for capturing type trees */ private val Captures: Key[CaptureSet] = Key() @@ -24,35 +18,6 @@ private val Captures: Key[CaptureSet] = Key() /** Context property to print root.Fresh(...) as "fresh" instead of "cap" */ val PrintFresh: Key[Unit] = Key() -object ccConfig: - - /** If true, allow mapping capture set variables under captureChecking with maps that are neither - * bijective nor idempotent. We currently do now know how to do this correctly in all - * cases, though. - */ - inline val allowUnsoundMaps = false - - /** If enabled, use a special path in recheckClosure for closures - * that are eta expansions. This can improve some error messages. - */ - inline val handleEtaExpansionsSpecially = true - - /** Don't require @use for reach capabilities that are accessed - * only in a nested closure. This is unsound without additional - * mitigation measures, as shown by unsound-reach-5.scala. - */ - inline val deferredReaches = false - - /** If true, turn on separation checking */ - def useSepChecks(using Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) - - /** Not used currently. Handy for trying out new features */ - def newScheme(using Context): Boolean = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) - -end ccConfig - /** Are we at checkCaptures phase? */ def isCaptureChecking(using Context): Boolean = ctx.phaseId == Phases.checkCapturesPhase.id @@ -79,111 +44,6 @@ class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show) /** A base trait for data producing addenda to error messages */ trait ErrorNote -/** Capture checking state, which is known to other capture checking components */ -class CCState: - - /** Error reprting notes produces since the last call to `test` */ - var notes: List[ErrorNote] = Nil - - def addNote(note: ErrorNote): Unit = - if !notes.exists(_.getClass == note.getClass) then - notes = note :: notes - - def test(op: => CompareResult): CompareResult = - val saved = notes - notes = Nil - try op match - case res: CompareFailure => res.withNotes(notes) - case res => res - finally notes = saved - - def testOK(op: => Boolean): CompareResult = - test(if op then CompareResult.OK else CompareResult.Fail(Nil)) - - /** Warnings relating to upper approximations of capture sets with - * existentially bound variables. - */ - val approxWarnings: mutable.ListBuffer[Message] = mutable.ListBuffer() - - private var curLevel: Level = outermostLevel - private val symLevel: mutable.Map[Symbol, Int] = mutable.Map() - - private var openExistentialScopes: List[MethodType] = Nil - - private var capIsRoot: Boolean = false - -object CCState: - - opaque type Level = Int - - val undefinedLevel: Level = -1 - - val outermostLevel: Level = 0 - - /** The level of the current environment. Levels start at 0 and increase for - * each nested function or class. -1 means the level is undefined. - */ - def currentLevel(using Context): Level = ccState.curLevel - - /** Perform `op` in the next inner level - * @pre We are currently in capture checking or setup - */ - inline def inNestedLevel[T](inline op: T)(using Context): T = - val ccs = ccState - val saved = ccs.curLevel - ccs.curLevel = ccs.curLevel.nextInner - try op finally ccs.curLevel = saved - - /** Perform `op` in the next inner level unless `p` holds. - * @pre We are currently in capture checking or setup - */ - inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T = - val ccs = ccState - val saved = ccs.curLevel - if !p then ccs.curLevel = ccs.curLevel.nextInner - try op finally ccs.curLevel = saved - - /** If we are currently in capture checking or setup, and `mt` is a method - * type that is not a prefix of a curried method, perform `op` assuming - * a fresh enclosing existential scope `mt`, otherwise perform `op` directly. - */ - inline def inNewExistentialScope[T](mt: MethodType)(op: => T)(using Context): T = - if isCaptureCheckingOrSetup then - val ccs = ccState - val saved = ccs.openExistentialScopes - if mt.marksExistentialScope then ccs.openExistentialScopes = mt :: ccs.openExistentialScopes - try op finally ccs.openExistentialScopes = saved - else - op - - /** Run `op` under the assumption that `cap` can subsume all other capabilties - * except Result capabilities. Every use of this method should be scrutinized - * for whether it introduces an unsoundness hole. - */ - inline def withCapAsRoot[T](op: => T)(using Context): T = - if isCaptureCheckingOrSetup then - val ccs = ccState - val saved = ccs.capIsRoot - ccs.capIsRoot = true - try op finally ccs.capIsRoot = saved - else op - - /** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */ - def capIsRoot(using Context): Boolean = ccState.capIsRoot - - /** The currently opened existential scopes */ - def openExistentialScopes(using Context): List[MethodType] = ccState.openExistentialScopes - - extension (x: Level) - def isDefined: Boolean = x >= 0 - def <= (y: Level) = (x: Int) <= y - def nextInner: Level = if isDefined then x + 1 else x - - extension (sym: Symbol)(using Context) - def ccLevel: Level = ccState.symLevel.getOrElse(sym, -1) - def recordLevel() = ccState.symLevel(sym) = currentLevel -end CCState - /** The currently valid CCState */ def ccState(using Context): CCState = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 @@ -251,7 +111,8 @@ extension (tp: Type) case tp: TypeRef => tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) case tp: TypeParamRef => - tp.derivesFrom(defn.Caps_CapSet) + !tp.underlying.exists // might happen during construction of lambdas + || tp.derivesFrom(defn.Caps_CapSet) case root.Result(_) => true case AnnotatedType(parent, annot) => defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef @@ -322,8 +183,7 @@ 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 => - assert(!parent.derivesFrom(defn.Caps_CapSet)) + case ann: CaptureAnnotation if !parent.derivesFrom(defn.Caps_CapSet) => AnnotatedType(parent, ann.boxedAnnot) case ann => tp case tp: RealTypeBounds => @@ -335,7 +195,8 @@ extension (tp: Type) * are of the form this.C but their pathroot is still this.C, not this. */ final def pathRoot(using Context): Type = tp.dealias match - case tp1: NamedType if tp1.symbol.maybeOwner.isClass && !tp1.symbol.is(TypeParam) => + case tp1: NamedType + if tp1.symbol.maybeOwner.isClass && tp1.symbol != defn.captureRoot && !tp1.symbol.is(TypeParam) => tp1.prefix.pathRoot case tp1 => tp1 @@ -636,11 +497,11 @@ extension (tp: Type) foldOver(x, t) acc(false, tp) - def level(using Context): Level = + def level(using Context): CCState.Level = tp match - case tp: TermRef => tp.symbol.ccLevel - case tp: ThisType => tp.cls.ccLevel.nextInner - case _ => undefinedLevel + case tp: TermRef => ccState.symLevel(tp.symbol) + case tp: ThisType => ccState.symLevel(tp.cls).nextInner + case _ => CCState.undefinedLevel extension (tp: MethodType) /** A method marks an existential scope unless it is the prefix of a curried method */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index 0b810218c07c..98f1502a0c1c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -9,21 +9,30 @@ import typer.ErrorReporting.Addenda import util.common.alwaysTrue import scala.collection.mutable import CCState.* -import Periods.NoRunId +import Periods.{NoRunId, RunWidth} import compiletime.uninitialized import StdNames.nme import CaptureSet.VarState import Annotations.Annotation import config.Printers.capt +object CaptureRef: + opaque type Validity = Int + def validId(runId: Int, iterId: Int): Validity = + runId + (iterId << RunWidth) + def currentId(using Context): Validity = validId(ctx.runId, ccState.iterationId) + val invalid: Validity = validId(NoRunId, 0) + /** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs, * as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities. * If there are several annotations they come with an order: * `*` first, `.rd` next, `?` last. */ trait CaptureRef extends TypeProxy, ValueType: + import CaptureRef.* + private var myCaptureSet: CaptureSet | Null = uninitialized - private var myCaptureSetRunId: Int = NoRunId + private var myCaptureSetValid: Validity = invalid private var mySingletonCaptureSet: CaptureSet.Const | Null = null private var myDerivedRefs: List[AnnotatedType] = Nil @@ -130,20 +139,24 @@ trait CaptureRef extends TypeProxy, ValueType: /** The capture set of the type underlying this reference */ final def captureSetOfInfo(using Context): CaptureSet = - if ctx.runId == myCaptureSetRunId then myCaptureSet.nn + if myCaptureSetValid == currentId then myCaptureSet.nn else if myCaptureSet.asInstanceOf[AnyRef] eq CaptureSet.Pending then CaptureSet.empty else myCaptureSet = CaptureSet.Pending val computed = CaptureSet.ofInfo(this) - if !isCaptureChecking || ctx.mode.is(Mode.IgnoreCaptures) || underlying.isProvisional then + if !isCaptureChecking + || ctx.mode.is(Mode.IgnoreCaptures) + || !underlying.exists + || underlying.isProvisional + then myCaptureSet = null else myCaptureSet = computed - myCaptureSetRunId = ctx.runId + myCaptureSetValid = currentId computed final def invalidateCaches() = - myCaptureSetRunId = NoRunId + myCaptureSetValid = invalid /** x subsumes x * x =:= y ==> x subsumes y @@ -157,7 +170,7 @@ trait CaptureRef extends TypeProxy, ValueType: * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y * Contains[X, y] ==> X subsumes y */ - final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = VarState.Separate): Boolean = + final def subsumes(y: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = def subsumingRefs(x: Type, y: Type): Boolean = x match case x: CaptureRef => y match @@ -242,7 +255,7 @@ trait CaptureRef extends TypeProxy, ValueType: * the test again with canAddHidden = true as a last effort before we * fail a comparison. */ - def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context, vs: VarState = VarState.Separate): Boolean = + def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = def yIsExistential = y.stripReadOnly match case root.Result(_) => capt.println(i"failed existential $this >: $y") @@ -252,12 +265,18 @@ trait CaptureRef extends TypeProxy, ValueType: || this.match case root.Fresh(hidden) => vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y))) - || !y.stripReadOnly.isCap && !yIsExistential && canAddHidden && vs.addHidden(hidden, y) + || !y.stripReadOnly.isCap + && !yIsExistential + && !y.isInstanceOf[TermParamRef] + && canAddHidden + && vs.addHidden(hidden, y) case x @ root.Result(binder) => - if y.derivesFromSharedCapability then true - else + val result = y match + case y @ root.Result(_) => vs.unify(x, y) + case _ => y.derivesFromSharedCapability + if !result then ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y)) - false + result case _ => y match case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureRunInfo.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRunInfo.scala new file mode 100644 index 000000000000..06107992b592 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRunInfo.scala @@ -0,0 +1,25 @@ +package dotty.tools.dotc +package cc + +import core.Contexts.{Context, ctx} +import config.Printers.capt + +trait CaptureRunInfo: + self: Run => + private var maxSize = 0 + private var maxPath: List[CaptureSet.DerivedVar] = Nil + + def recordPath(size: Int, path: => List[CaptureSet.DerivedVar]): Unit = + if size > maxSize then + maxSize = size + maxPath = path + + def printMaxPath()(using Context): Unit = + if maxSize > 0 then + println(s"max derived capture set path length: $maxSize") + println(s"max derived capture set path: ${maxPath.map(_.summarize).reverse}") + + protected def reset(): Unit = + maxSize = 0 + maxPath = Nil +end CaptureRunInfo diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 688605dcc32d..93ca0956baed 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -51,12 +51,15 @@ sealed abstract class CaptureSet extends Showable: /** Is this capture set constant (i.e. not an unsolved capture variable)? * Solved capture variables count as constant. */ - def isConst: Boolean + def isConst(using Context): Boolean /** Is this capture set always empty? For unsolved capture veriables, returns * always false. */ - def isAlwaysEmpty: Boolean + def isAlwaysEmpty(using Context): Boolean + + /** Is this set provisionally solved, so that another cc run might unfreeze it? */ + def isProvisionallySolved(using Context): Boolean /** An optional level limit, or undefinedLevel if none exists. All elements of the set * must be at levels equal or smaller than the level of the set, if it is defined. @@ -71,14 +74,14 @@ sealed abstract class CaptureSet extends Showable: final def isNotEmpty: Boolean = !elems.isEmpty /** Convert to Const. @pre: isConst */ - def asConst: Const = this match + def asConst(using Context): Const = this match case c: Const => c case v: Var => assert(v.isConst) Const(v.elems) /** Cast to variable. @pre: !isConst */ - def asVar: Var = + def asVar(using Context): Var = assert(!isConst) asInstanceOf[Var] @@ -178,7 +181,7 @@ sealed abstract class CaptureSet extends Showable: /** {x} <:< this where <:< is subcapturing, but treating all variables * as frozen. */ - def accountsFor(x: CaptureRef)(using ctx: Context, vs: VarState = VarState.Separate): Boolean = + def accountsFor(x: CaptureRef)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean = def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}" @@ -207,7 +210,7 @@ sealed abstract class CaptureSet extends Showable: def mightAccountFor(x: CaptureRef)(using Context): Boolean = reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true): CCState.withCapAsRoot: // OK here since we opportunistically choose an alternative which gets checked later - elems.exists(_.subsumes(x)(using ctx, VarState.ClosedUnrecorded)) + elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded)) || !x.isRootCapability && { val elems = x.captureSetOfInfo.elems @@ -293,46 +296,52 @@ sealed abstract class CaptureSet extends Showable: val elems1 = elems.filter(p) if elems1 == elems then this else Const(elems.filter(p)) - else Filtered(asVar, p) + else + this match + case self: Filtered => Filtered(self.source, ref => self.p(ref) && p(ref)) + case _ => Filtered(asVar, p) /** 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. - * - * Note: We have a problem how we handle the situation where we have a mapped set - * - * cs2 = tm(cs1) - * - * and then the propagation solver adds a new element `x` to `cs2`. What do we - * know in this case about `cs1`? We can answer this question in a sound way only - * if `tm` is a bijection on capture references or it is idempotent on capture references. - * (see definition in IdempotentCapRefMap). - * If `tm` is a bijection we know that `tm^-1(x)` must be in `cs1`. If `tm` is idempotent - * one possible solution is that `x` is in `cs1`, which is what we assume in this case. - * That strategy is sound but not complete. - * - * If `tm` is some other map, we don't know how to handle this case. For now, - * we simply refuse to handle other maps. If they do need to be handled, - * `OtherMapped` provides some approximation to a solution, but it is neither - * sound nor complete. + * and joining the results. If the current capture set is a variable we handle this as + * follows: + * - If the map is a BiTypeMap, the same transformation is applied to all + * future additions of new elements. We try to fuse with previous maps to + * avoid long paths of BiTypeMapped sets. + * - If the map is some other map that maps the current set of elements + * to itself, return the current var. We implicitly assume that the map + * will also map any elements added in the future to themselves. This assumption + * can be tested to hold by setting the ccConfig.checkSkippedMaps setting to true. + * - If the map is some other map that does not map all elements to themselves, + * freeze the current set (i.e. make it porvisionally solved) and return + * the mapped elements as a constant set. */ - def map(tm: TypeMap)(using Context): CaptureSet = tm match - case tm: BiTypeMap => - val mappedElems = elems.map(tm.forward) - if isConst then - if mappedElems == elems then this - else Const(mappedElems) - else BiMapped(asVar, tm, mappedElems) - case tm: IdentityCaptRefMap => - this - case tm: AvoidMap if this.isInstanceOf[HiddenSet] => - this - case _ => - val mapped = mapRefs(elems, tm, tm.variance) - if isConst then - if mapped.isConst && mapped.elems == elems && !mapped.keepAlways then this - else mapped - else Mapped(asVar, tm, tm.variance, mapped) + def map(tm: TypeMap)(using Context): CaptureSet = + tm match + case tm: BiTypeMap => + val mappedElems = elems.map(tm.forward) + if isConst then + if mappedElems == elems then this + else Const(mappedElems) + else if ccState.mapFutureElems then + def unfused = BiMapped(asVar, tm, mappedElems) + this match + case self: BiMapped => self.bimap.fuse(tm) match + case Some(fused: BiTypeMap) => BiMapped(self.source, fused, mappedElems) + case _ => unfused + case _ => unfused + else this + case tm: IdentityCaptRefMap => + this + case tm: AvoidMap if this.isInstanceOf[HiddenSet] => + this + case _ => + val mapped = mapRefs(elems, tm, tm.variance) + if mapped.elems == elems then + if ccConfig.checkSkippedMaps && !isConst then asVar.skippedMaps += tm + this + else + if !isConst then asVar.markSolved(provisional = true) + mapped /** A mapping resulting from substituting parameters of a BindingType to a list of types */ def substParams(tl: BindingType, to: List[Type])(using Context) = @@ -368,7 +377,7 @@ sealed abstract class CaptureSet extends Showable: * to this set. This might result in the set being solved to be constant * itself. */ - protected def propagateSolved()(using Context): Unit = () + protected def propagateSolved(provisional: Boolean)(using Context): Unit = () /** This capture set with a description that tells where it comes from */ def withDescription(description: String): CaptureSet @@ -396,8 +405,6 @@ object CaptureSet: type Vars = SimpleIdentitySet[Var] type Deps = SimpleIdentitySet[CaptureSet] - @sharable private var varId = 0 - /** If set to `true`, capture stack traces that tell us where sets are created */ private final val debugSets = false @@ -438,11 +445,15 @@ object CaptureSet: /** The subclass of constant capture sets with given elements `elems` */ class Const private[CaptureSet] (val elems: Refs, val description: String = "") extends CaptureSet: - def isConst = true - def isAlwaysEmpty = elems.isEmpty + def isConst(using Context) = true + def isAlwaysEmpty(using Context) = elems.isEmpty + def isProvisionallySolved(using Context) = false def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = - addIfHiddenOrFail(elem) + val res = addIfHiddenOrFail(elem) + if !res.isOK && this.isProvisionallySolved then + println(i"Cannot add $elem to provisionally solved $this") + res def addDependent(cs: CaptureSet)(using Context, VarState) = CompareResult.OK @@ -470,9 +481,9 @@ object CaptureSet: * were not yet compiled with capture checking on. */ object Fluid extends Const(emptyRefs): - override def isAlwaysEmpty = false + override def isAlwaysEmpty(using Context) = false override def addThisElem(elem: CaptureRef)(using Context, VarState) = CompareResult.OK - override def accountsFor(x: CaptureRef)(using Context, VarState): Boolean = true + override def accountsFor(x: CaptureRef)(using Context)(using VarState): Boolean = true override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true override def toString = "" end Fluid @@ -484,13 +495,19 @@ object CaptureSet: /** A unique identification number for diagnostics */ val id = - varId += 1 - varId + val ccs = ccState + ccs.varId += 1 + ccs.varId //assert(id != 40) - /** A variable is solved if it is aproximated to a from-then-on constant set. */ - private var isSolved: Boolean = false + /** A variable is solved if it is aproximated to a from-then-on constant set. + * Interpretation: + * 0 not solved + * Int.MaxValue definitively solved + * n > 0 provisionally solved in iteration n + */ + private var solved: Int = 0 /** The elements currently known to be in the set */ protected var myElems: Refs = initialElems @@ -503,8 +520,9 @@ object CaptureSet: */ var deps: Deps = SimpleIdentitySet.empty - def isConst = isSolved - def isAlwaysEmpty = isSolved && elems.isEmpty + def isConst(using Context) = solved >= ccState.iterationId + def isAlwaysEmpty(using Context) = isConst && elems.isEmpty + def isProvisionallySolved(using Context): Boolean = solved > 0 && solved != Int.MaxValue def isMaybeSet = false // overridden in BiMapped @@ -542,6 +560,16 @@ object CaptureSet: def resetDeps()(using state: VarState): Unit = deps = state.deps(this) + /** Check that all maps recorded in skippedMaps map `elem` to itself + * or something subsumed by it. + */ + private def checkSkippedMaps(elem: CaptureRef)(using Context): Unit = + for tm <- skippedMaps do + val elem1 = tm(elem) + for elem1 <- tm(elem).captureSet.elems do + assert(elem.subsumes(elem1), + i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this") + final def addThisElem(elem: CaptureRef)(using Context, VarState): CompareResult = if isConst || !recordElemsState() then // Fail if variable is solved or given VarState is frozen addIfHiddenOrFail(elem) @@ -559,28 +587,42 @@ object CaptureSet: // assert(id != 5 || elems.size != 3, this) val res = (CompareResult.OK /: deps): (r, dep) => r.andAlso(dep.tryInclude(normElem, this)) + if ccConfig.checkSkippedMaps && res.isOK then checkSkippedMaps(elem) res.orElse: elems -= elem res.addToTrace(this) + private def isPartOf(binder: Type)(using Context): Boolean = + val find = new TypeAccumulator[Boolean]: + def apply(b: Boolean, t: Type) = + b || t.match + case CapturingType(p, refs) => (refs eq Var.this) || this(b, p) + case _ => foldOver(b, t) + find(false, binder) + // TODO: Also track allowable TermParamRefs and root.Results in capture sets private def levelOK(elem: CaptureRef)(using Context): Boolean = if elem.isRootCapability then !noUniversal else elem match case elem @ root.Result(mt) => - !noUniversal - && !CCState.openExistentialScopes.contains(elem) - // Opened existentials on the left cannot be added to nested capture sets on the right - // of a comparison. Test case is open-existential.scala. + !noUniversal && isPartOf(mt.resType) case elem: TermRef if level.isDefined => elem.prefix match case prefix: CaptureRef => levelOK(prefix) case _ => - elem.symbol.ccLevel <= level + ccState.symLevel(elem.symbol) <= level case elem: ThisType if level.isDefined => - elem.cls.ccLevel.nextInner <= level + ccState.symLevel(elem.cls).nextInner <= level + case elem: ParamRef if !this.isInstanceOf[BiMapped] => + isPartOf(elem.binder.resType) + || { + capt.println( + i"""LEVEL ERROR $elem for $this + |elem binder = ${elem.binder}""") + false + } case ReachCapability(elem1) => levelOK(elem1) case ReadOnlyCapability(elem1) => @@ -645,21 +687,22 @@ object CaptureSet: * in the results of defs and vals. */ def solve()(using Context): Unit = - if !isConst then - CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later - val approx = upperApprox(empty) - .map(root.CapToFresh(NoSymbol).inverse) // Fresh --> cap - .showing(i"solve $this = $result", capt) - //println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}") - val newElems = approx.elems -- elems - given VarState() - if tryInclude(newElems, empty).isOK then - markSolved() + CCState.withCapAsRoot: // // OK here since we infer parameter types that get checked later + val approx = upperApprox(empty) + .map(root.CapToFresh(NoSymbol).inverse) // Fresh --> cap + .showing(i"solve $this = $result", capt) + //println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}") + val newElems = approx.elems -- elems + given VarState() + if tryInclude(newElems, empty).isOK then + markSolved(provisional = false) /** Mark set as solved and propagate this info to all dependent sets */ - def markSolved()(using Context): Unit = - isSolved = true - deps.foreach(_.propagateSolved()) + def markSolved(provisional: Boolean)(using Context): Unit = + solved = if provisional then ccState.iterationId else Int.MaxValue + deps.foreach(_.propagateSolved(provisional)) + + var skippedMaps: Set[TypeMap] = Set.empty def withDescription(description: String): this.type = this.description = this.description.join(" and ", description) @@ -689,11 +732,13 @@ object CaptureSet: * is not derived from some other variable. */ protected def ids(using Context): String = + def descr = getClass.getSimpleName.nn.take(1) val trail = this.match - case dv: DerivedVar => dv.source.ids - case _ => "" - val descr = getClass.getSimpleName.nn.take(1) - s"$id$descr$trail" + case dv: DerivedVar => + def summary = if ctx.settings.YccVerbose.value then dv.summarize else descr + s"$summary${dv.source.ids}" + case _ => descr + s"$id$trail" override def toString = s"Var$id$elems" end Var @@ -709,120 +754,42 @@ object CaptureSet: extends Var(owner, initialElems): // For debugging: A trace where a set was created. Note that logically it would make more - // sense to place this variable in Mapped, but that runs afoul of the initializatuon checker. - val stack = if debugSets && this.isInstanceOf[Mapped] then (new Throwable).getStackTrace().nn.take(20) else null + // sense to place this variable in BiMapped, but that runs afoul of the initializatuon checker. + // val stack = if debugSets && this.isInstanceOf[BiMapped] then (new Throwable).getStackTrace().nn.take(20) else null /** The variable from which this variable is derived */ def source: Var addAsDependentTo(source) - override def propagateSolved()(using Context) = - if source.isConst && !isConst then markSolved() - end DerivedVar - - /** A variable that changes when `source` changes, where all additional new elements are mapped - * using ∪ { tm(x) | x <- source.elems }. - * @param source the original set that is mapped - * @param tm the type map, which is assumed to be idempotent on capture refs - * (except if ccUnsoundMaps is enabled) - * @param variance the assumed variance with which types with capturesets of size >= 2 are approximated - * (i.e. co: full capture set, contra: empty set, nonvariant is not allowed.) - * @param initial The initial mappings of source's elements at the point the Mapped set is created. - */ - class Mapped private[CaptureSet] - (val source: Var, tm: TypeMap, variance: Int, initial: CaptureSet)(using @constructorOnly ctx: Context) - extends DerivedVar(source.owner, initial.elems): - addAsDependentTo(initial) // initial mappings could change by propagation + override def propagateSolved(provisional: Boolean)(using Context) = + if source.isConst && !isConst then markSolved(provisional) - private def mapIsIdempotent = tm.isInstanceOf[IdempotentCaptRefMap] + // ----------- Longest path recording ------------------------- - assert(ccConfig.allowUnsoundMaps || mapIsIdempotent, tm.getClass) + /** Summarize for set displaying in a path */ + def summarize: String = getClass.toString - private def whereCreated(using Context): String = - if stack == null then "" - else i""" - |Stack trace of variable creation:" - |${stack.mkString("\n")}""" + /** The length of the path of DerivedVars ending in this set */ + def pathLength: Int = source match + case source: DerivedVar => source.pathLength + 1 + case _ => 1 - 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 ccConfig.allowUnsoundMaps && !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 - CompareResult.OK - def propagateIf(cond: Boolean): CompareResult = - if cond then propagate else CompareResult.OK - - val mapped = extrapolateCaptureRef(elem, tm, variance) + /** The path of DerivedVars ending in this set */ + def path: List[DerivedVar] = source match + case source: DerivedVar => this :: source.path + case _ => this :: Nil - def isFixpoint = - mapped.isConst && mapped.elems.size == 1 && mapped.elems.contains(elem) + if ctx.settings.YccLog.value || util.Stats.enabled then + ctx.run.nn.recordPath(pathLength, path) - def failNoFixpoint = - val reason = - if variance <= 0 then i"the set's variance is $variance" - else i"$elem gets mapped to $mapped, which is not a supercapture." - report.warning(em"""trying to add $elem from unrecognized source $origin of mapped set $this$whereCreated - |The reference cannot be added since $reason""") - CompareResult.Fail(this :: Nil) - - if origin eq source then // elements have to be mapped - val added = mapped.elems.filter(!accountsFor(_)) - addNewElems(added) - .andAlso: - if mapped.isConst then CompareResult.OK - else if mapped.asVar.recordDepsState() then { addAsDependentTo(mapped); CompareResult.OK } - else CompareResult.Fail(this :: Nil) - .andAlso: - propagateIf(!added.isEmpty) - else if accountsFor(elem) then - CompareResult.OK - else if variance > 0 then - // we can soundly add nothing to source and `x` to this set - addNewElem(elem) - else if isFixpoint then - // We can soundly add `x` to both this set and source since `f(x) = x` - addNewElem(elem).andAlso(propagate) - else - // we are out of options; fail (which is always sound). - failNoFixpoint - end tryInclude - - override def computeApprox(origin: CaptureSet)(using Context): 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 - else - source.upperApprox(this).map(tm) - - override def propagateSolved()(using Context) = - if initial.isConst then super.propagateSolved() - - override def toString = s"Mapped$id($source, elems = $elems)" - end Mapped + end DerivedVar /** A mapping where the type map is required to be a bijection. * Parameters as in Mapped. */ final class BiMapped private[CaptureSet] - (val source: Var, bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) + (val source: Var, val bimap: BiTypeMap, initialElems: Refs)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, initialElems): override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = @@ -833,9 +800,13 @@ object CaptureSet: else if accountsFor(elem) then CompareResult.OK else - source.tryInclude(bimap.backward(elem), this) - .showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug) - .andAlso(addNewElem(elem)) + try + source.tryInclude(bimap.backward(elem), this) + .showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug) + .andAlso(addNewElem(elem)) + catch case ex: AssertionError => + println(i"fail while tryInclude $elem of ${elem.getClass} in $this / ${this.summarize}") + throw ex /** For a BiTypeMap, supertypes of the mapped type also constrain * the source via the inverse type mapping and vice versa. That is, if @@ -851,11 +822,12 @@ object CaptureSet: override def isMaybeSet: Boolean = bimap.isInstanceOf[MaybeMap] override def toString = s"BiMapped$id($source, elems = $elems)" + override def summarize = bimap.getClass.toString end BiMapped /** 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) + (val source: Var, val p: Context ?=> CaptureRef => Boolean)(using @constructorOnly ctx: Context) extends DerivedVar(source.owner, source.elems.filter(p)): override def tryInclude(elem: CaptureRef, origin: CaptureSet)(using Context, VarState): CompareResult = @@ -903,8 +875,8 @@ object CaptureSet: else res else res - override def propagateSolved()(using Context) = - if cs1.isConst && cs2.isConst && !isConst then markSolved() + override def propagateSolved(provisional: Boolean)(using Context) = + if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional) end Union class Intersection(cs1: CaptureSet, cs2: CaptureSet)(using Context) @@ -930,8 +902,8 @@ object CaptureSet: else CaptureSet(elemIntersection(cs1.upperApprox(this), cs2.upperApprox(this))) - override def propagateSolved()(using Context) = - if cs1.isConst && cs2.isConst && !isConst then markSolved() + override def propagateSolved(provisional: Boolean)(using Context) = + if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional) end Intersection def elemIntersection(cs1: CaptureSet, cs2: CaptureSet)(using Context): Refs = @@ -1068,12 +1040,6 @@ object CaptureSet: case _ => false - /** A TypeMap with the property that every capture reference in the image - * of the map is mapped to itself. I.e. for all capture references r1, r2, - * if M(r1) == r2 then M(r2) == r2. - */ - trait IdempotentCaptRefMap extends TypeMap - /** A TypeMap that is the identity on capture references */ trait IdentityCaptRefMap extends TypeMap @@ -1144,6 +1110,14 @@ object CaptureSet: /** A map from captureset variables to their dependent sets at the time of the snapshot. */ private val depsMap: util.EqHashMap[Var, Deps] = new util.EqHashMap + /** A map from root.Result values to other such values. If two result values + * `a` and `b` are unified, then `eqResultMap(a) = b` and `eqResultMap(b) = a`. + */ + private var eqResultMap: util.SimpleIdentityMap[root.Result, root.Result] = util.SimpleIdentityMap.empty + + /** A snapshot of the `eqResultMap` value at the start of a VarState transaction */ + private var eqResultSnapshot: util.SimpleIdentityMap[root.Result, root.Result] | Null = null + /** The recorded elements of `v` (it's required that a recording was made) */ def elems(v: Var): Refs = elemsMap(v) @@ -1183,10 +1157,31 @@ object CaptureSet: hidden.add(elem)(using ctx, this) true + /** If root1 and root2 belong to the same binder but have different originalBinders + * it means that one of the roots was mapped to the binder of the other by a + * substBinder when comparing two method types. In that case we can unify + * the two roots1, provided none of the two roots have already been unified + * themselves. So unification must be 1-1. + */ + def unify(root1: root.Result, root2: root.Result)(using Context): Boolean = + (root1, root2) match + case (root1 @ root.Result(binder1), root2 @ root.Result(binder2)) + if (binder1 eq binder2) + && (root1.rootAnnot.originalBinder ne root2.rootAnnot.originalBinder) + && eqResultMap(root1) == null + && eqResultMap(root2) == null + => + if eqResultSnapshot == null then eqResultSnapshot = eqResultMap + eqResultMap = eqResultMap.updated(root1, root2).updated(root2, root1) + true + case _ => + false + /** Roll back global state to what was recorded in this VarState */ def rollBack(): Unit = elemsMap.keysIterator.foreach(_.resetElems()(using this)) depsMap.keysIterator.foreach(_.resetDeps()(using this)) + if eqResultSnapshot != null then eqResultMap = eqResultSnapshot.nn private var seen: util.EqHashSet[CaptureRef] = new util.EqHashSet @@ -1225,37 +1220,36 @@ object CaptureSet: * reference `r` only if `r` is already present in the hidden set of the instance. * No new references can be added. */ - @sharable - object Separate extends Separating + def Separate(using Context): Separating = ccState.Separate /** Like Separate but in addition we assume that `cap` never subsumes anything else. * Used in `++` to not lose track of dependencies between function parameters. */ - @sharable - object HardSeparate extends Separating + def HardSeparate(using Context): Separating = ccState.HardSeparate /** A special state that turns off recording of elements. Used only - * in `addSub` to prevent cycles in recordings. + * in `addSub` to prevent cycles in recordings. Instantiated in ccState.Unrecorded. */ - @sharable - private[CaptureSet] object Unrecorded extends VarState: + class Unrecorded extends VarState: override def putElems(v: Var, refs: Refs) = true override def putDeps(v: Var, deps: Deps) = true override def rollBack(): Unit = () override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true override def toString = "unrecorded varState" + def Unrecorded(using Context): Unrecorded = ccState.Unrecorded + /** A closed state that turns off recording of hidden elements (but allows - * adding them). Used in `mightAccountFor`. + * adding them). Used in `mightAccountFor`. Instantiated in ccState.ClosedUnrecorded. */ - @sharable - private[CaptureSet] object ClosedUnrecorded extends Closed: + class ClosedUnrecorded extends Closed: override def addHidden(hidden: HiddenSet, elem: CaptureRef)(using Context): Boolean = true override def toString = "closed unrecorded varState" + def ClosedUnrecorded(using Context): ClosedUnrecorded = ccState.ClosedUnrecorded + end VarState - @sharable /** The current VarState, as passed by the implicit context */ def varState(using state: VarState): VarState = state @@ -1268,10 +1262,21 @@ object CaptureSet: case t: CaptureRef if t.isTrackableRef => mapRef(t) case _ => mapOver(t) - lazy val inverse = new BiTypeMap: + override def fuse(next: BiTypeMap)(using Context) = next match + case next: Inverse if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) + case next: NarrowingCapabilityMap if next.getClass == getClass => assert(false) + case _ => None + + class Inverse extends BiTypeMap: def apply(t: Type) = t // since f(c) <: c, this is the best inverse def inverse = NarrowingCapabilityMap.this override def toString = NarrowingCapabilityMap.this.toString ++ ".inverse" + override def fuse(next: BiTypeMap)(using Context) = next match + case next: NarrowingCapabilityMap if next.inverse.getClass == getClass => assert(false); Some(IdentityTypeMap) + case next: NarrowingCapabilityMap if next.getClass == getClass => assert(false) + case _ => None + + lazy val inverse = Inverse() end NarrowingCapabilityMap /** Maps `x` to `x?` */ @@ -1313,6 +1318,10 @@ object CaptureSet: .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) case ReadOnlyCapability(ref1) => ref1.captureSetOfInfo.map(ReadOnlyMap()) + case ref: ParamRef if !ref.underlying.exists => + // might happen during construction of lambdas, assume `{cap}` in this case so that + // `ref` will not seem subsumed by other capabilities in a `++`. + universal case _ => if ref.isRootCapability then ref.singletonCaptureSet else ofType(ref.underlying, followResult = false) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 4de1981553b6..a4551fa2b86c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import transform.{Recheck, PreRecheck, CapturedVars} import Recheck.* import scala.collection.mutable -import CaptureSet.{withCaptureSetsExplained, IdempotentCaptRefMap, CompareResult, CompareFailure, ExistentialSubsumesFailure} +import CaptureSet.{withCaptureSetsExplained, CompareResult, CompareFailure, ExistentialSubsumesFailure} import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} @@ -59,7 +59,7 @@ object CheckCaptures: def isOutermost = outer0 == null /** If an environment is open it tracks free references */ - def isOpen = !captured.isAlwaysEmpty && kind != EnvKind.Boxed + def isOpen(using Context) = !captured.isAlwaysEmpty && kind != EnvKind.Boxed def outersIterator: Iterator[Env] = new: private var cur = Env.this @@ -77,7 +77,7 @@ object CheckCaptures: * maps parameters in contravariant capture sets to the empty set. */ final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) - extends ApproximatingTypeMap, IdempotentCaptRefMap: + extends ApproximatingTypeMap: def apply(tp: Type): Type = tp match case tp: ParamRef => @@ -92,44 +92,6 @@ object CheckCaptures: override def toString = "SubstParamsMap" end SubstParamsMap - /** Used for substituting parameters in a special case: when all actual arguments - * are mutually distinct capabilities. - */ - final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context) - extends BiTypeMap: - thisMap => - - def apply(tp: Type): Type = tp match - case tp: ParamRef => - if tp.binder == from then to(tp.paramNum) else tp - case tp: NamedType => - if tp.prefix `eq` NoPrefix then tp - else tp.derivedSelect(apply(tp.prefix)) - case _: ThisType => - tp - case _ => - mapOver(tp) - override def toString = "SubstParamsBiMap" - - lazy val inverse = new BiTypeMap: - def apply(tp: Type): Type = tp match - case tp: NamedType => - var idx = 0 - var to1 = to - while idx < to.length && (tp ne to(idx)) do - idx += 1 - to1 = to1.tail - if idx < to.length then from.paramRefs(idx) - else if tp.prefix `eq` NoPrefix then tp - else tp.derivedSelect(apply(tp.prefix)) - case _: ThisType => - tp - case _ => - mapOver(tp) - override def toString = "SubstParamsBiMap.inverse" - def inverse = thisMap - end SubstParamsBiMap - /** A prototype that indicates selection with an immutable value */ class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto @@ -220,7 +182,7 @@ object CheckCaptures: trait CheckerAPI: /** Complete symbol info of a val or a def */ - def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type + def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type extension [T <: Tree](tree: T) @@ -271,6 +233,8 @@ class CheckCaptures extends Recheck, SymTransformer: class CaptureChecker(ictx: Context) extends Rechecker(ictx), CheckerAPI: + // println(i"checking ${ictx.source}"(using ictx)) + /** The current environment */ private val rootEnv: Env = inContext(ictx): Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) @@ -292,8 +256,20 @@ class CheckCaptures extends Recheck, SymTransformer: */ private val sepCheckFormals = util.EqHashMap[Tree, Type]() + /** The references used at identifier or application trees */ private val usedSet = util.EqHashMap[Tree, CaptureSet]() + /** The set of symbols that were rechecked via a completer */ + private val completed = new mutable.HashSet[Symbol] + + var needAnotherRun = false + + def resetIteration()(using Context): Unit = + needAnotherRun = false + resetNuTypes() + todoAtPostCheck.clear() + completed.clear() + extension [T <: Tree](tree: T) def needsSepCheck: Boolean = sepCheckFormals.contains(tree) def formalType: Type = sepCheckFormals.getOrElse(tree, NoType) @@ -302,18 +278,42 @@ class CheckCaptures extends Recheck, SymTransformer: /** Instantiate capture set variables appearing contra-variantly to their * upper approximation. */ - private def interpolator(startingVariance: Int = 1)(using Context) = new TypeTraverser: - variance = startingVariance - override def traverse(t: Type) = t match - case t @ CapturingType(parent, refs) => - refs match - case refs: CaptureSet.Var if variance < 0 => refs.solve() - case _ => - traverse(parent) - case t @ defn.RefinedFunctionOf(rinfo) => - traverse(rinfo) - case _ => - traverseChildren(t) + private def interpolate(tp: Type, sym: Symbol, startingVariance: Int = 1)(using Context): Unit = + + object variances extends TypeTraverser: + variance = startingVariance + val varianceOfVar = EqHashMap[CaptureSet.Var, Int]() + override def traverse(t: Type) = t match + case t @ CapturingType(parent, refs) => + refs match + case refs: CaptureSet.Var if !refs.isConst => + varianceOfVar(refs) = varianceOfVar.get(refs) match + case Some(v0) => if v0 == 0 then 0 else (v0 + variance) / 2 + case None => variance + case _ => + traverse(parent) + case t @ defn.RefinedFunctionOf(rinfo) => + traverse(rinfo) + case _ => + traverseChildren(t) + + val interpolator = new TypeTraverser: + override def traverse(t: Type) = t match + case t @ CapturingType(parent, refs) => + refs match + case refs: CaptureSet.Var if !refs.isConst => + if variances.varianceOfVar(refs) < 0 then refs.solve() + else refs.markSolved(provisional = !sym.isMutableVar) + case _ => + traverse(parent) + case t @ defn.RefinedFunctionOf(rinfo) => + traverse(rinfo) + case _ => + traverseChildren(t) + + variances.traverse(tp) + interpolator.traverse(tp) + end interpolate /* Also set any previously unset owners of toplevel Fresh instances to improve * error diagnostics in separation checking. @@ -337,29 +337,44 @@ class CheckCaptures extends Recheck, SymTransformer: /** If `tpt` is an inferred type, interpolate capture set variables appearing contra- * variantly in it. Also anchor Fresh instances with anchorCaps. */ - private def interpolateVarsIn(tpt: Tree, sym: Symbol)(using Context): Unit = + private def interpolateIfInferred(tpt: Tree, sym: Symbol)(using Context): Unit = if tpt.isInstanceOf[InferredTypeTree] then - interpolator().traverse(tpt.nuType) - .showing(i"solved vars in ${tpt.nuType}", capt) + interpolate(tpt.nuType, sym) + .showing(i"solved vars for $sym in ${tpt.nuType}", capt) anchorCaps(sym).traverse(tpt.nuType) - for msg <- ccState.approxWarnings do - report.warning(msg, tpt.srcPos) - ccState.approxWarnings.clear() + for msg <- ccState.approxWarnings do + report.warning(msg, tpt.srcPos) + ccState.approxWarnings.clear() /** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2") /** If `res` is not CompareResult.OK, report an error */ - def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = + def checkOK(res: CompareResult, prefix: => String, added: CaptureRef | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit = res match case res: CompareFailure => - inContext(root.printContext(added, res.blocking)): + def msg(provisional: Boolean) = def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString def descr: String = val d = res.blocking.description if d.isEmpty then provenance else "" - report.error(em"$prefix included in the allowed capture set ${res.blocking}$descr$toAdd", pos) + def kind = if provisional then "previously estimated\n" else "allowed " + em"$prefix included in the ${kind}capture set ${res.blocking}$descr$toAdd" + target match + case target: CaptureSet.Var + if res.blocking.isProvisionallySolved => + report.warning( + msg(provisional = true) + .prepend(i"Another capture checking run needs to be scheduled because\n"), + pos) + needAnotherRun = true + added match + case added: CaptureRef => target.elems += added + case added: CaptureSet => target.elems ++= added.elems + case _ => + inContext(root.printContext(added, res.blocking)): + report.error(msg(provisional = false), pos) case _ => /** Check subcapturing `{elem} <: cs`, report error on failure */ @@ -367,7 +382,7 @@ class CheckCaptures extends Recheck, SymTransformer: checkOK( ccState.test(elem.singletonCaptureSet.subCaptures(cs)), i"$elem cannot be referenced here; it is not", - elem, pos, provenance) + elem, cs, pos, provenance) /** Check subcapturing `cs1 <: cs2`, report error on failure */ def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos, @@ -376,7 +391,7 @@ class CheckCaptures extends Recheck, SymTransformer: ccState.test(cs1.subCaptures(cs2)), if cs1.elems.size == 1 then i"reference ${cs1.elems.toList.head}$cs1description is not" else i"references $cs1$cs1description are not all", - cs1, pos, provenance) + cs1, cs2, pos, provenance) /** 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`. @@ -384,7 +399,7 @@ class CheckCaptures extends Recheck, SymTransformer: def capturedVars(sym: Symbol)(using Context): CaptureSet = myCapturedVars.getOrElseUpdate(sym, if sym.ownersIterator.exists(_.isTerm) - then CaptureSet.Var(sym.owner, level = sym.ccLevel) + then CaptureSet.Var(sym.owner, level = ccState.symLevel(sym)) else CaptureSet.empty) // ---- Record Uses with MarkFree ---------------------------------------------------- @@ -412,7 +427,7 @@ class CheckCaptures extends Recheck, SymTransformer: i"\nof the enclosing ${owner.showLocated}" /** Does the given environment belong to a method that is (a) nested in a term - * and (b) not the method of an anonympus function? + * and (b) not the method of an anonymous function? */ def isOfNestedMethod(env: Env | Null)(using Context) = env != null @@ -718,7 +733,7 @@ class CheckCaptures extends Recheck, SymTransformer: protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type = val freshenedFormal = root.capToFresh(formal) val argType = recheck(arg, freshenedFormal) - .showing(i"recheck arg $arg vs $freshenedFormal", capt) + .showing(i"recheck arg $arg vs $freshenedFormal = $result", capt) if formal.hasAnnotation(defn.UseAnnot) || formal.hasAnnotation(defn.ConsumeAnnot) then // The @use and/or @consume annotation is added to `formal` by `prepareFunction` capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") @@ -775,19 +790,11 @@ class CheckCaptures extends Recheck, SymTransformer: * This means * - Instantiate result type with actual arguments * - if `sym` is a constructor, refine its type with `refineInstanceType` - * 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. */ override def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = val ownType = - if !mt.isResultDependent then - mt.resType - else if argTypes.forall(_.isTrackableRef) && isDistinct(argTypes) then - SubstParamsBiMap(mt, argTypes)(mt.resType) - else - SubstParamsMap(mt, argTypes)(mt.resType) + if !mt.isResultDependent then mt.resType + else SubstParamsMap(mt, argTypes)(mt.resType) if sym.isConstructor then refineConstructorInstance(ownType, mt, argTypes, sym) else ownType @@ -881,7 +888,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => override def recheckBlock(tree: Block, pt: Type)(using Context): Type = - inNestedLevel(super.recheckBlock(tree, pt)) + ccState.inNestedLevel(super.recheckBlock(tree, pt)) /** Recheck Closure node: add the captured vars of the anonymoys function * to the result type. See also `recheckClosureBlock` which rechecks the @@ -897,28 +904,53 @@ class CheckCaptures extends Recheck, SymTransformer: * { def $anonfun(...) = ...; closure($anonfun, ...)} */ override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = + + def matchParams(paramss: List[ParamClause], pt: Type): Unit = + //println(i"match $mdef against $pt") + paramss match + case params :: paramss1 => pt match + case defn.PolyFunctionOf(poly: PolyType) => + assert(params.hasSameLengthAs(poly.paramInfos)) + matchParams(paramss1, poly.instantiate(params.map(_.symbol.typeRef))) + case FunctionOrMethod(argTypes, resType) => + assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}") + for (argType, param) <- argTypes.lazyZip(params) do + val paramTpt = param.asInstanceOf[ValDef].tpt + val paramType = root.freshToCap(paramTpt.nuType) + checkConformsExpr(argType, paramType, param) + .showing(i"compared expected closure formal $argType against $param with ${paramTpt.nuType}", capt) + if ccConfig.preTypeClosureResults && !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then + // Check whether the closure's result conforms to the expected type + // This constrains parameter types of the closure which can give better + // error messages. + // But if the closure is an eta expanded method reference it's better to not constrain + // its internals early since that would give error messages in generated code + // which are less intelligible. An example is the line `a = x` in + // neg-custom-args/captures/vars.scala. That's why this code is conditioned. + // to apply only to closures that are not eta expansions. + assert(paramss1.isEmpty) + val respt = root.resultToFresh: + pt match + case defn.RefinedFunctionOf(rinfo) => + val paramTypes = params.map(_.asInstanceOf[ValDef].tpt.nuType) + rinfo.instantiate(paramTypes) + case _ => + resType + val res = root.resultToFresh(mdef.tpt.nuType) + // We need to open existentials here in order not to get vars mixed up in them + // We do the proper check with existentials when we are finished with the closure block. + capt.println(i"pre-check closure $expr of type $res against $respt") + checkConformsExpr(res, respt, expr) + case _ => + case Nil => + openClosures = (mdef.symbol, pt) :: openClosures + // openClosures is needed for errors but currently makes no difference + // TODO follow up on this try - // Constrain closure's parameters and result from the expected type before - // rechecking the body. - val res = recheckClosure(expr, pt, forceDependent = true) - if !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then - // Check whether the closure's results conforms to the expected type - // This constrains parameter types of the closure which can give better - // error messages. - // But if the closure is an eta expanded method reference it's better to not constrain - // its internals early since that would give error messages in generated code - // which are less intelligible. An example is the line `a = x` in - // neg-custom-args/captures/vars.scala. That's why this code is conditioned. - // to apply only to closures that are not eta expansions. - val res1 = root.resultToFresh(res) // TODO: why deep = true? - val pt1 = root.resultToFresh(pt) - // We need to open existentials here in order not to get vars mixed up in them - // We do the proper check with existentials when we are finished with the closure block. - capt.println(i"pre-check closure $expr of type $res1 against $pt1") - checkConformsExpr(res1, pt1, expr) + matchParams(mdef.paramss, pt) recheckDef(mdef, mdef.symbol) - res + recheckClosure(expr, pt, forceDependent = true) finally openClosures = openClosures.tail end recheckClosureBlock @@ -961,7 +993,7 @@ class CheckCaptures extends Recheck, SymTransformer: // for more info from the context, so we cannot interpolate. Note that we cannot // expect to have all necessary info available at the point where the anonymous // function is compiled since we do not propagate expected types into blocks. - interpolateVarsIn(tree.tpt, sym) + interpolateIfInferred(tree.tpt, sym) /** Recheck method definitions: * - check body in a nested environment that tracks uses, in a nested level, @@ -989,7 +1021,7 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv val localSet = capturedVars(sym) - if !localSet.isAlwaysEmpty then + if localSet ne CaptureSet.empty then curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure(tree.rhs)) // ctx with AssumedContains entries for each Contains parameter @@ -1001,13 +1033,13 @@ class CheckCaptures extends Recheck, SymTransformer: if ac.isEmpty then ctx else ctx.withProperty(CaptureSet.AssumedContains, Some(ac)) - inNestedLevel: // TODO: nestedLevel needed here? + ccState.inNestedLevel: // TODO: nestedLevel needed here? try checkInferredResult(super.recheckDefDef(tree, sym)(using bodyCtx), tree) finally if !sym.isAnonymousFunction then // Anonymous functions propagate their type to the enclosing environment // so it is not in general sound to interpolate their types. - interpolateVarsIn(tree.tpt, sym) + interpolateIfInferred(tree.tpt, sym) curEnv = saved end recheckDefDef @@ -1051,9 +1083,6 @@ class CheckCaptures extends Recheck, SymTransformer: tp end checkInferredResult - /** The set of symbols that were rechecked via a completer */ - private val completed = new mutable.HashSet[Symbol] - /** The normal rechecking if `sym` was already completed before */ override def skipRecheck(sym: Symbol)(using Context): Boolean = completed.contains(sym) @@ -1062,7 +1091,7 @@ class CheckCaptures extends Recheck, SymTransformer: * these checks can appear out of order, we need to first create the correct * environment for checking the definition. */ - def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type = val saved = curEnv try // Setup environment to reflect the new owner. @@ -1072,7 +1101,7 @@ class CheckCaptures extends Recheck, SymTransformer: .toMap def restoreEnvFor(sym: Symbol): Env = val localSet = capturedVars(sym) - if localSet.isAlwaysEmpty then rootEnv + if localSet eq CaptureSet.empty then rootEnv else envForOwner.get(sym) match case Some(e) => e case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) @@ -1099,7 +1128,7 @@ class CheckCaptures extends Recheck, SymTransformer: checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos, i"\nof the references allowed to be captured by $cls") val saved = curEnv - if !localSet.isAlwaysEmpty then + if localSet ne CaptureSet.empty then curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try val thisSet = cls.classInfo.selfType.captureSet.withDescription(i"of the self type of $cls") @@ -1124,7 +1153,7 @@ class CheckCaptures extends Recheck, SymTransformer: case AppliedType(fn, args) => disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) case _ => - inNestedLevelUnless(cls.is(Module)): + ccState.inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) finally curEnv = saved @@ -1182,7 +1211,7 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => - curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) + curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), curEnv) case _ => val res = try @@ -1229,8 +1258,11 @@ class CheckCaptures extends Recheck, SymTransformer: i"""reference ${ref}$levelStr |cannot be included in outer capture set $cs""" case ExistentialSubsumesFailure(ex, other) => + def since = + if other.isRootCapability then "" + else " since that capability is not a SharedCapability" i"""the existential capture root in ${ex.rootAnnot.originalBinder.resType} - |cannot subsume the capability $other""" + |cannot subsume the capability $other$since""" i""" | |Note that ${msg.toString}""" @@ -1341,7 +1373,7 @@ class CheckCaptures extends Recheck, SymTransformer: else if !owner.exists then false else isPure(owner.info) && isPureContext(owner.owner, limit) - // Augment expeced capture set `erefs` by all references in actual capture + // Augment expected capture set `erefs` by all references in actual capture // set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing // class `C`. If an added reference is not a ThisType itself, add it to the capture set // (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed @@ -1409,7 +1441,7 @@ class CheckCaptures extends Recheck, SymTransformer: val saved = curEnv curEnv = Env( curEnv.owner, EnvKind.NestedInOwner, - CaptureSet.Var(curEnv.owner, level = currentLevel), + CaptureSet.Var(curEnv.owner, level = ccState.currentLevel), if boxed then null else curEnv) try val (eargs, eres) = expected.dealias.stripCapturing match @@ -1571,33 +1603,62 @@ class CheckCaptures extends Recheck, SymTransformer: */ def checkOverrides = new TreeTraverser: class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, tree: Tree)(using Context) extends OverridingPairsChecker(clazz, self): - /** Check subtype with box adaptation. - * This function is passed to RefChecks to check the compatibility of overriding pairs. - * @param sym symbol of the field definition that is being checked - */ - override def checkSubType(actual: Type, expected: Type)(using Context): Boolean = - val expected1 = alignDependentFunction(addOuterRefs(expected, actual, tree.srcPos), actual.stripCapturing) - val actual1 = - val saved = curEnv - try - curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) - val adapted = - adaptBoxed(actual, expected1, tree, covariant = true, alwaysConst = true) - actual match - case _: MethodType => - // We remove the capture set resulted from box adaptation for method types, - // since class methods are always treated as pure, and their captured variables - // are charged to the capture set of the class (which is already done during - // box adaptation). - adapted.stripCapturing - case _ => adapted - finally curEnv = saved - actual1 frozen_<:< expected1 /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) + /** Perform box adaptation for override checking */ + override def adaptOverridePair(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = + if member.isType then + memberTp match + case TypeAlias(_) => + otherTp match + case otherTp: RealTypeBounds => + if otherTp.hi.isBoxedCapturing || otherTp.lo.isBoxedCapturing then + Some((memberTp, otherTp.unboxed)) + else otherTp.hi match + case hi @ CapturingType(parent: TypeRef, refs) + if parent.symbol == defn.Caps_CapSet && refs.isUniversal => + Some(( + memberTp, + otherTp.derivedTypeBounds( + otherTp.lo, + hi.derivedCapturingType(parent, root.Fresh().singletonCaptureSet)))) + case _ => None + case _ => None + case _ => None + else memberTp match + case memberTp @ ExprType(memberRes) => + adaptOverridePair(member, memberRes, otherTp) match + case Some((mres, otp)) => Some((memberTp.derivedExprType(mres), otp)) + case None => None + case _ => otherTp match + case otherTp @ ExprType(otherRes) => + adaptOverridePair(member, memberTp, otherRes) match + case Some((mtp, ores)) => Some((mtp, otherTp.derivedExprType(ores))) + case None => None + case _ => + val expected1 = alignDependentFunction(addOuterRefs(otherTp, memberTp, tree.srcPos), memberTp.stripCapturing) + val actual1 = + val saved = curEnv + try + curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) + val adapted = + adaptBoxed(memberTp, expected1, tree, covariant = true, alwaysConst = true) + memberTp match + case _: MethodType => + // We remove the capture set resulted from box adaptation for method types, + // since class methods are always treated as pure, and their captured variables + // are charged to the capture set of the class (which is already done during + // box adaptation). + adapted.stripCapturing + case _ => adapted + finally curEnv = saved + if (actual1 eq memberTp) && (expected1 eq otherTp) then None + else Some((actual1, expected1)) + end adaptOverridePair + override def checkInheritedTraitParameters: Boolean = false /** Check that overrides don't change the @use or @consume status of their parameters */ @@ -1675,7 +1736,19 @@ class CheckCaptures extends Recheck, SymTransformer: report.echo(s"$echoHeader\n$treeString\n") withCaptureSetsExplained: - super.checkUnit(unit) + def iterate(): Unit = + super.checkUnit(unit) + if !ctx.reporter.errorsReported + && (needAnotherRun + || ccConfig.alwaysRepeatRun && ccState.iterationId == 1) + then + resetIteration() + ccState.nextIteration: + setup.setupUnit(unit.tpdTree, this) + capt.println(s"**** capture checking run ${ccState.iterationId} started on ${ctx.source}") + iterate() + + iterate() checkOverrides.traverse(unit.tpdTree) postCheck(unit.tpdTree) checkSelfTypes(unit.tpdTree) @@ -1718,7 +1791,7 @@ class CheckCaptures extends Recheck, SymTransformer: inContext(ctx.fresh.setOwner(root)): checkSelfAgainstParents(root, root.baseClasses) val selfType = root.asClass.classInfo.selfType - interpolator(startingVariance = -1).traverse(selfType) + interpolate(selfType, root, startingVariance = -1) selfType match case CapturingType(_, refs: CaptureSet.Var) if !root.isEffectivelySealed @@ -1737,65 +1810,26 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"checked $root with $selfType") end checkSelfTypes - /** Heal ill-formed capture sets in the type parameter. - * - * We can push parameter refs into a capture set in type parameters - * that this type parameter can't see. - * For example, when capture checking the following expression: - * - * def usingLogFile[T](op: File^ => T): T = ... - * - * usingLogFile[box ?1 () -> Unit] { (f: File^) => () => { f.write(0) } } - * - * We may propagate `f` into ?1, making ?1 ill-formed. - * This also causes soundness issues, since `f` in ?1 should be widened to `cap`, - * giving rise to an error that `cap` cannot be included in a boxed capture set. - * - * To solve this, we still allow ?1 to capture parameter refs like `f`, but - * compensate this by pushing the widened capture set of `f` into ?1. - * This solves the soundness issue caused by the ill-formness of ?1. + /** Check ill-formed capture sets in a type parameter. We used to be able to + * push parameter refs into a capture set in type parameters that this type + * parameter can't see. We used to heal this by replacing illegal refs by their + * underlying capture sets. But now these should no longer be necessary, so + * instead of errors we use assertions. */ - private def healTypeParam(tree: Tree, paramName: TypeName, meth: Symbol)(using Context): Unit = + private def checkTypeParam(tree: Tree, paramName: TypeName, meth: Symbol)(using Context): Unit = val checker = new TypeTraverser: private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty - private def isAllowed(ref: CaptureRef): Boolean = ref match - case ref: TermParamRef => allowed.contains(ref) - case _ => true - - private def healCaptureSet(cs: CaptureSet): Unit = - cs.ensureWellformed: elem => - ctx ?=> - var seen = new util.HashSet[CaptureRef] - def recur(ref: CaptureRef): Unit = ref.stripReach match - case ref: TermParamRef - if !allowed.contains(ref) && !seen.contains(ref) => - seen += ref - if ref.isRootCapability then - report.error(i"escaping local reference $ref", tree.srcPos) - else - val widened = ref.captureSetOfInfo - val added = widened.filter(isAllowed(_)) - capt.println(i"heal $ref in $cs by widening to $added") - if !added.subCaptures(cs).isOK then - val location = if meth.exists then i" of ${meth.showLocated}" else "" - val paramInfo = - if ref.paramName.info.kind.isInstanceOf[UniqueNameKind] - then i"${ref.paramName} from ${ref.binder}" - else i"${ref.paramName}" - val debugSetInfo = if ctx.settings.YccDebug.value then i" $cs" else "" - report.error( - i"local reference $paramInfo leaks into outer capture set$debugSetInfo of type parameter $paramName$location", - tree.srcPos) - else - widened.elems.foreach(recur) - case _ => - recur(elem) + private def checkCaptureSet(cs: CaptureSet): Unit = + for elem <- cs.elems do + elem.stripReach match + case ref: TermParamRef => assert(allowed.contains(ref)) + case _ => def traverse(tp: Type) = tp match case CapturingType(parent, refs) => - healCaptureSet(refs) + checkCaptureSet(refs) traverse(parent) case defn.RefinedFunctionOf(rinfo: MethodType) => traverse(rinfo) @@ -1810,7 +1844,7 @@ class CheckCaptures extends Recheck, SymTransformer: if tree.isInstanceOf[InferredTypeTree] then checker.traverse(tree.nuType) - end healTypeParam + end checkTypeParam /** Under the unsealed policy: Arrays are like vars, check that their element types * do not contains `cap` (in fact it would work also to check on array creation @@ -1834,9 +1868,7 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(t) check.traverse(tp) - /** Perform the following kinds of checks - * - Check that arguments of TypeApplys and AppliedTypes conform to their bounds. - * - Heal ill-formed capture sets of type parameters. See `healTypeParam`. + /** Check that arguments of TypeApplys and AppliedTypes conform to their bounds. */ def postCheck(unit: tpd.Tree)(using Context): Unit = val checker = new TreeTraverser: @@ -1856,7 +1888,8 @@ class CheckCaptures extends Recheck, SymTransformer: bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing)) CCState.withCapAsRoot: // OK? We need this since bounds use `cap` instead of `fresh` checkBounds(normArgs, tl) - args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) + if ccConfig.postCheckCapturesets then + args.lazyZip(tl.paramNames).foreach(checkTypeParam(_, _, fun.symbol)) case _ => case _ => end check @@ -1872,7 +1905,9 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case tree: InferredTypeTree => case tree: New => - case tree: TypeTree => checkAppliedTypesIn(tree.withType(tree.nuType)) + case tree: TypeTree => + CCState.withCapAsRoot: + checkAppliedTypesIn(tree.withType(tree.nuType)) case _ => traverseChildren(t) checkApplied.traverse(unit) end postCheck diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index c8ab2ccbe81a..6434db0638a3 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -477,6 +477,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: | actualCaptures = ${args.map(arg => CaptureSet(captures(arg)))}, | deps = ${deps.toList}""") val parts = qual :: args + var reported: SimpleIdentitySet[Tree] = SimpleIdentitySet.empty for arg <- args do val argPeaks = PeaksPair( @@ -495,7 +496,9 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: // 1. test argPeaks.actual against previously captured hidden sets if !argPeaks.actual.sharedWith(currentPeaks.hidden).isEmpty then val clashing = clashingPart(argPeaks.actual, _.hidden) - if !clashing.isEmpty then sepApplyError(fn, parts, clashing, arg) + if !clashing.isEmpty then + sepApplyError(fn, parts, clashing, arg) + reported += clashing else assert(!argDeps.isEmpty) if arg.needsSepCheck then @@ -505,9 +508,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: if !argPeaks.hidden.sharedWith(currentPeaks.actual).isEmpty then val clashing = clashingPart(argPeaks.hidden, _.actual) if !clashing.isEmpty then - if !clashing.needsSepCheck then - // if clashing needs a separation check then we already got an erro - // in (1) at position of clashing. No need to report it twice. + if !reported.contains(clashing) then //println(i"CLASH $arg / ${argPeaks.formal} vs $clashing / ${peaksOfTree(clashing).actual} / ${captures(clashing).peaks}") sepApplyError(fn, parts, arg, clashing) else assert(!argDeps.isEmpty) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 6d52ad94613b..07ff3b841283 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -11,7 +11,6 @@ import config.Feature import config.Printers.{capt, captDebug} import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* -import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded import util.SimpleIdentitySet import util.chaining.* @@ -21,6 +20,7 @@ import collection.mutable import CCState.* import dotty.tools.dotc.util.NoSourcePosition import CheckCaptures.CheckerAPI +import NamerOps.methodType /** Operations accessed from CheckCaptures */ trait SetupAPI: @@ -302,9 +302,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: apply(parent) case tp: TypeLambda => // Don't recurse into parameter bounds, just cleanup any stray retains annotations - tp.derivedLambdaType( - paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), - resType = this(tp.resType)) + ccState.withoutMappedFutureElems: + tp.derivedLambdaType( + paramInfos = tp.paramInfos.mapConserve(_.dropAllRetains.bounds), + resType = this(tp.resType)) case _ => mapFollowingAliases(tp) addVar( @@ -336,6 +337,20 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def fail(msg: Message) = if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos) + /** If C derives from Capability and we have a C^cs in source, we leave it as is + * instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated + * universal capture sets from the parent of a CapturingType. + */ + def stripImpliedCaptureSet(tp: Type): Type = tp match + case tp @ CapturingType(parent, refs) + if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => + parent + case tp: AliasingBounds => + tp.derivedAlias(stripImpliedCaptureSet(tp.alias)) + case tp: RealTypeBounds => + tp.derivedTypeBounds(stripImpliedCaptureSet(tp.lo), stripImpliedCaptureSet(tp.hi)) + case _ => tp + object toCapturing extends DeepTypeMap, SetupTypeMap: override def toString = "transformExplicitType" @@ -367,16 +382,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(fntpe, cs, boxed = false) else fntpe - /** If C derives from Capability and we have a C^cs in source, we leave it as is - * instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated - * universal capture sets from the parent of a CapturingType. - */ - def stripImpliedCaptureSet(tp: Type): Type = tp match - case tp @ CapturingType(parent, refs) - if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => - parent - case _ => tp - /** Check that types extending SharedCapability don't have a `cap` in their capture set. * TODO This is not enough. * We need to also track that we cannot get exclusive capabilities in paths @@ -456,48 +461,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: toCapturing.keepFunAliases = false transform(tp1) else tp1 - if freshen then root.capToFresh(tp2).tap(addOwnerAsHidden(_, sym)) - else tp2 + val tp3 = + if sym.isType then stripImpliedCaptureSet(tp2) + else tp2 + if freshen then root.capToFresh(tp3).tap(addOwnerAsHidden(_, sym)) + else tp3 end transformExplicitType - /** Substitute parameter symbols in `from` to paramRefs in corresponding - * method or poly types `to`. We use a single BiTypeMap to do everything. - * @param from a list of lists of type or term parameter symbols of a curried method - * @param to a list of method or poly types corresponding one-to-one to the parameter lists - */ - private class SubstParams(from: List[List[Symbol]], to: List[LambdaType])(using Context) - extends DeepTypeMap, BiTypeMap: - - def apply(t: Type): Type = t match - case t: NamedType => - if t.prefix == NoPrefix then - val sym = t.symbol - def outer(froms: List[List[Symbol]], tos: List[LambdaType]): Type = - def inner(from: List[Symbol], to: List[ParamRef]): Type = - if from.isEmpty then outer(froms.tail, tos.tail) - else if sym eq from.head then to.head - else inner(from.tail, to.tail) - if tos.isEmpty then t - else inner(froms.head, tos.head.paramRefs) - outer(from, to) - else t.derivedSelect(apply(t.prefix)) - case _ => - mapOver(t) - - lazy val inverse = new BiTypeMap: - override def toString = "SubstParams.inverse" - def apply(t: Type): Type = t match - case t: ParamRef => - def recur(from: List[LambdaType], to: List[List[Symbol]]): Type = - if from.isEmpty then t - else if t.binder eq from.head then to.head(t.paramNum).namedType - else recur(from.tail, to.tail) - recur(to, from) - case _ => - mapOver(t) - def inverse = SubstParams.this - end SubstParams - /** Update info of `sym` for CheckCaptures phase only */ private def updateInfo(sym: Symbol, info: Type)(using Context) = toBeUpdated += sym @@ -528,8 +498,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def setupTraverser(checker: CheckerAPI) = new TreeTraverserWithPreciseImportContexts: import checker.* - private val paramSigChange = util.EqHashSet[Tree]() - /** Transform type of tree, and remember the transformed type as the type of the tree * @pre !(boxed && sym.exists) */ @@ -540,8 +508,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: then transformInferredType(tree.tpe) else transformExplicitType(tree.tpe, sym, freshen = !boxed, tptToCheck = tree) if boxed then transformed = box(transformed) - if sym.is(Param) && (transformed ne tree.tpe) then - paramSigChange += tree tree.setNuType( if sym.hasAnnotation(defn.UncheckedCapturesAnnot) then makeUnchecked(transformed) else transformed) @@ -575,8 +541,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if isExcluded(meth) then return - meth.recordLevel() - inNestedLevel: + ccState.recordLevel(meth) + ccState.inNestedLevel: inContext(ctx.withOwner(meth)): paramss.foreach(traverse) transformResultType(tpt, meth) @@ -584,7 +550,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree @ ValDef(_, tpt: TypeTree, _) => val sym = tree.symbol - sym.recordLevel() + ccState.recordLevel(sym) val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) @@ -600,8 +566,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: TypeDef if tree.symbol.isClass => val sym = tree.symbol - sym.recordLevel() - inNestedLevelUnless(sym.is(Module)): + ccState.recordLevel(sym) + ccState.inNestedLevelUnless(sym.is(Module)): inContext(ctx.withOwner(sym)) traverseChildren(tree) @@ -613,7 +579,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tpt.setNuType(box(transformInferredType(tpt.tpe))) case tree: Block => - inNestedLevel(traverseChildren(tree)) + ccState.inNestedLevel(traverseChildren(tree)) case _ => traverseChildren(tree) @@ -646,84 +612,57 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else tree.tpt.nuType // A test whether parameter signature might change. This returns true if one of - // the parameters has a new type installee. The idea here is that we store a new + // the parameters has a new type installed. The idea here is that we store a new // type only if the transformed type is different from the original. def paramSignatureChanges = tree.match case tree: DefDef => tree.paramss.nestedExists: - case param: ValDef => paramSigChange.contains(param.tpt) - case param: TypeDef => paramSigChange.contains(param.rhs) + case param: ValDef => param.tpt.hasNuType + case param: TypeDef => param.rhs.hasNuType case _ => false // A symbol's signature changes if some of its parameter types or its result type // have a new type installed here (meaning hasRememberedType is true) def signatureChanges = - tree.tpt.hasNuType && !sym.isConstructor || paramSignatureChanges - - // Replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs are put in correspondence by BiTypeMaps with the - // capture sets of the types of the method's parameter symbols and result type. - def integrateRT( - info: Type, // symbol info to replace - psymss: List[List[Symbol]], // the local (type and term) parameter symbols corresponding to `info` - resType: Type, // the locally computed return type - prevPsymss: List[List[Symbol]], // the local parameter symbols seen previously in reverse order - prevLambdas: List[LambdaType] // the outer method and polytypes generated previously in reverse order - ): Type = - info match - case mt: MethodOrPoly => - val psyms = psymss.head - // TODO: the substitution does not work for param-dependent method types. - // For example, `(x: T, y: x.f.type) => Unit`. In this case, when we - // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method - // type is still under initialization and `paramInfos` is still `null`, - // so the new `NamedType` will not have a denotation. - def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match - case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] - case _ => info - 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 => adaptedInfo(psym, subst(root.freshToCap(psym.nextInfo)).asInstanceOf[mt.PInfo])), - mt1 => - integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) - ) - case info: ExprType => - info.derivedExprType(resType = - integrateRT(info.resType, psymss, resType, prevPsymss, prevLambdas)) - case info => - if prevLambdas.isEmpty then resType - else SubstParams(prevPsymss, prevLambdas)(resType) + tree.tpt.hasNuType || paramSignatureChanges + + def paramsToCap(mt: Type)(using Context): Type = mt match + case mt: MethodType => + mt.derivedLambdaType( + paramInfos = mt.paramInfos.map(root.freshToCap), + resType = paramsToCap(mt.resType)) + case mt: PolyType => + mt.derivedLambdaType(resType = paramsToCap(mt.resType)) + case _ => mt // If there's a change in the signature, update the info of `sym` if sym.exists && signatureChanges then - val newInfo = - root.toResultInResults(report.error(_, tree.srcPos)): - integrateRT(sym.info, sym.paramSymss, localReturnType, Nil, Nil) - .showing(i"update info $sym: ${sym.info} = $result", capt) - if newInfo ne sym.info then - val updatedInfo = - if sym.isAnonymousFunction - || sym.is(Param) - || sym.is(ParamAccessor) - || sym.isPrimaryConstructor - then - // closures are handled specially; the newInfo is constrained from - // the expected type and only afterwards we recheck the definition - newInfo - else new LazyType: - // infos of other methods are determined from their definitions, which - // are checked on demand + val updatedInfo = + + val paramSymss = sym.paramSymss + def newInfo(using Context) = // will be run in this or next phase + root.toResultInResults(report.error(_, tree.srcPos)): + if sym.is(Method) then + paramsToCap(methodType(paramSymss, localReturnType)) + else tree.tpt.nuType + if tree.tpt.isInstanceOf[InferredTypeTree] + && !sym.is(Param) && !sym.is(ParamAccessor) + then + val prevInfo = sym.info + new LazyType: def complete(denot: SymDenotation)(using Context) = assert(ctx.phase == thisPhase.next, i"$sym") - capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") - //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() - denot.info = newInfo - completeDef(tree, sym) - updateInfo(sym, updatedInfo) + sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck + completeDef(tree, sym, this) + sym.info = newInfo + .showing(i"new info of $sym = $result", capt) + else if sym.is(Method) then + new LazyType: + def complete(denot: SymDenotation)(using Context) = + sym.info = newInfo + .showing(i"new info of $sym = $result", capt) + else newInfo + updateInfo(sym, updatedInfo) case tree: Bind => val sym = tree.symbol @@ -731,7 +670,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tree: TypeDef => tree.symbol match case cls: ClassSymbol => - inNestedLevelUnless(cls.is(Module)): + ccState.inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo // Compute new self type @@ -751,7 +690,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // Infer the self type for the rest, which is all classes without explicit // self types (to which we also add nested module classes), provided they are // neither pure, nor are publicily extensible with an unconstrained self type. - CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = currentLevel)) + CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = ccState.currentLevel)) // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): @@ -766,7 +705,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if cls.is(ModuleClass) then // if it's a module, the capture set of the module reference is the capture set of the self type val modul = cls.sourceModule - updateInfo(modul, CapturingType(modul.info, selfInfo1.asInstanceOf[Type].captureSet)) + val selfCaptures = selfInfo1 match + case CapturingType(_, refs) => refs + case _ => CaptureSet.empty + // Note: Can't do val selfCaptures = selfInfo1.captureSet here. + // This would potentially give stackoverflows when setup is run repeatedly. + // One test case is pos-custom-args/captures/checkbounds.scala under + // ccConfig.alwaysRepeatRun = true. + updateInfo(modul, CapturingType(modul.info, selfCaptures)) modul.termRef.invalidateCaches() case _ => case _ => @@ -894,7 +840,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: /** Add a capture set variable to `tp` if necessary. */ private def addVar(tp: Type, owner: Symbol)(using Context): Type = - decorate(tp, CaptureSet.Var(owner, _, level = currentLevel)) + decorate(tp, CaptureSet.Var(owner, _, level = ccState.currentLevel)) /** A map that adds capture sets at all contra- and invariant positions * in a type where a capture set would be needed. This is used to make types @@ -902,7 +848,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * We don't need to add in covariant positions since pure types are * anyway compatible with capturing types. */ - private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: + private def fluidify(using Context) = new TypeMap: def apply(t: Type): Type = t match case t: MethodType => mapOver(t) diff --git a/compiler/src/dotty/tools/dotc/cc/ccConfig.scala b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala new file mode 100644 index 000000000000..4c06f4a0843d --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/ccConfig.scala @@ -0,0 +1,57 @@ +package dotty.tools +package dotc +package cc + +import core.Contexts.Context +import config.{Feature, SourceVersion} + +object ccConfig: + + /** If enabled, use a special path in recheckClosure for closures + * to compare the result tpt of the anonymous functon with the expected + * result type. This can narrow the scope of error messages. + */ + inline val preTypeClosureResults = false + + /** If this and `preTypeClosureResults` are both enabled, disable `preTypeClosureResults` + * for eta expansions. This can improve some error messages. + */ + inline val handleEtaExpansionsSpecially = true + + /** Don't require @use for reach capabilities that are accessed + * only in a nested closure. This is unsound without additional + * mitigation measures, as shown by unsound-reach-5.scala. + */ + inline val deferredReaches = false + + /** Check that if a type map (which is not a BiTypeMap) maps initial capture + * set variable elements to themselves it will not map any elements added in + * the future to something else. That is, we can safely use a capture set + * variable itself as the image under the map. By default this is off since it + * is a bit expensive to check. + */ + inline val checkSkippedMaps = false + + /** Always repeat a capture checking run at least once if there are no errors + * yet. Used for stress-testing the logic for when a new capture checking run needs + * to be scheduled because a provisionally solved capture set was later extended. + * So far this happens only in very few tests. With the flag on, the logic is + * tested for all tests except neg tests. + */ + inline val alwaysRepeatRun = false + + /** After capture checking, check that no capture set contains ParamRefs that are outside + * its scope. This used to occur and was fixed by healTypeParam. It should no longer + * occur now. + */ + inline val postCheckCapturesets = false + + /** If true, turn on separation checking */ + def useSepChecks(using Context): Boolean = + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`) + + /** Not used currently. Handy for trying out new features */ + def newScheme(using Context): Boolean = + Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`) + +end ccConfig \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala index da4ee330ca3c..ee668efa8378 100644 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ b/compiler/src/dotty/tools/dotc/cc/root.scala @@ -11,11 +11,10 @@ import typer.ErrorReporting.errorType import Names.TermName import NameKinds.ExistentialBinderName import NameOps.isImpureFunction -import CaptureSet.IdempotentCaptRefMap import reporting.Message import util.{SimpleIdentitySet, EqHashMap} import util.Spans.NoSpan -import annotation.internal.sharable +import annotation.constructorOnly /** A module defining three kinds of root capabilities * - `cap` of kind `Global`: This is the global root capability. Among others it is @@ -76,15 +75,14 @@ object root: case Kind.Global => false end Kind - @sharable private var rootId = 0 - /** The annotation of a root instance */ - case class Annot(kind: Kind) extends Annotation: + case class Annot(kind: Kind)(using @constructorOnly ictx: Context) extends Annotation: /** id printed under -uniqid, for debugging */ val id = - rootId += 1 - rootId + val ccs = ccState + ccs.rootId += 1 + ccs.rootId override def symbol(using Context) = defn.RootCapabilityAnnot override def tree(using Context) = New(symbol.typeRef, Nil) @@ -113,6 +111,11 @@ object root: case Kind.Result(binder) => tm match case tm: Substituters.SubstBindingMap[MethodType] @unchecked if tm.from eq binder => derivedAnnotation(tm.to) + case tm: Substituters.SubstBindingsMap => + var i = 0 + while i < tm.from.length && (tm.from(i) ne binder) do i += 1 + if i < tm.from.length then derivedAnnotation(tm.to(i).asInstanceOf[MethodType]) + else this case _ => this case _ => this end Annot @@ -162,7 +165,9 @@ object root: case _ if root.isCap => Some(Kind.Global) case _ => None - /** Map each occurrence of cap to a different Sep.Cap instance */ + /** Map each occurrence of cap to a different Fresh instance + * Exception: CapSet^ stays as it is. + */ class CapToFresh(owner: Symbol)(using Context) extends BiTypeMap, FollowAliasesMap: thisMap => @@ -171,6 +176,8 @@ object root: else t match case t: CaptureRef if t.isCap => Fresh.withOwner(owner) + case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => + t case t @ CapturingType(_, _) => mapOver(t) case t @ AnnotatedType(parent, ann) => @@ -182,17 +189,27 @@ object root: case _ => mapFollowingAliases(t) + override def fuse(next: BiTypeMap)(using Context) = next match + case next: Inverse => assert(false); Some(IdentityTypeMap) + case _ => None + override def toString = "CapToFresh" - lazy val inverse: BiTypeMap & FollowAliasesMap = new BiTypeMap with FollowAliasesMap: + class Inverse extends BiTypeMap, FollowAliasesMap: def apply(t: Type): Type = t match case t @ Fresh(_) => cap case t @ CapturingType(_, refs) => mapOver(t) case _ => mapFollowingAliases(t) + override def fuse(next: BiTypeMap)(using Context) = next match + case next: CapToFresh => assert(false); Some(IdentityTypeMap) + case _ => None + def inverse = thisMap override def toString = thisMap.toString + ".inverse" + lazy val inverse = Inverse() + end CapToFresh /** Maps cap to fresh */ @@ -205,7 +222,7 @@ object root: /** Map top-level free existential variables one-to-one to Fresh instances */ def resultToFresh(tp: Type)(using Context): Type = - val subst = new IdempotentCaptRefMap: + val subst = new TypeMap: val seen = EqHashMap[Annotation, CaptureRef]() var localBinders: SimpleIdentitySet[MethodType] = SimpleIdentitySet.empty @@ -304,7 +321,12 @@ object root: case t: (LazyRef | TypeVar) => mapConserveSuper(t) case _ => - if keepAliases then mapOver(t) else mapFollowingAliases(t) + try + if keepAliases then mapOver(t) + else mapFollowingAliases(t) + catch case ex: AssertionError => + println(i"error while mapping $t") + throw ex end toResultInResults /** If `refs` contains an occurrence of `cap` or `cap.rd`, the current context diff --git a/compiler/src/dotty/tools/dotc/core/Substituters.scala b/compiler/src/dotty/tools/dotc/core/Substituters.scala index be474215fbb8..6cd238bb0e19 100644 --- a/compiler/src/dotty/tools/dotc/core/Substituters.scala +++ b/compiler/src/dotty/tools/dotc/core/Substituters.scala @@ -2,7 +2,6 @@ package dotty.tools.dotc package core import Types.*, Symbols.*, Contexts.* -import cc.CaptureSet.IdempotentCaptRefMap /** Substitution operations on types. See the corresponding `subst` and * `substThis` methods on class Type for an explanation. @@ -164,10 +163,35 @@ object Substituters: } final class SubstBindingMap[BT <: BindingType](val from: BT, val to: BT)(using Context) extends DeepTypeMap, BiTypeMap { + override def fuse(next: BiTypeMap)(using Context) = next match + case next: SubstBindingMap[_] => + if next.from eq to then Some(SubstBindingMap(from, next.to)) + else Some(SubstBindingsMap(Array(from, next.from), Array(to, next.to))) + case _ => None def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) def inverse = SubstBindingMap(to, from) } + final class SubstBindingsMap(val from: Array[BindingType], val to: Array[BindingType])(using Context) extends DeepTypeMap, BiTypeMap { + override def fuse(next: BiTypeMap)(using Context) = next match + case next: SubstBindingMap[_] => + var i = 0 + while i < from.length && (to(i) ne next.from) do i += 1 + if i < from.length then Some(SubstBindingsMap(from, to.updated(i, next.to))) + else Some(SubstBindingsMap(from :+ next.from, to :+ next.to)) + case _ => None + + def apply(tp: Type): Type = tp match + case tp: BoundType => + var i = 0 + while i < from.length && (from(i) ne tp.binder) do i += 1 + if i < from.length then tp.copyBoundType(to(i).asInstanceOf[tp.BT]) else tp + case _ => + mapOver(tp) + + def inverse = SubstBindingsMap(to, from) + } + final class Subst1Map(from: Symbol, to: Type)(using Context) extends DeepTypeMap { def apply(tp: Type): Type = subst1(tp, from, to, this)(using mapCtx) } @@ -180,7 +204,7 @@ object Substituters: def apply(tp: Type): Type = subst(tp, from, to, this)(using mapCtx) } - final class SubstSymMap(from: List[Symbol], to: List[Symbol])(using Context) extends DeepTypeMap, BiTypeMap { + final class SubstSymMap(from: List[Symbol], to: List[Symbol])(using Context) extends DeepTypeMap { def apply(tp: Type): Type = substSym(tp, from, to, this)(using mapCtx) def inverse = SubstSymMap(to, from) // implicitly requires that `to` contains no duplicates. } @@ -189,15 +213,15 @@ object Substituters: def apply(tp: Type): Type = substThis(tp, from, to, this)(using mapCtx) } - final class SubstRecThisMap(from: Type, to: Type)(using Context) extends DeepTypeMap, IdempotentCaptRefMap { + final class SubstRecThisMap(from: Type, to: Type)(using Context) extends DeepTypeMap { def apply(tp: Type): Type = substRecThis(tp, from, to, this)(using mapCtx) } - final class SubstParamMap(from: ParamRef, to: Type)(using Context) extends DeepTypeMap, IdempotentCaptRefMap { + final class SubstParamMap(from: ParamRef, to: Type)(using Context) extends DeepTypeMap { def apply(tp: Type): Type = substParam(tp, from, to, this)(using mapCtx) } - final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends DeepTypeMap, IdempotentCaptRefMap { + final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends DeepTypeMap { def apply(tp: Type): Type = substParams(tp, from, to, this)(using mapCtx) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index f8b0675bd204..b8a8be57ca05 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -256,7 +256,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling report.log(explained(_.isSubType(tp1, tp2, approx), short = false)) } // Eliminate LazyRefs before checking whether we have seen a type before - val normalize = new TypeMap with CaptureSet.IdempotentCaptRefMap { + val normalize = new TypeMap { val DerefLimit = 10 var derefCount = 0 def apply(t: Type) = t match { @@ -426,7 +426,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (tp1.prefix.isStable) return tryLiftedToThis1 case _ => if isCaptureVarComparison then - return subCaptures(tp1.captureSet, tp2.captureSet).isOK + return CCState.withCapAsRoot: + subCaptures(tp1.captureSet, tp2.captureSet).isOK if (tp1 eq NothingType) || isBottom(tp1) then return true } @@ -575,7 +576,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && (isBottom(tp1) || GADTusage(tp2.symbol)) if isCaptureVarComparison then - return subCaptures(tp1.captureSet, tp2.captureSet).isOK + return CCState.withCapAsRoot: + subCaptures(tp1.captureSet, tp2.captureSet).isOK isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) || compareGADT @@ -804,8 +806,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling (tp1.signature consistentParams tp2.signature) && matchingMethodParams(tp1, tp2) && (!tp2.isImplicitMethod || tp1.isImplicitMethod) && - CCState.inNewExistentialScope(tp2): - isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) + isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false } compareMethod diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 0b758061febd..a1e26c20fdbb 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.Inferencing.* import typer.IfBottom import reporting.TestingReporter import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing} -import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap, VarState} +import CaptureSet.{CompareResult, IdentityCaptRefMap, VarState} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -56,7 +56,7 @@ object TypeOps: } /** The TypeMap handling the asSeenFrom */ - class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap { + class AsSeenFromMap(pre: Type, cls: Symbol)(using Context) extends ApproximatingTypeMap { /** The number of range approximations in invariant or contravariant positions * performed by this TypeMap. @@ -180,7 +180,7 @@ object TypeOps: if (normed.exists) simplify(normed, theMap) else mapOver case tp: MethodicType => // See documentation of `Types#simplified` - val addTypeVars = new TypeMap with IdempotentCaptRefMap: + val addTypeVars = new TypeMap: val constraint = ctx.typerState.constraint def apply(t: Type): Type = t match case t: TypeParamRef => constraint.typeVarOfParam(t).orElse(t) @@ -448,7 +448,7 @@ object TypeOps: } /** An approximating map that drops NamedTypes matching `toAvoid` and wildcard types. */ - abstract class AvoidMap(using Context) extends AvoidWildcardsMap, IdempotentCaptRefMap: + abstract class AvoidMap(using Context) extends AvoidWildcardsMap: @threadUnsafe lazy val localParamRefs = util.HashSet[Type]() def toAvoid(tp: NamedType): Boolean diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index df7700c73a17..a7a50f2003c0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -39,7 +39,7 @@ import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized import cc.* -import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} +import CaptureSet.{CompareResult, IdentityCaptRefMap} import scala.annotation.internal.sharable import scala.annotation.threadUnsafe @@ -1167,10 +1167,9 @@ object Types extends TypeUtils { * * @param isSubType a function used for checking subtype relationships. */ - final def overrides(that: Type, matchLoosely: => Boolean, checkClassInfo: Boolean = true, - isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { + final def overrides(that: Type, matchLoosely: => Boolean, checkClassInfo: Boolean = true)(using Context): Boolean = { !checkClassInfo && this.isInstanceOf[ClassInfo] - || isSubType(this.widenExpr, that.widenExpr) + || (this.widenExpr frozen_<:< that.widenExpr) || matchLoosely && { val this1 = this.widenNullaryMethod val that1 = that.widenNullaryMethod @@ -4071,7 +4070,7 @@ object Types extends TypeUtils { /** The least supertype of `resultType` that does not contain parameter dependencies */ def nonDependentResultApprox(using Context): Type = if isResultDependent then - val dropDependencies = new ApproximatingTypeMap with IdempotentCaptRefMap { + val dropDependencies = new ApproximatingTypeMap { def apply(tp: Type) = tp match { case tp @ TermParamRef(`thisLambdaType`, _) => range(defn.NothingType, atVariance(1)(apply(tp.underlying))) @@ -4220,6 +4219,35 @@ object Types extends TypeUtils { } mt } + + /** Not safe to use in general: Check that all references to an enclosing + * TermParamRef name point to that TermParamRef + */ + def checkValid2(mt: MethodType)(using Context): mt.type = { + var t = new TypeTraverser: + val ps = mt.paramNames.zip(mt.paramRefs).toMap + def traverse(t: Type) = + t match + case CapturingType(p, refs) => + def checkRefs(refs: CaptureSet) = + for elem <- refs.elems do + elem match + case elem: TermParamRef => + val elemName = elem.binder.paramNames(elem.paramNum) + //assert(elemName.toString != "f") + ps.get(elemName) match + case Some(elemRef) => assert(elemRef eq elem, i"bad $mt") + case _ => + case root.Result(binder) if binder ne mt => + assert(binder.paramNames.toList != mt.paramNames.toList, i"bad $mt") + case _ => + checkRefs(refs) + traverse(p) + case _ => + traverseChildren(t) + t.traverse(mt.resType) + mt + } } object MethodType extends MethodTypeCompanion("MethodType") { @@ -4716,7 +4744,7 @@ object Types extends TypeUtils { override def hashIsStable: Boolean = false } - abstract class ParamRef extends BoundType { + abstract class ParamRef extends BoundType, CaptureRef { type BT <: LambdaType def paramNum: Int def paramName: binder.ThisName = binder.paramNames(paramNum) @@ -4763,7 +4791,7 @@ object Types extends TypeUtils { * refer to `TypeParamRef(binder, paramNum)`. */ abstract case class TypeParamRef(binder: TypeLambda, paramNum: Int) - extends ParamRef, CaptureRef { + extends ParamRef { type BT = TypeLambda def kindString: String = "Type" def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum) @@ -6086,9 +6114,6 @@ object Types extends TypeUtils { def forward(ref: CaptureRef): CaptureRef = val result = this(ref) def ensureTrackable(tp: Type): CaptureRef = tp match - /* Issue #22437: handle case when info is not yet available during postProcess in CC setup */ - case tp: (TypeParamRef | TermRef) if tp.underlying == NoType => - tp case tp: CaptureRef => if tp.isTrackableRef then tp else ensureTrackable(tp.underlying) @@ -6100,10 +6125,11 @@ object Types extends TypeUtils { /** A restriction of the inverse to a function on tracked CaptureRefs */ def backward(ref: CaptureRef): CaptureRef = inverse(ref) match - /* Ensure bijection for issue #22437 fix in method forward above: */ - case result: (TypeParamRef | TermRef) if result.underlying == NoType => - result case result: CaptureRef if result.isTrackableRef => result + + /** Fuse with another map */ + def fuse(next: BiTypeMap)(using Context): Option[TypeMap] = None + end BiTypeMap abstract class TypeMap(implicit protected var mapCtx: Context) diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index a9a17f6db464..20b0c8534920 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -8,7 +8,7 @@ import NameKinds.DefaultGetterName import NullOpsDecorator.* import collection.immutable.BitSet import scala.annotation.tailrec -import cc.isCaptureChecking +import cc.{isCaptureChecking, CCState} import scala.compiletime.uninitialized @@ -210,8 +210,7 @@ object OverridingPairs: * @param isSubType A function to be used for checking subtype relationships * between term fields. */ - def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false, - isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = + def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false)(using Context): Boolean = if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( @@ -226,6 +225,6 @@ object OverridingPairs: ) else member.name.is(DefaultGetterName) // default getters are not checked for compatibility - || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack, isSubType = isSubType) + || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) end OverridingPairs diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 60c36fdbbbb7..f057959a52b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -20,7 +20,6 @@ import config.Printers.recheckr import util.Property import StdNames.nme import annotation.constructorOnly -import cc.CaptureSet.IdempotentCaptRefMap import annotation.tailrec import dotty.tools.dotc.cc.boxed @@ -189,6 +188,9 @@ abstract class Recheck extends Phase, SymTransformer: def keepNuTypes(using Context): Boolean = ctx.settings.Xprint.value.containsPhase(thisPhase) + def resetNuTypes()(using Context): Unit = + nuTypes.clear(resetToInitial = false) + /** A map from NamedTypes to the denotations they had before this phase. * Needed so that we can `reset` them after this phase. */ @@ -288,7 +290,7 @@ abstract class Recheck extends Phase, SymTransformer: * The invocation is currently disabled in recheckApply. */ private def mapJavaArgs(formals: List[Type])(using Context): List[Type] = - val tm = new TypeMap with IdempotentCaptRefMap: + val tm = new TypeMap: def apply(t: Type) = t match case t: TypeRef if t.symbol == defn.ObjectClass => defn.FromJavaObjectType @@ -583,7 +585,7 @@ abstract class Recheck extends Phase, SymTransformer: * Otherwise, `tp` itself */ def widenSkolems(tp: Type)(using Context): Type = - object widenSkolems extends TypeMap, IdempotentCaptRefMap: + object widenSkolems extends TypeMap: var didWiden: Boolean = false def apply(t: Type): Type = t match case t: SkolemType if variance >= 0 => @@ -606,6 +608,7 @@ abstract class Recheck extends Phase, SymTransformer: case _ => checkConformsExpr(tpe.widenExpr, pt.widenExpr, tree) def isCompatible(actual: Type, expected: Type)(using Context): Boolean = + try actual <:< expected || expected.isRepeatedParam && isCompatible(actual, @@ -614,6 +617,9 @@ abstract class Recheck extends Phase, SymTransformer: val widened = widenSkolems(expected) (widened ne expected) && isCompatible(actual, widened) } + catch case ex: AssertionError => + println(i"fail while $actual iscompat $expected") + throw ex def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Type = //println(i"check conforms $actual <:< $expected") diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 3d63911d199e..f81c1bf19cb1 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -242,25 +242,24 @@ object RefChecks { && (inLinearizationOrder(sym1, sym2, parent) || parent.is(JavaDefined)) && !sym2.is(AbsOverride) - /** 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 omit override checks between `overriding` and `overridden`. * Overridden in capture checking to handle non-capture checked classes leniently. */ def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = true - protected def additionalChecks(overriding: Symbol, overridden: Symbol)(using Context): Unit = () + /** Adapt member type and other type so that they can be compared with `frozen_<:<`. + * @return optionally, if adaptation is necessary, the pair of adapted types (memberTp', otherTp') + * Note: we return an Option result to avoid a tuple allocation in the normal case + * where no adaptation is necessary. + */ + def adaptOverridePair(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = None - private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType + protected def additionalChecks(overriding: Symbol, overridden: Symbol)(using Context): Unit = () - def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = + def checkAll(checkOverride: (Symbol, Symbol) => Unit) = while hasNext do if needsCheck(overriding, overridden) then - checkOverride(subtypeChecker, overriding, overridden) + checkOverride(overriding, overridden) additionalChecks(overriding, overridden) next() @@ -275,7 +274,7 @@ object RefChecks { if dcl.is(Deferred) then for other <- dcl.allOverriddenSymbols do if !other.is(Deferred) then - checkOverride(subtypeChecker, dcl, other) + checkOverride(dcl, other) end checkAll // Disabled for capture checking since traits can get different parameter refinements @@ -428,19 +427,27 @@ object RefChecks { /* Check that all conditions for overriding `other` by `member` * of class `clazz` are met. */ - def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = - def memberTp(self: Type) = + def checkOverride(member: Symbol, other: Symbol): Unit = + def memberType(self: Type) = if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) - def otherTp(self: Type) = - self.memberInfo(other) + def otherType(self: Type) = + self.memberInfo(other) + + var memberTp = memberType(self) + var otherTp = otherType(self) + checker.adaptOverridePair(member, memberTp, otherTp) match + case Some((mtp, otp)) => + memberTp = mtp + otherTp = otp + case None => refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") - def noErrorType = !memberTp(self).isErroneous && !otherTp(self).isErroneous + def noErrorType = !memberTp.isErroneous && !otherTp.isErroneous def overrideErrorMsg(core: Context ?=> String, compareTypes: Boolean = false): Message = - val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType) + val (mtp, otp) = if compareTypes then (memberTp, otherTp) else (NoType, NoType) OverrideError(core, self, member, other, mtp, otp) def compatTypes(memberTp: Type, otherTp: Type): Boolean = @@ -448,8 +455,8 @@ object RefChecks { isOverridingPair(member, memberTp, other, otherTp, fallBack = warnOnMigration( overrideErrorMsg("no longer has compatible type"), - (if (member.owner == clazz) member else clazz).srcPos, version = `3.0`), - isSubType = checkSubType) + (if member.owner == clazz then member else clazz).srcPos, + version = `3.0`)) catch case ex: MissingType => // can happen when called with upwardsSelf as qualifier of memberTp and otherTp, // because in that case we might access types that are not members of the qualifier. @@ -469,7 +476,7 @@ object RefChecks { // with box adaptation, we simply ignore capture annotations here. // This should be safe since the compatibility under box adaptation is already // checked. - memberTp(self).matches(otherTp(self)) + memberTp.matches(otherTp) } def emitOverrideError(fullmsg: Message) = @@ -624,12 +631,21 @@ object RefChecks { overrideError("is not inline, cannot implement an inline method") else if (other.isScala2Macro && !member.isScala2Macro) // (1.11) overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros") - else if !compatTypes(memberTp(self), otherTp(self)) - && !compatTypes(memberTp(upwardsSelf), otherTp(upwardsSelf)) + else if !compatTypes(memberTp, otherTp) && !member.is(Tracked) // Tracked members need to be excluded since they are abstract type members with // singleton types. Concrete overrides usually have a wider type. // TODO: Should we exclude all refinements inherited from parents? + && { + var memberTpUp = memberType(upwardsSelf) + var otherTpUp = otherType(upwardsSelf) + checker.adaptOverridePair(member, memberTpUp, otherTpUp) match + case Some((mtp, otp)) => + memberTpUp = mtp + otherTpUp = otp + case _ => + !compatTypes(memberTpUp, otherTpUp) + } then overrideError("has incompatible type", compareTypes = true) else if (member.targetName != other.targetName) @@ -637,7 +653,7 @@ object RefChecks { overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match") else overrideError("cannot have a @targetName annotation since external names would be different") - else if intoOccurrences(memberTp(self)) != intoOccurrences(otherTp(self)) then + else if intoOccurrences(memberTp) != intoOccurrences(otherTp) then overrideError("has different occurrences of `into` modifiers", compareTypes = true) else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) && !member.is(Tracked) // see remark on tracked members above diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 47ed2aa6564d..e62c80d7bff7 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -37,6 +37,7 @@ class CompilationTests { compileFilesInDir("tests/pos-special/sourcepath/outer", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFile("tests/pos-special/sourcepath/outer/nested/Test4.scala", defaultOptions.and("-sourcepath", "tests/pos-special/sourcepath")), compileFilesInDir("tests/pos-scala2", defaultOptions.and("-source", "3.0-migration")), + compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), compileFile("tests/pos-special/utf8encoded.scala", defaultOptions.and("-encoding", "UTF8")), compileFile("tests/pos-special/utf16encoded.scala", defaultOptions.and("-encoding", "UTF16")), compileDir("tests/pos-special/i18589", defaultOptions.and("-Wsafe-init").without("-Ycheck:all")), @@ -55,12 +56,6 @@ class CompilationTests { aggregateTests(tests*).checkCompile() } - @Test def posCC: Unit = - given TestGroup = TestGroup("compilePosCC") - aggregateTests( - compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), - ).checkCompile() - @Test def rewrites: Unit = { implicit val testGroup: TestGroup = TestGroup("rewrites") diff --git a/library/src/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala index 909adc13a1c2..9c4af7f2336d 100644 --- a/library/src/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -1,12 +1,12 @@ package scala.annotation -/** An annotation that indicates capture of a set of references under -Ycc. +/** An annotation that indicates capture of a set of references under capture checking. * * T @retains(x, y, z) * * is the internal representation used for the capturing type * - * {x, y, z} T + * T ^ {x, y, z} * * The annotation can also be written explicitly if one wants to avoid the * non-standard capturing type syntax. diff --git a/tests/neg-custom-args/captures/box-adapt-boxing.scala b/tests/neg-custom-args/captures/box-adapt-boxing.scala index 0052828dbabb..f23496c5ea9f 100644 --- a/tests/neg-custom-args/captures/box-adapt-boxing.scala +++ b/tests/neg-custom-args/captures/box-adapt-boxing.scala @@ -1,11 +1,11 @@ trait Cap def main(io: Cap^, fs: Cap^): Unit = { - val test1: Unit -> Unit = _ => { + val test1: Unit -> Unit = _ => { // error type Op = [T] -> (T ->{io} Unit) -> Unit val f: (Cap^{io}) -> Unit = ??? val op: Op = ??? - op[Cap^{io}](f) // error + op[Cap^{io}](f) // expected type of f: {io} (box {io} Cap) -> Unit // actual type: ({io} Cap) -> Unit // adapting f to the expected type will also diff --git a/tests/neg-custom-args/captures/box-adapt-cases.check b/tests/neg-custom-args/captures/box-adapt-cases.check index e5cadb051ac1..c89f7a09f293 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.check +++ b/tests/neg-custom-args/captures/box-adapt-cases.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/box-adapt-cases.scala:15:10 ------------------------------ 15 | x.value(cap => cap.use()) // error | ^^^^^^^^^^^^^^^^ - | Found: (cap: box Cap^?) ->{io} Int + | Found: (cap: box Cap^{io}) ->{io} Int | Required: (cap: box Cap^{io}) -> Int | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 0e1a016442ed..a6d04354bbbf 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,17 +1,27 @@ +-- Warning: tests/neg-custom-args/captures/byname.scala:5:21 ----------------------------------------------------------- +5 | def g(x: Int) = if cap2 == cap2 then 1 else x + | ^^^^ + | Another capture checking run needs to be scheduled because + | reference (cap2 : Cap) is not included in the previously estimated + | capture set {} of method f -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- 10 | h(f2()) // error | ^^^^ - | Found: Int ->{cap1} Int - | Required: Int ->? Int + | Found: () ?->{cap1} Int ->{cap1} Int + | Required: () ?=> Int ->{cap2} Int | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- +-- [E007] Type Mismatch 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} - | of an enclosing function literal with expected type () ?->{cap1} I --- Error: tests/neg-custom-args/captures/byname.scala:22:12 ------------------------------------------------------------ + | Found: () ?->{cap2} I^? + | Required: () ?->{cap1} I + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:22:5 ---------------------------------------- 22 | h2(() => g())() // error - | ^^^ - | reference (cap2 : Cap) is not included in the allowed capture set {cap1} - | of an enclosing function literal with expected type () ->{cap1} I + | ^^^^^^^^^ + | Found: () ->{cap2} I^? + | Required: () ->{cap1} I + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index 80ee1aba84e1..f200bb6083b5 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -20,7 +20,7 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { // error + val b = handle[Exception, () => Nothing] { // error // 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 804e18072752..e0eb8731b3de 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,13 +1,17 @@ --- Error: tests/neg-custom-args/captures/capt1.scala:5:11 -------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:5:2 ------------------------------------------ 5 | () => if x == null then y else y // error - | ^ - | reference (x : C^) 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:8:11 -------------------------------------------------------------- + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: () ->{x} C^? + | Required: () -> C + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:8:2 ------------------------------------------ 8 | () => if x == null then y else y // error - | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type Matchable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: () ->{x} C^? + | Required: Matchable + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:15:2 ----------------------------------------- 15 | def f(y: Int) = if x == null then y else y // error | ^ @@ -38,11 +42,13 @@ | ^^^^^^^^^ | Type variable X of method h cannot be instantiated to () -> box C^ since | the part box C^ of that type captures the root capability `cap`. --- Error: tests/neg-custom-args/captures/capt1.scala:36:30 ------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:36:24 ---------------------------------------- 36 | val z2 = h[() -> Cap](() => x) // error // error - | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ + | ^^^^^^^ + | Found: () ->{x} box C^{x} + | Required: () -> box C^ + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/capt1.scala:38:13 ------------------------------------------------------------- 38 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/cc-this.check b/tests/neg-custom-args/captures/cc-this.check index 070e815d6d45..8e17901d1dcd 100644 --- a/tests/neg-custom-args/captures/cc-this.check +++ b/tests/neg-custom-args/captures/cc-this.check @@ -1,15 +1,19 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this.scala:8:15 --------------------------------------- -8 | val y: C = this // error - | ^^^^ - | Found: (C.this : C^{C.this.x}) - | Required: C - | - | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/cc-this.scala:10:15 ----------------------------------------------------------- -10 | class C2(val x: () => Int): // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this.scala:10:15 -------------------------------------- +10 | val y: C = this // error + | ^^^^ + | Found: (C.this : C^{C.this.x}) + | Required: C + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/cc-this.scala:12:15 ----------------------------------------------------------- +12 | 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 --- Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ------------------------------------------------------------ -17 | class C4(val f: () => Int) extends C3 // error +-- Error: tests/neg-custom-args/captures/cc-this.scala:19:8 ------------------------------------------------------------ +19 | class C4(val f: () => Int) extends C3 // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |reference (C4.this.f : () => Int) captured by this self type is not included in the allowed capture set {} of pure base class class C3 +-- Error: tests/neg-custom-args/captures/cc-this.scala:33:8 ------------------------------------------------------------ +33 | def c3 = c2.y // error + | ^ + | Separation failure: method c3's inferred result type C{val x: () => Int}^{cc} hides non-local parameter cc diff --git a/tests/neg-custom-args/captures/cc-this.scala b/tests/neg-custom-args/captures/cc-this.scala index e4336ed457af..c64098c8f5b4 100644 --- a/tests/neg-custom-args/captures/cc-this.scala +++ b/tests/neg-custom-args/captures/cc-this.scala @@ -1,3 +1,5 @@ +import caps.consume + class Cap extends caps.Capability def eff(using Cap): Unit = () @@ -16,4 +18,18 @@ def test(using Cap) = class C4(val f: () => Int) extends C3 // error +// The following is a variation of pos/cc-this.scala +def test2(using @consume cc: Cap) = + + class C(val x: () => Int): + val y: C^ = this + + def f = () => + eff(using cc) + 1 + def c1 = new C(f) + def c2 = c1 + def c3 = c2.y // error + val c4: C^ = c3 + val _ = c3: C^ diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check index d3caa720e88a..76b4036a8821 100644 --- a/tests/neg-custom-args/captures/dcs-tvar.check +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -1,10 +1,10 @@ -- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- 6 | () => runOps(xs) // error | ^^ - | reference xs* is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit + | Local reach capability xs* leaks into capture scope of method f. + | To allow this, the parameter xs should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- 9 | () => runOps(xs) // error | ^^ - | reference xs* is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit + | Local reach capability xs* leaks into capture scope of method g. + | To allow this, the parameter xs should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check index 14ecbcffd8dd..4e2fb9525e8f 100644 --- a/tests/neg-custom-args/captures/delayedRunops.check +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -1,13 +1,13 @@ -- Error: tests/neg-custom-args/captures/delayedRunops.scala:17:13 ----------------------------------------------------- 17 | runOps(ops1) // error | ^^^^ - | reference ops* is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit + | Local reach capability ops* leaks into capture scope of method delayedRunOps1. + | To allow this, the parameter ops should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/delayedRunops.scala:29:13 ----------------------------------------------------- 29 | runOps(ops1) // error | ^^^^ - | reference ops* is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit + | Local reach capability ops* leaks into capture scope of method delayedRunOps3. + | To allow this, the parameter ops should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:16 ----------------------------------------------------- 22 | val ops1: List[() => Unit] = ops // error | ^^^^^^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/depfun-reach.check b/tests/neg-custom-args/captures/depfun-reach.check index 5ffb0873d752..c4b74c5123c6 100644 --- a/tests/neg-custom-args/captures/depfun-reach.check +++ b/tests/neg-custom-args/captures/depfun-reach.check @@ -1,3 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:12:27 --------------------------------- +12 | List(() => op.foreach((f,g) => { f(); g() })) // error (???) + | ^^^^^^^^^^^^^^^^^^^^ + | Found: (x$1: (box () ->? Unit, box () ->? Unit)^?) ->? Unit + | Required: (x$1: (box () ->{op*} Unit, box () ->{op*} Unit)) => Unit + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:19:4 ---------------------------------- 19 | op // error | ^^ diff --git a/tests/neg-custom-args/captures/depfun-reach.scala b/tests/neg-custom-args/captures/depfun-reach.scala index 5e8c298df637..074a9429bec4 100644 --- a/tests/neg-custom-args/captures/depfun-reach.scala +++ b/tests/neg-custom-args/captures/depfun-reach.scala @@ -9,7 +9,7 @@ object List: def test(io: Object^, async: Object^) = def compose(op: List[(() ->{cap} Unit, () ->{cap} Unit)]): List[() ->{op*} Unit] = - List(() => op.foreach((f,g) => { f(); g() })) + List(() => op.foreach((f,g) => { f(); g() })) // error (???) def compose1(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{op*} Unit] = compose(op) diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 47559ab97568..9fc99f1a10d2 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -14,16 +14,24 @@ -------------------------------------------------------------------------------------------------------------------- | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:74:10 ------------------------ -74 | Future: fut ?=> // error: type mismatch +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:69:10 ------------------------ +69 | Future: fut ?=> // error, type mismatch | ^ - | Found: Future[box T^?]^{fr, lbl} - | Required: Future[box T^?]^? + |Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async} + | box Future[box T^?]^{fr, contextual$9} + |Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]^) ?->{fresh} box Future[box T^?]^? + | + |Note that reference contextual$9.type + |cannot be included in outer capture set ? +70 | fr.await.ok + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:73:35 ------------------------ +73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch + | ^ + |Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]^) ?->{fr, async} Future[box T^?]^{fr, lbl} + |Required: (lbl: boundary.Label[Result[Future[T], E]]^) ?->{fresh} Future[T] +74 | Future: fut ?=> 75 | fr.await.ok | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 --------------------------------------------- -68 | Result.make: //lbl ?=> // error, escaping label from Result - | ^^^^^^^^^^^ - |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): - | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index b3756056abbd..33596772b9a0 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -65,12 +65,12 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: //lbl ?=> // error, escaping label from Result - Future: fut ?=> + Result.make: //lbl ?=> + Future: fut ?=> // error, type mismatch fr.await.ok def fail5[T, E](fr: Future[Result[T, E]]^) = - Result.make[Future[T], E]: lbl ?=> - Future: fut ?=> // error: type mismatch + Result.make[Future[T], E]: lbl ?=> // error: type mismatch + Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index 28611959d905..dc832fca0ee9 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -14,16 +14,24 @@ -------------------------------------------------------------------------------------------------------------------- | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:74:10 --------------------------------- -74 | Future: fut ?=> // error: type mismatch +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:69:10 --------------------------------- +69 | Future: fut ?=> // error, type mismatch | ^ - | Found: Future[box T^?]^{fr, lbl} - | Required: Future[box T^?]^? + |Found: (contextual$9: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]) ?->{fr, async} + | box Future[box T^?]^{fr, contextual$9} + |Required: (contextual$9: boundary.Label[Result[box Future[box T^?]^?, box E^?]]) ?->{fresh} box Future[box T^?]^? + | + |Note that reference contextual$9.type + |cannot be included in outer capture set ? +70 | fr.await.ok + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps.scala:73:35 --------------------------------- +73 | Result.make[Future[T], E]: lbl ?=> // error: type mismatch + | ^ + |Found: (lbl: boundary.Label[box Result[box Future[box T^?]^?, box E^?]^?]) ?->{fr, async} Future[box T^?]^{fr, lbl} + |Required: (lbl: boundary.Label[Result[Future[T], E]]) ?->{fresh} Future[T] +74 | Future: fut ?=> 75 | fr.await.ok | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/effect-swaps.scala:68:15 ------------------------------------------------------ -68 | Result.make: // error: local reference leaks - | ^^^^^^^^^^^ - |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]): - | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 3f0cc25fbb25..06dca2bfa004 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -65,12 +65,12 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: // error: local reference leaks - Future: fut ?=> + Result.make: + Future: fut ?=> // error, type mismatch fr.await.ok def fail5[T, E](fr: Future[Result[T, E]]^) = - Result.make[Future[T], E]: lbl ?=> - Future: fut ?=> // error: type mismatch + Result.make[Future[T], E]: lbl ?=> // error: type mismatch + Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/erased-methods2.check b/tests/neg-custom-args/captures/erased-methods2.check index 832d9a6c4a10..876ff36b3696 100644 --- a/tests/neg-custom-args/captures/erased-methods2.check +++ b/tests/neg-custom-args/captures/erased-methods2.check @@ -1,28 +1,26 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/erased-methods2.scala:21:9 ------------------------------- -21 | ?=> (x$2: CT[Ex2]^) // error - | ^ - | Found: (erased x$2: CT[Ex2]^) ?->{x$1} Unit - | Required: (erased x$2: CT[Ex2]^) ?->? Unit +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/erased-methods2.scala:20:4 ------------------------------- +20 | = (x$1: CT[Ex3]^) // error + | ^ + | Found: (erased x$1: CT[Ex3]^) ?->? (erased x$2: CT[Ex2]^?) ?->{x$1} Unit + | Required: (erased x$1: CT[Ex3]^) ?->{fresh} (erased x$2: CT[Ex2]^) ?->{localcap} Unit | - | Note that the existential capture root in (erased x$2: CT[Ex2]^) ?=> Unit - | cannot subsume the capability x$1.type + | Note that the existential capture root in (erased x$2: CT[Ex2]^) ?=> Unit + | cannot subsume the capability x$1.type since that capability is not a SharedCapability +21 | ?=> (x$2: CT[Ex2]^) 22 | ?=> 23 | //given (CT[Ex3]^) = x$1 24 | Throw(new Ex3) | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/erased-methods2.scala:32:9 ------------------------------- -32 | ?=> (erased x$2: CT[Ex2]^) // error - | ^ - | Found: (erased x$2: CT[Ex2]^) ?->{x$1} (erased x$2: CT[Ex1]^) ?->{x$1} Unit - | Required: (erased x$1²: CT[Ex2]^) ?->? (erased x$2: CT[Ex1]^) ?->? Unit +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/erased-methods2.scala:31:4 ------------------------------- +31 | = (erased x$1: CT[Ex3]^) // error + | ^ + |Found: (erased x$1: CT[Ex3]^) ?->? (erased x$1: CT[Ex2]^?) ?->{x$1} (erased x$2: CT[Ex1]^?) ?->{x$1} Unit + |Required: (erased x$1: CT[Ex3]^) ?->{fresh} (erased x$1: CT[Ex2]^) ?->{localcap} (erased x$2: CT[Ex1]^) ?->{localcap} Unit | - | where: x$1 is a parameter in an anonymous function in method foo10a - | x$1² is a reference to a value parameter - | - | - | Note that the existential capture root in (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?->{localcap} Unit - | cannot subsume the capability x$1.type + |Note that the existential capture root in (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?->{localcap} Unit + |cannot subsume the capability x$1.type since that capability is not a SharedCapability +32 | ?=> (erased x$2: CT[Ex2]^) 33 | ?=> (erased x$3: CT[Ex1]^) 34 | ?=> Throw(new Ex3) | diff --git a/tests/neg-custom-args/captures/erased-methods2.scala b/tests/neg-custom-args/captures/erased-methods2.scala index 0b59f741323a..6e111f1702da 100644 --- a/tests/neg-custom-args/captures/erased-methods2.scala +++ b/tests/neg-custom-args/captures/erased-methods2.scala @@ -17,8 +17,8 @@ def foo9a(i: Int) : (x$1: CT[Ex3]^) ?=> (x$2: CT[Ex2]^) ?=> Unit - = (x$1: CT[Ex3]^) - ?=> (x$2: CT[Ex2]^) // error + = (x$1: CT[Ex3]^) // error + ?=> (x$2: CT[Ex2]^) ?=> //given (CT[Ex3]^) = x$1 Throw(new Ex3) @@ -28,7 +28,7 @@ def foo10a(i: Int) ?=> (erased x$1: CT[Ex2]^) ?=> (erased x$2: CT[Ex1]^) ?=> Unit - = (erased x$1: CT[Ex3]^) - ?=> (erased x$2: CT[Ex2]^) // error + = (erased x$1: CT[Ex3]^) // error + ?=> (erased x$2: CT[Ex2]^) ?=> (erased x$3: CT[Ex1]^) ?=> Throw(new Ex3) diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index b7669e9b68ea..d658adcad17b 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -5,8 +5,10 @@ | Required: () -> Proc^{f} | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/eta.scala:6:14 ------------------------------------------- 6 | bar( () => f ) // error - | ^ - | reference (f : Proc^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box () ->? Unit + | ^^^^^^^ + | Found: () ->{f} box () ->{f} Unit + | Required: () -> box () ->? Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/filevar-multi-ios.scala b/tests/neg-custom-args/captures/filevar-multi-ios.scala index 8ffc8d8e299c..f827184a44e8 100644 --- a/tests/neg-custom-args/captures/filevar-multi-ios.scala +++ b/tests/neg-custom-args/captures/filevar-multi-ios.scala @@ -17,10 +17,10 @@ object test1: op(new File) def test(io3: IO, io4: IO) = - withFile(io3): f => + withFile(io3): f => // error val o = Service(io3, io4) - o.file = f // error - o.file2 = f // error + o.file = f + o.file2 = f o.log object test2: @@ -34,8 +34,8 @@ object test2: op(new File) def test(io3: IO, io4: IO) = - withFile(io3): f => + withFile(io3): f => // error val o = Service(io3, io4) o.file = f - o.file2 = f // error + o.file2 = f o.log diff --git a/tests/neg-custom-args/captures/gears-problem.check b/tests/neg-custom-args/captures/gears-problem.check index eb37feb6d568..1c3e648a6264 100644 --- a/tests/neg-custom-args/captures/gears-problem.check +++ b/tests/neg-custom-args/captures/gears-problem.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:24:34 -------------------------------- 24 | val fut2: Future[T]^{fs*} = r.get // error | ^^^^^ - | Found: Future[box T^?]^{collector.futures*} + | Found: Future[box T^{}]^{collector.futures*} | Required: Future[T]^{fs*} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.check b/tests/neg-custom-args/captures/heal-tparam-cs.check index 6367452db7f7..4cd30d74f4dc 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.check +++ b/tests/neg-custom-args/captures/heal-tparam-cs.check @@ -1,3 +1,15 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:10:23 ------------------------------- +10 | val test1 = localCap { c => // error + | ^ + | Found: (c: Capp^) ->? box () ->{c} Unit + | Required: (c: Capp^) ->{fresh} box () ->? Unit + | + | Note that reference c.type + | cannot be included in outer capture set ? +11 | () => { c.use() } +12 | } + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:15:13 ------------------------------- 15 | localCap { c => // error | ^ @@ -5,7 +17,7 @@ | Required: (c: Capp^) -> () ->{localcap} Unit | | Note that the existential capture root in () => Unit - | cannot subsume the capability x$0.type + | cannot subsume the capability x$0.type since that capability is not a SharedCapability 16 | (c1: Capp^) => () => { c1.use() } 17 | } | @@ -19,21 +31,17 @@ 27 | } | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:41:10 ------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:41:4 -------------------------------- 41 | io => () => io.use() // error - | ^^^^^^^^^^^^^^ - | Found: () ->{io} Unit - | Required: () ->? Unit + | ^^^^^^^^^^^^^^^^^^^^ + | Found: (io: Capp^) ->? () ->{io} Unit + | Required: (io: Capp^) -> () -> Unit | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:44:10 ------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:44:4 -------------------------------- 44 | io => () => io.use() // error - | ^^^^^^^^^^^^^^ - | Found: () ->{io} Unit - | Required: () ->? Unit + | ^^^^^^^^^^^^^^^^^^^^ + | Found: (io: Capp^) ->? () ->{io} Unit + | Required: (io: Capp^) -> () -> Unit | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/heal-tparam-cs.scala:10:14 ---------------------------------------------------- -10 | val test1 = localCap { c => // error - | ^^^^^^^^ - | local reference c leaks into outer capture set of type parameter T of method localCap diff --git a/tests/neg-custom-args/captures/heal-tparam-cs.scala b/tests/neg-custom-args/captures/heal-tparam-cs.scala index 6d0b838613f8..231aca12cc53 100644 --- a/tests/neg-custom-args/captures/heal-tparam-cs.scala +++ b/tests/neg-custom-args/captures/heal-tparam-cs.scala @@ -17,7 +17,7 @@ def main(io: Capp^, net: Capp^): Unit = { } val test3: (c: Capp^{io}) -> () ->{io} Unit = - localCap { c => // ok + localCap { c => (c1: Capp^{io}) => () => { c1.use() } } diff --git a/tests/neg-custom-args/captures/i15049.scala b/tests/neg-custom-args/captures/i15049.scala index ff6b17c360de..7f8368631d4f 100644 --- a/tests/neg-custom-args/captures/i15049.scala +++ b/tests/neg-custom-args/captures/i15049.scala @@ -7,4 +7,4 @@ class Foo: def Test: Unit = val f = new Foo f.withSession(s => s).request // error - f.withSession[Session^](t => t) // error + f.withSession[Session^](t => t) // error // error diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index b867636a64cd..98791d729c16 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,18 +1,8 @@ --- Error: tests/neg-custom-args/captures/i15772.scala:21:26 ------------------------------------------------------------ -21 | val c : C^{x} = new C(x) // error - | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int -- Error: tests/neg-custom-args/captures/i15772.scala:22:46 ------------------------------------------------------------ 22 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error | ^^^^^^^ |C^ => Unit cannot be box-converted to box C{val arg: C^}^{c} ->{cap, c} Unit |since the additional capture set {c} resulting from box conversion is not allowed in box C{val arg: C^}^{c} => Unit --- Error: tests/neg-custom-args/captures/i15772.scala:28:26 ------------------------------------------------------------ -28 | val c : C^{x} = new C(x) // error - | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Int -- Error: tests/neg-custom-args/captures/i15772.scala:29:35 ------------------------------------------------------------ 29 | val boxed2 : Observe[C^] = box2(c) // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/i15772.scala b/tests/neg-custom-args/captures/i15772.scala index face1e8a0ff5..31bd2a5f2c20 100644 --- a/tests/neg-custom-args/captures/i15772.scala +++ b/tests/neg-custom-args/captures/i15772.scala @@ -18,14 +18,14 @@ class C(val arg: C^) { def main1(x: C^) : () -> Int = () => - val c : C^{x} = new C(x) // error + val c : C^{x} = new C(x) val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error boxed1((cap: C^) => unsafe(c)) 0 def main2(x: C^) : () -> Int = () => - val c : C^{x} = new C(x) // error + val c : C^{x} = new C(x) val boxed2 : Observe[C^] = box2(c) // error boxed2((cap: C^) => unsafe(c)) 0 diff --git a/tests/neg-custom-args/captures/i15923.scala b/tests/neg-custom-args/captures/i15923.scala index e71f01996938..f7de2d8b4d2a 100644 --- a/tests/neg-custom-args/captures/i15923.scala +++ b/tests/neg-custom-args/captures/i15923.scala @@ -9,6 +9,6 @@ def bar() = { result } - val leak = withCap(cap => mkId(cap)) // error // error + val leak = withCap(cap => mkId(cap)) // error leak { cap => cap.use() } } \ No newline at end of file diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check index 745ccea1f905..e7ae191dbf14 100644 --- a/tests/neg-custom-args/captures/i16114.check +++ b/tests/neg-custom-args/captures/i16114.check @@ -3,21 +3,11 @@ | ^^^^ | Type variable T of method expect cannot be instantiated to box Cap^ since | that type captures the root capability `cap`. --- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- -20 | fs // error (limitation) - | ^^ - | reference (fs : Cap^) is not included in the allowed capture set {io} - | of an enclosing function literal with expected type Unit ->{io} Unit -- Error: tests/neg-custom-args/captures/i16114.scala:24:13 ------------------------------------------------------------ 24 | expect[Cap^] { // error | ^^^^ | Type variable T of method expect cannot be instantiated to box Cap^ since | that type captures the root capability `cap`. --- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- -26 | io // error (limitation) - | ^^ - | reference (io : Cap^) is not included in the allowed capture set {fs} - | of an enclosing function literal with expected type Unit ->{fs} Unit -- Error: tests/neg-custom-args/captures/i16114.scala:30:13 ------------------------------------------------------------ 30 | expect[Cap^] { // error | ^^^^ @@ -33,13 +23,3 @@ | ^^^^ | Type variable T of method expect cannot be instantiated to box Cap^ since | that type captures the root capability `cap`. --- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- -40 | io.use() // error - | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type Unit -> Unit --- Error: tests/neg-custom-args/captures/i16114.scala:41:8 ------------------------------------------------------------- -41 | io // error - | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type Unit -> Unit diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index 801ea3b11a3d..dccd9564d8ca 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -17,13 +17,13 @@ def main(fs: Cap^): Unit = { val op1: Unit ->{io} Unit = (x: Unit) => expect[Cap^] { // error io.use() - fs // error (limitation) + fs } val op2: Unit ->{fs} Unit = (x: Unit) => expect[Cap^] { // error fs.use() - io // error (limitation) + io } val op3: Unit ->{io} Unit = (x: Unit) => @@ -37,8 +37,8 @@ def main(fs: Cap^): Unit = { val op: Unit -> Unit = (x: Unit) => expect[Cap^] { // error - io.use() // error - io // error + io.use() + io } op } diff --git a/tests/neg-custom-args/captures/i16226.check b/tests/neg-custom-args/captures/i16226.check new file mode 100644 index 000000000000..5a3c3b690f6d --- /dev/null +++ b/tests/neg-custom-args/captures/i16226.check @@ -0,0 +1,19 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i16226.scala:13:4 ---------------------------------------- +13 | (ref1, f1) => map[A, B](ref1, f1) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (ref1: LazyRef[box A^?]{val elem: () ->{cap, fresh} A^?}^{io}, f1: (x$0: A^?) => B^?) ->? + | LazyRef[box B^?]{val elem: () ->{localcap} B^?}^{f1, ref1} + | Required: (ref1: LazyRef[A]^{io}, f1: A => B) ->{fresh} LazyRef[B]^{fresh} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i16226.scala:15:4 ---------------------------------------- +15 | (ref1, f1) => map[A, B](ref1, f1) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (ref1: LazyRef[box A^?]{val elem: () ->{cap, fresh} A^?}^{io}, f1: (x$0: A^?) => B^?) ->? + | LazyRef[box B^?]{val elem: () ->{localcap} B^?}^{f1, ref1} + | Required: (ref: LazyRef[A]^{io}, f: A => B) ->{fresh} LazyRef[B]^{localcap} + | + | Note that the existential capture root in LazyRef[B]^ + | cannot subsume the capability f1.type since that capability is not a SharedCapability + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i16226.scala b/tests/neg-custom-args/captures/i16226.scala new file mode 100644 index 000000000000..8e2d91b300ef --- /dev/null +++ b/tests/neg-custom-args/captures/i16226.scala @@ -0,0 +1,16 @@ +class Cap extends caps.Capability + +class LazyRef[T](val elem: () => T): + val get: () ->{elem} T = elem + def map[U](f: T => U): LazyRef[U]^{f, this} = + new LazyRef(() => f(elem())) + +def map[A, B](ref: LazyRef[A]^, f: A => B): LazyRef[B]^{f, ref} = + new LazyRef(() => f(ref.elem())) + +def main(io: Cap) = { + def mapc[A, B]: (LazyRef[A]^{io}, A => B) => LazyRef[B]^ = + (ref1, f1) => map[A, B](ref1, f1) // error + def mapd[A, B]: (ref: LazyRef[A]^{io}, f: A => B) => LazyRef[B]^ = + (ref1, f1) => map[A, B](ref1, f1) // error +} diff --git a/tests/neg-custom-args/captures/i21313.check b/tests/neg-custom-args/captures/i21313.check index f76f4bc6871e..f087c1f86d63 100644 --- a/tests/neg-custom-args/captures/i21313.check +++ b/tests/neg-custom-args/captures/i21313.check @@ -5,7 +5,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21313.scala:15:12 --------------------------------------- 15 | ac1.await(src2) // error | ^^^^ - | Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^?) + | Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^{}) | Required: Source[Int, scala.caps.CapSet^{ac1}]^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index cb1400ebc420..8eade64e372f 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -4,10 +4,17 @@ | Type variable T of object Boxed cannot be instantiated to box IO^ since | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:15:18 ------------------------------------------------------------ -15 | val a = usingIO[IO^](x => x) // error +15 | val a = usingIO[IO^](x => x) // error // error | ^^^ | Type variable R of method usingIO cannot be instantiated to box IO^ since | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21401.scala:15:23 --------------------------------------- +15 | val a = usingIO[IO^](x => x) // error // error + | ^^^^^^ + | Found: (x: IO^) ->? box IO^{x} + | Required: (x: IO^) ->{fresh} box IO^{fresh} + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ 16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error | ^^^ diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index f6071e2a47d5..bd10a97952a2 100644 --- a/tests/neg-custom-args/captures/i21401.scala +++ b/tests/neg-custom-args/captures/i21401.scala @@ -12,7 +12,7 @@ def mkRes(x: IO^): Res = val op1: Boxed[IO^] -> R = op op1(Boxed[IO^](x)) // error def test2() = - val a = usingIO[IO^](x => x) // error + val a = usingIO[IO^](x => x) // error // error val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error val y: IO^{x*} = x.unbox // was error diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 382768b73bc6..6f1a2a4e23fa 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -1,11 +1,8 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:33 --------------------------------------- 12 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? - | ^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: F) ->{files*.rd} box Logger{val f²: File^?}^? - | Required: (f: box F^{files*.rd}) ->{fresh} box Logger{val f²: File^?}^? - | - | where: f is a reference to a value parameter - | f² is a value in class Logger + | ^ + | Found: (f : F) + | Required: File^ | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:15:12 --------------------------------------- diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check index b022d71b8418..70efe6990d4c 100644 --- a/tests/neg-custom-args/captures/i21920.check +++ b/tests/neg-custom-args/captures/i21920.check @@ -1,10 +1,10 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 --------------------------------------- -34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f²}]^?}^? - | Required: Cell[File] +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:35 --------------------------------------- +34 | val cell: Cell[File] = File.open(f => Cell(() => Seq(f))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: File^?) ->? box Cell[box File^?]{val head: () ->? IterableOnce[box File^?]^?}^? + | Required: (f: File^) ->{fresh} box Cell[box File^?]{val head: () ->? IterableOnce[box File^?]^?}^? | - | where: f is a reference to a value parameter - | f² is a reference to a value parameter + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21920.scala b/tests/neg-custom-args/captures/i21920.scala index 7ea5a63969b1..6bdff6dd65fb 100644 --- a/tests/neg-custom-args/captures/i21920.scala +++ b/tests/neg-custom-args/captures/i21920.scala @@ -7,8 +7,8 @@ trait Iterator[+A] extends IterableOnce[A]: trait IterableOnce[+A] extends Any: def iterator: Iterator[A]^{this} -final class Cell[A](head: => IterableOnce[A]^): - def headIterator: Iterator[A]^{this} = head.iterator +final class Cell[A](head: () => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head().iterator class File private (): private var closed = false @@ -31,6 +31,6 @@ object Seq: def apply[A](xs: A*): IterableOnce[A] = ??? @main def Main() = - val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + val cell: Cell[File] = File.open(f => Cell(() => Seq(f))) // error val file = cell.headIterator.next() file.read() diff --git a/tests/neg-custom-args/captures/i22808.scala b/tests/neg-custom-args/captures/i22808.scala new file mode 100644 index 000000000000..2b8cc92c8acf --- /dev/null +++ b/tests/neg-custom-args/captures/i22808.scala @@ -0,0 +1,20 @@ +class Box[T](x: T): + def m: T = ??? +def test1(io: Object^): Unit = + def foo(): Unit = bar() + def bar(): Unit = + val x = () => + foo() + val y = Box(io) + println(y.m) // warning: another run needs to be scheduled + val _: () -> Unit = x // error + +def test2(io: Object^): Unit = + def foo(): Unit = bar() + def bar(): Unit = + val x = () => + foo() + val _: () -> Unit = x // error + val y = Box(io) + println(y.m) // warning: another run needs to be scheduled + val _: () -> Unit = x // error diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index 65fed0c4ec7e..acdbfaaca3e0 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{cap1}) - | Required: lazylists.LazyList[Int] + | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^{}}^{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/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 9199d468b55a..e2327a576070 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,18 +1,8 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} Cap^ + | reference (io : Cap^) is not included in the allowed capture set {} of the self type of class Fuzz -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} Cap^ --- Error: tests/neg-custom-args/captures/leaked-curried.scala:13:15 ---------------------------------------------------- -13 | val get: () ->{} () ->{io} Cap^ = // error: separation - | ^^^^^^^^^^^^^^^^^^^^^^ - | Separation failure: value get's type () -> () ->{io} Cap^ hides non-local parameter io --- Error: tests/neg-custom-args/captures/leaked-curried.scala:16:15 ---------------------------------------------------- -16 | val get: () ->{} () ->{io} Cap^ = // error: separation - | ^^^^^^^^^^^^^^^^^^^^^^ - | Separation failure: value get's type () -> () ->{io} Cap^ hides non-local parameter io + | reference (io : Cap^) 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 576c9d8a5db9..96b8c0254d76 100644 --- a/tests/neg-custom-args/captures/leaked-curried.scala +++ b/tests/neg-custom-args/captures/leaked-curried.scala @@ -10,10 +10,10 @@ def main(): Unit = val leaked = withCap: (io: Cap^) => class Fuzz extends Box, Pure: self => - val get: () ->{} () ->{io} Cap^ = // error: separation + val get: () ->{} () ->{io} Cap^ = () => () => io // error class Foo extends Box, Pure: - val get: () ->{} () ->{io} Cap^ = // error: separation + 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/leaking-iterators.check b/tests/neg-custom-args/captures/leaking-iterators.check index 2f47a26e894a..54a8d6ccea9e 100644 --- a/tests/neg-custom-args/captures/leaking-iterators.check +++ b/tests/neg-custom-args/captures/leaking-iterators.check @@ -1,4 +1,13 @@ --- Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:2 -------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/leaking-iterators.scala:56:16 ---------------------------- 56 | usingLogFile: log => // error - | ^^^^^^^^^^^^ - | local reference log leaks into outer capture set of type parameter R of method usingLogFile in package cctest + | ^ + | Found: (log: java.io.FileOutputStream^) ->? box cctest.Iterator[Int]^{log} + | Required: (log: java.io.FileOutputStream^) ->{fresh} box cctest.Iterator[Int]^? + | + | Note that reference log.type + | cannot be included in outer capture set ? +57 | xs.iterator.map: x => +58 | log.write(x) +59 | x * x + | + | 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 96512055e119..7ef29c36efc3 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -6,10 +6,7 @@ -- [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: String) ->? String - | - | Note that reference (cap3 : CC^), defined in method scope - | cannot be included in outer capture set ? of value r + | Found: (x: String) ->{cap3} String + | Required: (x: String) -> String | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/linear-buffer.check b/tests/neg-custom-args/captures/linear-buffer.check index 16ba3bd096a2..55af88406356 100644 --- a/tests/neg-custom-args/captures/linear-buffer.check +++ b/tests/neg-custom-args/captures/linear-buffer.check @@ -11,7 +11,7 @@ -- Error: tests/neg-custom-args/captures/linear-buffer.scala:6:9 ------------------------------------------------------- 6 | def foo = // error | ^ - |Separation failure: method foo's inferred result type BadBuffer[box T^?]^ hides non-local this of class class BadBuffer. + |Separation failure: method foo's inferred result type BadBuffer[box T^{}]^ hides non-local this of class class BadBuffer. |The access must be in a @consume method to allow this. -- Error: tests/neg-custom-args/captures/linear-buffer.scala:19:17 ----------------------------------------------------- 19 | val buf3 = app(buf, 3) // error diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index a55abfaaf98d..a49722eb4bb9 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -21,9 +21,6 @@ | Found: () => Unit | Required: () ->{p} Unit | - | Note that the universal capability `cap` - | cannot be included in capture set {p} of variable y - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:15:8 ------------------------------------- 15 | y = q // error, was OK under unsealed @@ -31,9 +28,6 @@ | 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 - | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/outer-var.scala:17:57 --------------------------------------------------------- 17 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index b1e46c300ef7..8dc433afc979 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -1,18 +1,18 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:23:11 -------------------------------------- -23 | cur = (() => f.write()) :: Nil // error - | ^^^^^^^^^^^^^^^^^^^^^^^ - | Found: List[box () ->{f} Unit] - | Required: List[box () ->{xs*} Unit] +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:22:13 -------------------------------------- +22 | usingFile: f => // error + | ^ + | Found: (f: File^?) ->? Unit + | Required: (f: File^) ->{fresh} Unit +23 | cur = (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:34:7 --------------------------------------- -34 | (() => f.write()) :: Nil // error - | ^^^^^^^^^^^^^^^^^^^^^^^ - | Found: List[box () ->{f} Unit] - | Required: box List[box () ->{xs*} Unit]^? - | - | Note that reference (f : File^), defined in method $anonfun - | cannot be included in outer capture set {xs*} of value cur +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:13 -------------------------------------- +32 | usingFile: f => // error + | ^ + | Found: (f: File^?) ->? Unit + | Required: (f: File^) ->{fresh} Unit +33 | cur.set: +34 | (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:37:6 ------------------------------------------------------------ @@ -44,81 +44,58 @@ | ^ | Type variable A of constructor Id cannot be instantiated to box () => Unit since | that type captures the root capability `cap`. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:63:27 -------------------------------------- -63 | val f1: File^{id*} = id(f) // error // error - | ^^^^^ - | Found: File^{f} - | Required: File^{id*} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:59:27 -------------------------------------- +59 | val id: File^ -> File^ = x => x // error + | ^^^^^^ + | Found: (x: File^) ->? File^{x} + | Required: (x: File^) -> File^{fresh} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:62:38 -------------------------------------- +62 | val leaked = usingFile[File^{id*}]: f => // error // error + | ^ + | Found: (f: File^?) ->? box File^? + | Required: (f: File^) ->{fresh} box File^{id*} +63 | val f1: File^{id*} = id(f) +64 | f1 | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:67:37 -------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:67:32 -------------------------------------- 67 | val id: (x: File^) -> File^ = x => x // error - | ^ - | Found: (x : File^) - | Required: File^? + | ^^^^^^ + | Found: (x: File^) ->? File^{x} + | Required: (x: File^) -> File^{localcap} | - | Note that the existential capture root in File^ - | cannot subsume the capability x.type + | Note that the existential capture root in File^ + | cannot subsume the capability x.type since that capability is not a SharedCapability | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:71:27 -------------------------------------- -71 | val f1: File^{id*} = id(f) // error // error - | ^^^^^ - | Found: File^{f} - | Required: File^{id*} +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:70:38 -------------------------------------- +70 | val leaked = usingFile[File^{id*}]: f => // error // error + | ^ + | Found: (f: File^?) ->? box File^? + | Required: (f: File^) ->{fresh} box File^{id*} +71 | val f1: File^{id*} = id(f) +72 | f1 | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:88:10 ----------------------------------------------------------- -88 | ps.map((x, y) => compose1(x, y)) // error // error // error sepcheck +88 | ps.map((x, y) => compose1(x, y)) // error | ^ | Local reach capability ps* leaks into capture scope of method mapCompose. | To allow this, the parameter ps should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/reaches.scala:88:13 ----------------------------------------------------------- -88 | ps.map((x, y) => compose1(x, y)) // error // error // error sepcheck - | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose. - | To allow this, the parameter ps should be declared with a @use annotation --- Error: tests/neg-custom-args/captures/reaches.scala:88:28 ----------------------------------------------------------- -88 | ps.map((x, y) => compose1(x, y)) // error // error // error sepcheck - | ^ - | Separation failure: argument of type A ->{x} box A^? - | to method compose1: [A, B, C](f: A => B, g: B => C): A ->{f, g} C - | corresponds to capture-polymorphic formal parameter f of type box A^? => box A^? - | and hides capabilities {x}. - | Some of these overlap with the captures of the second argument with type A ->{y} box A^?. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:91:10 -------------------------------------- +91 | ps.map((x, y) => compose1(x, y)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (x$1: (box (x$0: A^) ->? A^?, box (x$0: A^) ->? A^?)^?) ->? box (x$0: A^?) ->? A^? + | Required: (x$1: (box A ->{ps*} A, box A ->{ps*} A)) ->{fresh} box (x$0: A^?) ->? A^? | - | Hidden set of current argument : {x} - | Hidden footprint of current argument : {x, ps*} - | Capture set of second argument : {y} - | Footprint set of second argument : {y, ps*} - | The two sets overlap at : {ps*} --- Error: tests/neg-custom-args/captures/reaches.scala:91:28 ----------------------------------------------------------- -91 | ps.map((x, y) => compose1(x, y)) // error sepcheck - | ^ - | Separation failure: argument of type A ->{x} box A^? - | to method compose1: [A, B, C](f: A => B, g: B => C): A ->{f, g} C - | corresponds to capture-polymorphic formal parameter f of type box A^? => box A^? - | and hides capabilities {x}. - | Some of these overlap with the captures of the second argument with type A ->{y} box A^?. - | - | Hidden set of current argument : {x} - | Hidden footprint of current argument : {x, ps*} - | Capture set of second argument : {y} - | Footprint set of second argument : {y, ps*} - | The two sets overlap at : {ps*} + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:62:31 ----------------------------------------------------------- -62 | val leaked = usingFile[File^{id*}]: f => // error +62 | val leaked = usingFile[File^{id*}]: f => // error // error | ^^^ | id* cannot be tracked since its deep capture set is empty --- Error: tests/neg-custom-args/captures/reaches.scala:63:18 ----------------------------------------------------------- -63 | val f1: File^{id*} = id(f) // error // error - | ^^^ - | id* cannot be tracked since its deep capture set is empty -- Error: tests/neg-custom-args/captures/reaches.scala:70:31 ----------------------------------------------------------- -70 | val leaked = usingFile[File^{id*}]: f => // error +70 | val leaked = usingFile[File^{id*}]: f => // error // error | ^^^ | id* cannot be tracked since its deep capture set is empty --- Error: tests/neg-custom-args/captures/reaches.scala:71:18 ----------------------------------------------------------- -71 | val f1: File^{id*} = id(f) // error // error - | ^^^ - | id* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index 32afd4066333..709f818a02a8 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -19,8 +19,8 @@ def runAll0(@use xs: List[Proc]): Unit = next() cur = cur.tail: List[() ->{xs*} Unit] - usingFile: f => - cur = (() => f.write()) :: Nil // error + usingFile: f => // error + cur = (() => f.write()) :: Nil def runAll1(@use xs: List[Proc]): Unit = val cur = Ref[List[() ->{xs*} Unit]](xs) // OK, by revised VAR @@ -29,9 +29,9 @@ def runAll1(@use xs: List[Proc]): Unit = next() cur.set(cur.get.tail: List[() ->{xs*} Unit]) - usingFile: f => + usingFile: f => // error cur.set: - (() => f.write()) :: Nil // error + (() => f.write()) :: Nil def runAll2(@consume xs: List[Proc]): Unit = var cur: List[Proc] = xs // error @@ -56,19 +56,19 @@ def test = id(() => f.write()) // was error def attack2 = - val id: File^ -> File^ = x => x + val id: File^ -> File^ = x => x // error // val id: File^ -> File^{fresh} - val leaked = usingFile[File^{id*}]: f => // error - val f1: File^{id*} = id(f) // error // error + val leaked = usingFile[File^{id*}]: f => // error // error + val f1: File^{id*} = id(f) f1 def attack3 = val id: (x: File^) -> File^ = x => x // error // val id: File^ -> EX C.File^C - val leaked = usingFile[File^{id*}]: f => // error - val f1: File^{id*} = id(f) // error // error + val leaked = usingFile[File^{id*}]: f => // error // error + val f1: File^{id*} = id(f) f1 class List[+A]: @@ -85,7 +85,7 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = z => g(f(z)) def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = - ps.map((x, y) => compose1(x, y)) // error // error // error sepcheck + ps.map((x, y) => compose1(x, y)) // error def mapCompose2[A](@use ps: List[(A => A, A => A)]): List[A ->{ps*} A] = - ps.map((x, y) => compose1(x, y)) // error sepcheck + ps.map((x, y) => compose1(x, y)) // error diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index 926e6772bd8f..e9238365ec18 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -1,24 +1,24 @@ -- Error: tests/neg-custom-args/captures/reaches2.scala:10:10 ---------------------------------------------------------- 10 | ps.map((x, y) => compose1(x, y)) // error // error // error | ^ - | reference ps* is not included in the allowed capture set {} - | of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box A^? ->? A^? + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/reaches2.scala:10:13 ---------------------------------------------------------- 10 | ps.map((x, y) => compose1(x, y)) // error // error // error | ^ - | reference ps* is not included in the allowed capture set {} - | of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box A^? ->? A^? + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/reaches2.scala:10:28 ---------------------------------------------------------- 10 | ps.map((x, y) => compose1(x, y)) // error // error // error | ^ - | Separation failure: argument of type A ->{x} box A^? - | to method compose1: [A, B, C](f: A => B, g: B => C): A ->{f, g} C - | corresponds to capture-polymorphic formal parameter f of type box A^? => box A^? - | and hides capabilities {x}. - | Some of these overlap with the captures of the second argument with type A ->{y} box A^?. + | Separation failure: argument of type A ->{x} A + | to method compose1: [A, B, C](f: A => B, g: B => C): A ->{f, g} C + | corresponds to capture-polymorphic formal parameter f of type box A^? => box A^? + | and hides capabilities {x}. + | Some of these overlap with the captures of the second argument with type A ->{y} A. | - | Hidden set of current argument : {x} - | Hidden footprint of current argument : {x, ps*} - | Capture set of second argument : {y} - | Footprint set of second argument : {y, ps*} - | The two sets overlap at : {ps*} + | Hidden set of current argument : {x} + | Hidden footprint of current argument : {x, ps*} + | Capture set of second argument : {y} + | Footprint set of second argument : {y, ps*} + | The two sets overlap at : {ps*} diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index f78c99f919af..e92e2fd960a0 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking trait IO def test1(): Unit = - val f: IO^ => IO^ = x => x + val f: IO^ => IO^ = x => x // error val g: IO^ => IO^{f*} = f // error def test2(): Unit = val f: [R] -> (IO^ => R) -> R = ??? diff --git a/tests/neg-custom-args/captures/scoped-caps.scala b/tests/neg-custom-args/captures/scoped-caps.scala new file mode 100644 index 000000000000..b4705b3d6a62 --- /dev/null +++ b/tests/neg-custom-args/captures/scoped-caps.scala @@ -0,0 +1,28 @@ +class A +class B +class S extends caps.SharedCapability + +def test(io: Object^): Unit = + val f: (x: A^) -> B^ = ??? + val g: A^ -> B^ = f // error + val _: (y: A^) -> B^ = f + val _: (x: A^) -> B^ = g // error + val _: A^ -> B^ = f // error + val _: A^ -> B^ = g + val _: A^ -> B^ = x => g(x) // error, since g is pure, g(x): B^{x} , which does not match B^{fresh} + val _: (x: A^) -> B^ = x => f(x) // error: existential in B cannot subsume `x` since `x` is not shared + + val h: S -> B^ = ??? + val _: (x: S) -> B^ = h // error: direct conversion fails + val _: (x: S) -> B^ = x => h(x) // but eta expansion succeeds (for SharedCapabilities) + + val j: (x: S) -> B^ = ??? + val _: (x: S) -> B^ = j + val _: (x: S) -> B^ = x => j(x) + val _: S -> B^ = j // error + val _: S -> B^ = x => j(x) // error + + val g2: A^ => B^ = ??? + val _: A^ => B^ = x => g2(x) // error: g2(x): B^{g2, x}, and the `x` cannot be subsumed by fresh + val g3: A^ => B^ = ??? + val _: A^{io} => B^ = x => g3(x) // ok, now g3(x): B^{g3, x}, which is widened to B^{g3, io} diff --git a/tests/neg-custom-args/captures/sep-box.check b/tests/neg-custom-args/captures/sep-box.check index 2a2608134130..f60f09d906a8 100644 --- a/tests/neg-custom-args/captures/sep-box.check +++ b/tests/neg-custom-args/captures/sep-box.check @@ -1,14 +1,14 @@ -- Error: tests/neg-custom-args/captures/sep-box.scala:41:9 ------------------------------------------------------------ 41 | par(h1.value, h2.value) // error | ^^^^^^^^ - | Separation failure: argument of type Ref^{xs*} + | Separation failure: argument of type Ref^{h1.value*} | to method par: (x: Ref^, y: Ref^): Unit | corresponds to capture-polymorphic formal parameter x of type Ref^ - | and hides capabilities {xs*}. - | Some of these overlap with the captures of the second argument with type Ref^{xs*}. + | and hides capabilities {h1.value*}. + | Some of these overlap with the captures of the second argument with type Ref^{h2.value*}. | - | Hidden set of current argument : {xs*} - | Hidden footprint of current argument : {xs*} - | Capture set of second argument : {xs*} - | Footprint set of second argument : {xs*} + | Hidden set of current argument : {h1.value*} + | Hidden footprint of current argument : {h1.value*, xs*} + | Capture set of second argument : {h2.value*} + | Footprint set of second argument : {h2.value*, xs*} | The two sets overlap at : {xs*} diff --git a/tests/neg-custom-args/captures/sep-compose.check b/tests/neg-custom-args/captures/sep-compose.check index 459f00789ea8..3bd1e5b08eeb 100644 --- a/tests/neg-custom-args/captures/sep-compose.check +++ b/tests/neg-custom-args/captures/sep-compose.check @@ -12,20 +12,20 @@ | Capture set of second argument : {f} | Footprint set of second argument : {f, a, io} | The two sets overlap at : {f, a, io} --- Error: tests/neg-custom-args/captures/sep-compose.scala:33:7 -------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/sep-compose.scala:33:10 ------------------------------------------------------- 33 | seq4(f)(f) // error - | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq4: (x: () ->{a, cap} Unit)(y: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () ->{a, cap} Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | ^ + | Separation failure: argument of type (f : () ->{a} Unit) + | to method seq4: (x: () ->{a, cap} Unit)(y: () => Unit): Unit + | corresponds to capture-polymorphic formal parameter y of type () => Unit + | and hides capabilities {f}. + | Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of first argument : {f} + | Footprint set of first argument : {f, a, io} + | The two sets overlap at : {f, a, io} -- Error: tests/neg-custom-args/captures/sep-compose.scala:34:7 -------------------------------------------------------- 34 | seq5(f)(f) // error | ^ @@ -54,20 +54,20 @@ | Capture set of second argument : {f} | Footprint set of second argument : {f, a, io} | The two sets overlap at : {f, a, io} --- Error: tests/neg-custom-args/captures/sep-compose.scala:36:7 -------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/sep-compose.scala:36:10 ------------------------------------------------------- 36 | seq7(f, f) // error - | ^ - | Separation failure: argument of type (f : () ->{a} Unit) - | to method seq7: (x: () ->{a, cap} Unit, y: () => Unit): Unit - | corresponds to capture-polymorphic formal parameter x of type () ->{a, cap} Unit - | and hides capabilities {f}. - | Some of these overlap with the captures of the second argument with type (f : () ->{a} Unit). + | ^ + | Separation failure: argument of type (f : () ->{a} Unit) + | to method seq7: (x: () ->{a, cap} Unit, y: () => Unit): Unit + | corresponds to capture-polymorphic formal parameter y of type () => Unit + | and hides capabilities {f}. + | Some of these overlap with the captures of the first argument with type (f : () ->{a} Unit). | - | Hidden set of current argument : {f} - | Hidden footprint of current argument : {f, a, io} - | Capture set of second argument : {f} - | Footprint set of second argument : {f, a, io} - | The two sets overlap at : {f, a, io} + | Hidden set of current argument : {f} + | Hidden footprint of current argument : {f, a, io} + | Capture set of first argument : {f} + | Footprint set of first argument : {f, a, io} + | The two sets overlap at : {f, a, io} -- Error: tests/neg-custom-args/captures/sep-compose.scala:37:7 -------------------------------------------------------- 37 | seq8(f)(f) // error | ^ diff --git a/tests/neg-custom-args/captures/simple-using.check b/tests/neg-custom-args/captures/simple-using.check index 2df7c70e0540..e46b9e50b58d 100644 --- a/tests/neg-custom-args/captures/simple-using.check +++ b/tests/neg-custom-args/captures/simple-using.check @@ -1,4 +1,10 @@ --- Error: tests/neg-custom-args/captures/simple-using.scala:8:2 -------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/simple-using.scala:8:15 ---------------------------------- 8 | usingLogFile { f => () => f.write(2) } // error - | ^^^^^^^^^^^^ - | local reference f leaks into outer capture set of type parameter T of method usingLogFile + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.FileOutputStream^) ->? box () ->{f} Unit + | Required: (f: java.io.FileOutputStream^) ->{fresh} box () ->? Unit + | + | Note that reference f.type + | cannot be included in outer capture set ? + | + | 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 b67dc464d929..9cd40a8bc8c4 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,30 +1,53 @@ -- Error: tests/neg-custom-args/captures/try.scala:23:28 --------------------------------------------------------------- -23 | val a = handle[Exception, CanThrow[Exception]] { // error +23 | val a = handle[Exception, CanThrow[Exception]] { // error // error | ^^^^^^^^^^^^^^^^^^^ | Type variable R of method handle cannot be instantiated to box CT[Exception]^ since | that type captures the root capability `cap`. --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:30:32 ------------------------------------------ -30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: () ->{x} Nothing - | Required: () ->? Nothing +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------ +23 | val a = handle[Exception, CanThrow[Exception]] { // error // error + | ^ + | Found: (x: CT[Exception]^) ->? box CT[Exception]^{x} + | Required: (x: CT[Exception]^) ->{fresh} box CT[Exception]^{fresh} +24 | (x: CanThrow[Exception]) => x +25 | }{ | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- -47 |val global: () -> Int = handle { +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:43 ------------------------------------------ +29 | val b = handle[Exception, () -> Nothing] { // error + | ^ + | Found: (x: CT[Exception]^) ->? () ->{x} Nothing + | Required: (x: CT[Exception]^) ->{fresh} () -> Nothing +30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) +31 | } { + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:35:18 ------------------------------------------ +35 | val xx = handle { // error + | ^ + | Found: (x: CT[Exception]^) ->? box () ->{x} Int + | Required: (x: CT[Exception]^) ->{fresh} box () ->? Int + | + | Note that reference x.type + | cannot be included in outer capture set ? +36 | (x: CanThrow[Exception]) => +37 | () => +38 | raise(new Exception)(using x) +39 | 22 +40 | } { + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:47:31 ------------------------------------------ +47 |val global: () -> Int = handle { // error + | ^ + | Found: (x: CT[Exception]^) ->? box () ->{x} Int + | Required: (x: CT[Exception]^) ->{fresh} box () ->? Int + | + | Note that reference x.type + | cannot be included in outer capture set ? 48 | (x: CanThrow[Exception]) => 49 | () => 50 | raise(new Exception)(using x) 51 | 22 -52 |} { // error - | ^ - | Found: () ->{x} Int - | Required: () -> Int -53 | (ex: Exception) => () => 22 -54 |} +52 |} { | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/try.scala:35:11 --------------------------------------------------------------- -35 | val xx = handle { // error - | ^^^^^^ - | local reference x leaks into outer capture set of type parameter R of method handle diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index a85a18f69caa..753958b234c5 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,14 +20,14 @@ def handle[E <: Exception, R <: Top](op: CT[E]^ => R)(handler: E => R): R = catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { // error + val a = handle[Exception, CanThrow[Exception]] { // error // error (x: CanThrow[Exception]) => x }{ (ex: Exception) => ??? } - 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) => ??? } @@ -44,11 +44,11 @@ def test = yy // OK -val global: () -> Int = handle { +val global: () -> Int = handle { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) 22 -} { // error +} { (ex: Exception) => () => 22 } diff --git a/tests/neg-custom-args/captures/unsound-reach-7.scala b/tests/neg-custom-args/captures/unsound-reach-7.scala index df345cdf7f6d..aa4ea932e5b7 100644 --- a/tests/neg-custom-args/captures/unsound-reach-7.scala +++ b/tests/neg-custom-args/captures/unsound-reach-7.scala @@ -7,7 +7,7 @@ trait Async def main(io: IO^, async: Async^) = def bad[X](ops: List[(X, () ->{io} Unit)])(f: () ->{ops*} Unit): () ->{io} Unit = f // error def runOps(@use ops: List[(() => Unit, () => Unit)]): () ->{ops*} Unit = - () => ops.foreach((f1, f2) => { f1(); f2() }) + () => ops.foreach((f1, f2) => { f1(); f2() }) // error (???) def delayOps(@use ops: List[(() ->{async} Unit, () ->{io} Unit)]): () ->{io} Unit = val runner: () ->{ops*} Unit = runOps(ops) val badRunner: () ->{io} Unit = bad[() ->{async} Unit](ops)(runner) diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check index 4897698e336d..6789ec306464 100644 --- a/tests/neg-custom-args/captures/use-capset.check +++ b/tests/neg-custom-args/captures/use-capset.check @@ -13,7 +13,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:50 ----------------------------------- 13 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} | ^^ - | Found: (h2 : () ->? List[box Object^{io}]^{} ->{io} Object^{io}) - | Required: () -> List[box Object^{io}] -> Object^{io} + | Found: (h2 : () ->{} List[box Object^{io}]^{} ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/usingFile.scala b/tests/neg-custom-args/captures/usingFile.scala index 3927be5ff506..a1724e1d7ee9 100644 --- a/tests/neg-custom-args/captures/usingFile.scala +++ b/tests/neg-custom-args/captures/usingFile.scala @@ -15,9 +15,9 @@ object Test: def usingLogger[T](f: OutputStream^)(op: Logger^{f} => T): T = ??? - usingFile( // error + usingFile( "foo", - file => { + file => { // error usingLogger(file)(l => () => l.log("test")) } ) diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index 068d8be78c70..1c77435685c7 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -1,12 +1,40 @@ --- Error: tests/neg-custom-args/captures/usingLogFile.scala:22:14 ------------------------------------------------------ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:22:27 --------------------------------- 22 | val later = usingLogFile { f => () => f.write(0) } // error - | ^^^^^^^^^^^^ - | local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2 --- Error: tests/neg-custom-args/captures/usingLogFile.scala:27:23 ------------------------------------------------------ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.FileOutputStream^) ->? box () ->{f} Unit + | Required: (f: java.io.FileOutputStream^) ->{fresh} box () ->? Unit + | + | Note that reference f.type + | cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:27:36 --------------------------------- 27 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error - | ^^^^^^^^^^^^ - | local reference f leaks into outer capture set of type parameter T of method usingLogFile in object Test2 --- Error: tests/neg-custom-args/captures/usingLogFile.scala:43:16 ------------------------------------------------------ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.FileOutputStream^) ->? box Test2.Cell[box () ->{f} Unit]^? + | Required: (f: java.io.FileOutputStream^) ->{fresh} box Test2.Cell[box () ->? Unit]^? + | + | Note that reference f.type + | cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:43:33 --------------------------------- 43 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error - | ^^^^^^^^^ - | local reference f leaks into outer capture set of type parameter T of method usingFile in object Test3 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.OutputStream^) ->? box Int ->{f} Unit + | Required: (f: java.io.OutputStream^) ->{fresh} box Int ->? Unit + | + | Note that reference f.type + | cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/usingLogFile.scala:52:6 ---------------------------------- +52 | usingLogger(_, l => () => l.log("test"))) // error after checking mapping scheme + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (_$1: java.io.OutputStream^) ->? box () ->{_$1} Unit + | Required: (_$1: java.io.OutputStream^) ->{fresh} box () ->? Unit + | + | Note that reference _$1.type + | cannot be included in outer capture set ? + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/usingLogFile.scala b/tests/neg-custom-args/captures/usingLogFile.scala index 2b46a5401f46..17603d5309b3 100644 --- a/tests/neg-custom-args/captures/usingLogFile.scala +++ b/tests/neg-custom-args/captures/usingLogFile.scala @@ -49,5 +49,5 @@ object Test3: def test = val later = usingFile("logfile", // now ok - usingLogger(_, l => () => l.log("test"))) + usingLogger(_, l => () => l.log("test"))) // error after checking mapping scheme later() diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index bd5e017a2b0c..fae2645fcb8b 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -4,9 +4,6 @@ | Found: (x: String) ->{cap3} String | Required: (x: String) ->{cap1} String | - | Note that reference (cap3 : CC^), defined in method scope - | cannot be included in outer capture set {cap1} of variable a - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error @@ -14,9 +11,6 @@ | Found: (x: String) ->{cap3} String | Required: (x: String) ->{cap1} String | - | Note that reference (cap3 : CC^), defined in method scope - | cannot be included in outer capture set {cap1} of variable a - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:27:12 ----------------------------------------- 27 | b = List(g) // error @@ -25,7 +19,16 @@ | Required: List[box String ->{cap1, cap2} String] | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars.scala:36:2 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:36:8 ------------------------------------------ 36 | local { cap3 => // error - | ^^^^^ - | local reference cap3 leaks into outer capture set of type parameter T of method local + | ^ + | Found: (cap3: CC^) ->? box String ->{cap3} String + | Required: (cap3: CC^) -> box String ->? String + | + | Note that reference cap3.type + | cannot be included in outer capture set ? +37 | def g(x: String): String = if cap3 == cap3 then "" else "a" +38 | g +39 | } + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index 4d4e42601cb0..e2028941a009 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -3,6 +3,13 @@ | ^^^^^^^^ | Type variable T of trait Foo cannot be instantiated to IO^ since | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:9:24 ----------------------------------- +9 | val foo: IO^ -> IO^ = x => x // error // error + | ^^^^^^ + | Found: (x: IO^) ->? IO^{x} + | Required: (x: IO^) -> IO^{fresh} + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- 13 | val y2: IO^ -> IO^ = y1.foo // error | ^^^^^^ @@ -14,7 +21,7 @@ | Local reach capability x* leaks into capture scope of method test. | To allow this, the parameter x should be declared with a @use annotation -- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error +9 | val foo: IO^ -> IO^ = x => x // error // error | ^ | error overriding value foo in trait Foo of type IO^ -> box IO^; | value foo of type IO^ -> IO^ has incompatible type diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala index 6d3a57d6a669..ae64a3d61fc1 100644 --- a/tests/neg-custom-args/captures/widen-reach.scala +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -6,7 +6,7 @@ trait Foo[+T]: val foo: IO^ -> T trait Bar extends Foo[IO^]: // error - val foo: IO^ -> IO^ = x => x // error + val foo: IO^ -> IO^ = x => x // error // error def test(x: Foo[IO^]): Unit = val y1: Foo[IO^{x*}] = x diff --git a/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala new file mode 100644 index 000000000000..e57946bdadc1 --- /dev/null +++ b/tests/pos-custom-args/captures/cap-paramlist8-desugared.scala @@ -0,0 +1,28 @@ + import language.experimental.captureChecking + import caps.cap + + trait Ctx[T >: Nothing <: Any]() extends Object + + def test: Unit = + { + val x: Any^{cap} = ??? + val y: Any^{cap} = ??? + object O { + val z: Any^{cap} = ??? + } + val baz3: + Int -> [C >: caps.CapSet <: caps.CapSet^, + D >: caps.CapSet <: caps.CapSet^{C^}, E >: caps.CapSet <: + caps.CapSet^{C^, x}] => () -> [F >: caps.CapSet^{x, y} <: + caps.CapSet^{C^, E^}] => (x: Int) -> (Ctx[F]) ?-> Int + = (i: Int) => [ + C >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{cap}, + D >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^}, + E >: _root_.scala.caps.CapSet <: _root_.scala.caps.CapSet^{C^, x}] => + () => [ + F + >: _root_.scala.caps.CapSet^{x, y} <: + _root_.scala.caps.CapSet^{C^, E^} + ] => (x: Int) => (ev: Ctx[F]) ?=> 1 + () + } diff --git a/tests/pos-custom-args/captures/cc-this.scala b/tests/pos-custom-args/captures/cc-this.scala index 638c20d94a91..72c848630def 100644 --- a/tests/pos-custom-args/captures/cc-this.scala +++ b/tests/pos-custom-args/captures/cc-this.scala @@ -1,5 +1,6 @@ -import caps.consume +import caps.consume +import caps.unsafe.unsafeAssumeSeparate class Cap extends caps.Capability @@ -16,6 +17,10 @@ def test(using @consume cc: Cap) = def c1 = new C(f) def c2 = c1 - def c3 = c2.y + def c3 = unsafeAssumeSeparate: + c2.y // unsafe since c3's inferred type is + // C{val x: () ->{} Int}^{cc} + // and that type hides non-local cc. + // c.f. test2 in neg test cc-this.scala val c4: C^ = c3 val _ = c3: C^ diff --git a/tests/pos-custom-args/captures/check-override-typebounds.scala b/tests/pos-custom-args/captures/check-override-typebounds.scala new file mode 100644 index 000000000000..7a662c78b208 --- /dev/null +++ b/tests/pos-custom-args/captures/check-override-typebounds.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait A: + type T <: caps.Capability + +class B extends A: + type T = C + +class C extends caps.Capability + + +trait A2: + type T[Cap^] + + def takesCap[Cap^](t: T[Cap]): Unit + diff --git a/tests/pos-custom-args/captures/i16226.scala b/tests/pos-custom-args/captures/i16226.scala index 071eefbd3420..af0a44e6bdfc 100644 --- a/tests/pos-custom-args/captures/i16226.scala +++ b/tests/pos-custom-args/captures/i16226.scala @@ -1,4 +1,4 @@ -class Cap extends caps.Capability +class Cap extends caps.SharedCapability class LazyRef[T](val elem: () => T): val get: () ->{elem} T = elem @@ -9,6 +9,6 @@ def map[A, B](ref: LazyRef[A]^, f: A => B): LazyRef[B]^{f, ref} = new LazyRef(() => f(ref.elem())) def main(io: Cap) = { - def mapd[A, B]: (LazyRef[A]^{io}, A => B) => LazyRef[B]^ = + def mapd[A, B]: (ref: LazyRef[A]^{io}, f: A ->{io} B) => LazyRef[B]^ = (ref1, f1) => map[A, B](ref1, f1) } diff --git a/tests/pos-custom-args/captures/i20135-explicit.scala b/tests/pos-custom-args/captures/i20135-explicit.scala new file mode 100644 index 000000000000..47a82331d713 --- /dev/null +++ b/tests/pos-custom-args/captures/i20135-explicit.scala @@ -0,0 +1,11 @@ +import language.experimental.captureChecking + +class Network + +class Page(val nw: Network^): + def render(client: Page^{nw} -> Unit) = client(this) + +def main(net: Network^) = + var page: Page{val nw: Network^{net}}^{net} = Page(net) + page.render(p => ()) + diff --git a/tests/pos-custom-args/captures/lists.scala b/tests/pos-custom-args/captures/lists.scala index 5f4991c6be54..7ca26e74f484 100644 --- a/tests/pos-custom-args/captures/lists.scala +++ b/tests/pos-custom-args/captures/lists.scala @@ -21,7 +21,9 @@ def map[A, B](f: A => B)(xs: LIST[A]): LIST[B] = class Cap extends caps.Capability def test(c: Cap, d: Cap, e: Cap) = + def f(x: Cap): Unit = if c == x then () + def g(x: Cap): Unit = if d == x then () val y = f val ys = CONS(y, NIL) @@ -45,6 +47,16 @@ def test(c: Cap, d: Cap, e: Cap) = def m2c: [A, B] -> (f: A => B) -> LIST[A] ->{f} LIST[B] = m2 + def m3 = [A, B] => () => + (f: A => B) => (xs: LIST[A]) => xs.map(f) + + def m3c: [A, B] -> () -> (f: A => B) -> LIST[A] ->{f} LIST[B] = m3 + + def m4 = [A, B] => + (f: A => B) => () => (xs: LIST[A]) => xs.map(f) + + def m4c: [A, B] -> (f: A => B) -> () ->{f} LIST[A] ->{f} LIST[B] = m4 + def eff[A](x: A) = if x == e then x else x val eff2 = [A] => (x: A) => if x == e then x else x @@ -87,4 +99,3 @@ def test(c: Cap, d: Cap, e: Cap) = val c2c: LIST[Cap ->{d, y} Unit]^{e} = c2 val c3 = zs.map(eff2[Cap ->{d, y} Unit]) val c3c: LIST[Cap ->{d, y} Unit]^{e} = c3 -