Skip to content

Preparations to enable access kind inference. #23321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/CCState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@ class CCState:

// ------ Iteration count of capture checking run

private var iterCount = 1
private var iterCount = 0

def iterationId = iterCount

def nextIteration[T](op: => T): T =
iterCount += 1
try op finally iterCount -= 1

def start(): Unit =
iterCount = 1

// ------ Global counters -----------------------

/** Next CaptureSet.Var id */
Expand Down
15 changes: 11 additions & 4 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down Expand Up @@ -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)
Expand Down
53 changes: 40 additions & 13 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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 --------------------------------------------------

Expand Down Expand Up @@ -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)

Expand Down
15 changes: 12 additions & 3 deletions scala2-library-cc/src/scala/collection/Iterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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`.
*
Expand Down Expand Up @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import language.experimental.captureChecking
import language.`3.7` // no separation checking, TODO enable
import caps.*

def test[C^] =
Expand Down
3 changes: 2 additions & 1 deletion tests/neg-custom-args/captures/capture-vars-subtyping2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions tests/neg-custom-args/captures/lazyref.check
Original file line number Diff line number Diff line change
Expand Up @@ -39,46 +39,46 @@
| ^^^^
| 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 ------------------------------------------------------------
26 | if cap1 == cap2 // error: separation failure // error: separation failure
| ^^^^
| 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 -----------------------------------------------------------
26 | if cap1 == cap2 // error: separation failure // error: separation failure
| ^^^^
| 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 -----------------------------------------------------------
27 | then ref1 // error: separation failure
| ^^^^
| 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 -----------------------------------------------------------
28 | else ref2) // error: separation failure
| ^^^^
| 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 ------------------------------------------------------------
29 | .map(g) // error: separation failure
| ^
| 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
7 changes: 4 additions & 3 deletions tests/neg-custom-args/captures/scope-extrude-mut.check
Original file line number Diff line number Diff line change
@@ -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`
18 changes: 18 additions & 0 deletions tests/neg-custom-args/captures/sep-curried-redux.scala
Original file line number Diff line number Diff line change
@@ -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

22 changes: 22 additions & 0 deletions tests/pending/pos-custom-args/captures/SizeCompareOps-redux.scala
Original file line number Diff line number Diff line change
@@ -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
Loading