diff --git a/compiler/src/dotty/tools/dotc/cc/CCState.scala b/compiler/src/dotty/tools/dotc/cc/CCState.scala index 5feae257b97e..c425c5bb4266 100644 --- a/compiler/src/dotty/tools/dotc/cc/CCState.scala +++ b/compiler/src/dotty/tools/dotc/cc/CCState.scala @@ -70,7 +70,7 @@ class CCState: // ------ Iteration count of capture checking run - private var iterCount = 1 + private var iterCount = 0 def iterationId = iterCount @@ -78,6 +78,9 @@ class CCState: iterCount += 1 try op finally iterCount -= 1 + def start(): Unit = + iterCount = 1 + // ------ Global counters ----------------------- /** Next CaptureSet.Var id */ diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 7a6df9af0dcc..6cf2372b19b6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -22,11 +22,18 @@ private val Captures: Key[CaptureSet] = Key() def isCaptureChecking(using Context): Boolean = ctx.phaseId == Phases.checkCapturesPhase.id -/** Are we at checkCaptures or Setup phase? */ +/** Are we in the CheckCaptures or Setup phase? */ def isCaptureCheckingOrSetup(using Context): Boolean = - val ccId = Phases.checkCapturesPhase.id - val ctxId = ctx.phaseId - ctxId == ccId || ctxId == ccId - 1 + val ccPhase = Phases.checkCapturesPhase + ccPhase.exists + && { + val ccId = ccPhase.id + val ctxId = ctx.phaseId + ctxId == ccId + || ctxId == ccId - 1 && ccState.iterationId > 0 + // Note: just checking phase id is not enough since Setup would + // also be the phase after pattern matcher. + } /** A dependent function type with given arguments and result type * TODO Move somewhere else where we treat all function type related ops together. diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 22236a3853ef..70c0b2e72f5a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1360,6 +1360,8 @@ object CaptureSet: else empty case CapturingType(parent, refs) => recur(parent) ++ refs + case tp @ AnnotatedType(parent, ann) if ann.symbol.isRetains => + recur(parent) ++ ann.tree.toCaptureSet case tpd @ defn.RefinedFunctionOf(rinfo: MethodType) if followResult => ofType(tpd.parent, followResult = false) // pick up capture set from parent type ++ recur(rinfo.resType) // add capture set of result diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 1bdd7ce92129..c8fdbb378f76 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1584,7 +1584,7 @@ class CheckCaptures extends Recheck, SymTransformer: case actual @ CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_Mutable) && expected.isValueType - && !expected.isMutableType + && !expected.derivesFromMutable && !expected.isSingleton && !expected.isBoxedCapturing => actual.derivedCapturingType(parent, refs.readOnly) @@ -1761,6 +1761,7 @@ class CheckCaptures extends Recheck, SymTransformer: private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] override def checkUnit(unit: CompilationUnit)(using Context): Unit = + ccState.start() setup.setupUnit(unit.tpdTree, this) collectCapturedMutVars.traverse(unit.tpdTree) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 69bfa96df836..2c4ae364c1a8 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -422,7 +422,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => tp - /** Map references to capability classes C to C^, + /** Map references to capability classes C to C^{cap.rd}, * normalize captures and map to dependent functions. */ def defaultApply(t: Type) = @@ -618,6 +618,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: traverse(body) catches.foreach(traverse) traverse(finalizer) + case tree: New => case _ => traverseChildren(tree) postProcess(tree) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 210e7f12b4b4..2dc96a53d3f7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -53,6 +53,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling needsGc = false maxErrorLevel = -1 errorNotes = Nil + logSize = 0 if Config.checkTypeComparerReset then checkReset() private var pendingSubTypes: util.MutableSet[(Type, Type)] | Null = null @@ -62,6 +63,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling private var maxErrorLevel: Int = -1 protected var errorNotes: List[(Int, ErrorNote)] = Nil + private val undoLog = mutable.ArrayBuffer[() => Unit]() + private var logSize = 0 + private var needsGc = false private var canCompareAtoms: Boolean = true // used for internal consistency checking @@ -542,7 +546,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case tp1 @ CapturingType(parent1, refs1) => def compareCapturing = if tp2.isAny then true - else if subCaptures(refs1, tp2.captureSet) && sameBoxed(tp1, tp2, refs1) + else if compareCaptures(tp1, refs1, tp2, tp2.captureSet) || !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure then val tp2a = @@ -669,12 +673,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && isSubInfo(info1.resultType, info2.resultType.subst(info2, info1)) case (info1 @ CapturingType(parent1, refs1), info2: Type) if info2.stripCapturing.isInstanceOf[MethodOrPoly] => - subCaptures(refs1, info2.captureSet) && sameBoxed(info1, info2, refs1) + compareCaptures(info1, refs1, info2, info2.captureSet) && isSubInfo(parent1, info2) case (info1: Type, CapturingType(parent2, refs2)) if info1.stripCapturing.isInstanceOf[MethodOrPoly] => val refs1 = info1.captureSet - (refs1.isAlwaysEmpty || subCaptures(refs1, refs2)) && sameBoxed(info1, info2, refs1) + (refs1.isAlwaysEmpty || compareCaptures(info1, refs1, info2, refs2)) && isSubInfo(info1, parent2) case _ => isSubType(info1, info2) @@ -873,8 +877,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => false singletonOK - || subCaptures(refs1, refs2) - && sameBoxed(tp1, tp2, refs1) + || compareCaptures(tp1, refs1, tp2, refs2) && (recur(tp1.widen.stripCapturing, parent2) || tp1.isInstanceOf[SingletonType] && recur(tp1, parent2) // this alternative is needed in case the right hand side is a @@ -1010,7 +1013,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if isCaptureCheckingOrSetup then tp1 .match - case tp1: Capability if tp1.isTracked => + case tp1: Capability if isCaptureCheckingOrSetup && tp1.isTracked => CapturingType(tp1w.stripCapturing, tp1.singletonCaptureSet) case _ => tp1w @@ -1579,15 +1582,25 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && tp1.derivesFrom(defn.Caps_CapSet) && tp2.derivesFrom(defn.Caps_CapSet) + def rollBack(prevSize: Int): Unit = + var i = prevSize + while i < undoLog.size do + undoLog(i)() + i += 1 + undoLog.takeInPlace(prevSize) + // begin recur if tp2 eq NoType then false else if tp1 eq tp2 then true else val savedCstr = constraint val savedGadt = ctx.gadt + val savedLogSize = logSize inline def restore() = state.constraint = savedCstr ctx.gadtState.restore(savedGadt) + if undoLog.size != savedLogSize then + rollBack(savedLogSize) val savedSuccessCount = successCount try val result = inNestedLevel: @@ -2110,7 +2123,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * since `T >: Int` is subsumed by both alternatives in the first match clause. * * However, the following should not: - * + * * def foo[T](e: Expr[T]): T = e match * case I1(_) | B(_) => 42 * @@ -2877,13 +2890,24 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling println(i"fail while subCaptures $refs1 <:< $refs2") throw ex - /** Is the boxing status of tp1 and tp2 the same, or alternatively, is - * the capture sets `refs1` of `tp1` a subcapture of the empty set? - * In the latter case, boxing status does not matter. + /** + * - Compare capture sets using subCaptures. If the lower type derives from Mutable and the + * upper type does not, make the lower set read-only. + * - Test whether the boxing status of tp1 and tp2 the same, or alternatively, + * whether the capture set `refs1` of `tp1` is subcapture of the empty set? + * In the latter case, boxing status does not matter. */ - protected def sameBoxed(tp1: Type, tp2: Type, refs1: CaptureSet)(using Context): Boolean = - (tp1.isBoxedCapturing == tp2.isBoxedCapturing) - || refs1.subCaptures(CaptureSet.empty, makeVarState()) + protected def compareCaptures(tp1: Type, refs1: CaptureSet, tp2: Type, refs2: CaptureSet): Boolean = + val refs1Adapted = + if tp1.derivesFromMutable && !tp2.derivesFromMutable + then refs1.readOnly + else refs1 + subCaptures(refs1Adapted, refs2) + && (tp1.isBoxedCapturing == tp2.isBoxedCapturing) + || refs1.subCaptures(CaptureSet.empty, makeVarState()) + + protected def logUndoAction(action: () => Unit) = + undoLog += action // ----------- Diagnostics -------------------------------------------------- @@ -3504,6 +3528,9 @@ object TypeComparer { def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): Boolean = comparing(_.subCaptures(refs1, refs2, vs)) + def logUndoAction(action: () => Unit)(using Context): Unit = + comparer.logUndoAction(action) + def inNestedLevel(op: => Boolean)(using Context): Boolean = comparer.inNestedLevel(op) diff --git a/scala2-library-cc/src/scala/collection/Iterable.scala b/scala2-library-cc/src/scala/collection/Iterable.scala index 6556f31d378d..6b6a03f88965 100644 --- a/scala2-library-cc/src/scala/collection/Iterable.scala +++ b/scala2-library-cc/src/scala/collection/Iterable.scala @@ -17,6 +17,7 @@ import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.Builder import scala.collection.View.{LeftPartitionMapped, RightPartitionMapped} import language.experimental.captureChecking +import scala.caps.unsafe.unsafeAssumePure /** Base trait for generic collections. * @@ -302,7 +303,8 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable * this.sizeIs > size // this.sizeCompare(size) > 0 * }}} */ - @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(this) + @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(this.unsafeAssumePure) + // CC Problem: unsafeAssumePure needed since we had to change the argument signatire of SizeCompareOps /** Compares the size of this $coll to the size of another `Iterable`. * @@ -866,8 +868,15 @@ object IterableOps { * These operations are implemented in terms of * [[scala.collection.IterableOps.sizeCompare(Int) `sizeCompare(Int)`]]. */ - final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]^) extends AnyVal { - this: SizeCompareOps^{it} => + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]/*^*/) extends AnyVal { + this: SizeCompareOps/*^*/ => + // CC Problem: if we add the logically needed `^`s to the `it` parameter and the + // self type, separation checking fails in the compiler-generated equals$extends + // method of the value class. There seems to be something wrong how pattern + // matching interacts with separation checking. A minimized test case + // is pending/pos-custom-args/captures/SizeCompareOps-redux.scala. + // Without the `^`s, the `sizeIs` method needs an unsafeAssumePure. + /** Tests if the size of the collection is less than some value. */ @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 /** Tests if the size of the collection is less than or equal to some value. */ diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping.scala b/tests/neg-custom-args/captures/capture-vars-subtyping.scala index 9a32abe0b6e3..68b26dcf564d 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import language.`3.7` // no separation checking, TODO enable import caps.* def test[C^] = diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala index ccf646f34dca..235bfb5e1cf0 100644 --- a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -25,7 +25,8 @@ trait BoundsTest: val ec2: CapSet^{C} = e val eb: B = e val eb2: CapSet^{B} = e - val ea: A = e + locally: + val ea: A = e val ea2: CapSet^{A} = e val ex: X = e // error val ex2: CapSet^{X} = e // error diff --git a/tests/neg-custom-args/captures/lazyref.check b/tests/neg-custom-args/captures/lazyref.check index 9c2f1a2d1a50..dde9e6fd2bac 100644 --- a/tests/neg-custom-args/captures/lazyref.check +++ b/tests/neg-custom-args/captures/lazyref.check @@ -39,7 +39,7 @@ | ^^^^ | Separation failure: Illegal access to {cap1} which is hidden by the previous definition | of value ref2 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {cap1} + | This type hides capabilities {LazyRef.this.elem, cap1} | | where: => refers to a fresh root capability in the type of value elem -- Error: tests/neg-custom-args/captures/lazyref.scala:26:9 ------------------------------------------------------------ @@ -47,7 +47,7 @@ | ^^^^ | Separation failure: Illegal access to {cap1} which is hidden by the previous definition | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | This type hides capabilities {LazyRef.this.elem, ref2*, cap1} | | where: => refers to a fresh root capability in the type of value elem -- Error: tests/neg-custom-args/captures/lazyref.scala:26:17 ----------------------------------------------------------- @@ -55,7 +55,7 @@ | ^^^^ | Separation failure: Illegal access to {cap2} which is hidden by the previous definition | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | This type hides capabilities {LazyRef.this.elem, ref2*, cap1} | | where: => refers to a fresh root capability in the type of value elem -- Error: tests/neg-custom-args/captures/lazyref.scala:27:11 ----------------------------------------------------------- @@ -63,7 +63,7 @@ | ^^^^ | Separation failure: Illegal access to {cap1, ref1} which is hidden by the previous definition | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | This type hides capabilities {LazyRef.this.elem, ref2*, cap1} | | where: => refers to a fresh root capability in the type of value elem -- Error: tests/neg-custom-args/captures/lazyref.scala:28:11 ----------------------------------------------------------- @@ -71,7 +71,7 @@ | ^^^^ | Separation failure: Illegal access to {cap1, cap2, ref1} which is hidden by the previous definition | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | This type hides capabilities {LazyRef.this.elem, ref2*, cap1} | | where: => refers to a fresh root capability in the type of value elem -- Error: tests/neg-custom-args/captures/lazyref.scala:29:9 ------------------------------------------------------------ @@ -79,6 +79,6 @@ | ^ | Separation failure: Illegal access to {cap2} which is hidden by the previous definition | of value ref3 with type LazyRef[Int]{val elem: () => Int}^{cap2, ref1}. - | This type hides capabilities {ref2*, cap1} + | This type hides capabilities {LazyRef.this.elem, ref2*, cap1} | | where: => refers to a fresh root capability in the type of value elem diff --git a/tests/neg-custom-args/captures/scope-extrude-mut.check b/tests/neg-custom-args/captures/scope-extrude-mut.check index 1e01ddf834f8..70e6abc6a2ff 100644 --- a/tests/neg-custom-args/captures/scope-extrude-mut.check +++ b/tests/neg-custom-args/captures/scope-extrude-mut.check @@ -1,9 +1,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrude-mut.scala:9:8 ------------------------------ 9 | a = a1 // error | ^^ - | Found: A^{a1.rd} - | Required: A^ + | Found: (a1 : A^) + | Required: A^² | - | where: ^ refers to a fresh root capability in the type of variable a + | where: ^ refers to a fresh root capability created in value a1 when constructing mutable A + | ^² refers to a fresh root capability in the type of variable a | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/sep-curried-redux.scala b/tests/neg-custom-args/captures/sep-curried-redux.scala new file mode 100644 index 000000000000..31b349b09f94 --- /dev/null +++ b/tests/neg-custom-args/captures/sep-curried-redux.scala @@ -0,0 +1,18 @@ +import language.experimental.captureChecking +import caps.* + +class Ref[T](init: T) extends Mutable: + private var value: T = init + def get: T = value + mut def set(newValue: T): Unit = value = newValue + +// a library function that assumes that a and b MUST BE separate +def swap[T](a: Ref[Int]^, b: Ref[Int]^): Unit = ??? + +def test4(): Unit = + val a: Ref[Int]^ = Ref(0) + val foo: (x: Ref[Int]^) -> (y: Ref[Int]^) ->{x} Unit = + x => y => swap(x, y) + val f = foo(a) + f(a) // error + diff --git a/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala b/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala new file mode 100644 index 000000000000..f694699fded1 --- /dev/null +++ b/tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala @@ -0,0 +1,22 @@ +package collection +trait IterableOps[+A, +CC[_], +C] extends Any: + def sizeCompare(size: Int): Int + +object IterableOps: + + type AnyConstr[X] = Any + + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]^) extends AnyVal: + this: SizeCompareOps^{it} => + /** Tests if the size of the collection is less than some value. */ + @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 + /** Tests if the size of the collection is less than or equal to some value. */ + @inline def <=(size: Int): Boolean = it.sizeCompare(size) <= 0 + /** Tests if the size of the collection is equal to some value. */ + @inline def ==(size: Int): Boolean = it.sizeCompare(size) == 0 + /** Tests if the size of the collection is not equal to some value. */ + @inline def !=(size: Int): Boolean = it.sizeCompare(size) != 0 + /** Tests if the size of the collection is greater than or equal to some value. */ + @inline def >=(size: Int): Boolean = it.sizeCompare(size) >= 0 + /** Tests if the size of the collection is greater than some value. */ + @inline def >(size: Int): Boolean = it.sizeCompare(size) > 0