Skip to content

Commit 650874b

Browse files
committed
Introduce mutability qualifiers
1 parent 4344f05 commit 650874b

File tree

6 files changed

+164
-5
lines changed

6 files changed

+164
-5
lines changed

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,12 @@ object Capabilities:
330330
final def isExclusive(using Context): Boolean =
331331
!isReadOnly && (isTerminalCapability || captureSetOfInfo.isExclusive)
332332

333+
/** Similar to isExlusive, but also includes capabilties with capture
334+
* set variables in their info whose status is still open.
335+
*/
336+
final def maybeExclusive(using Context): Boolean =
337+
!isReadOnly && (isTerminalCapability || captureSetOfInfo.maybeExclusive)
338+
333339
final def isWellformed(using Context): Boolean = this match
334340
case self: CoreCapability => self.isTrackableRef
335341
case _ => true

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,24 @@ import Capabilities.*
4444
*/
4545
sealed abstract class CaptureSet extends Showable:
4646
import CaptureSet.*
47+
import Mutability.*
4748

4849
/** The elements of this capture set. For capture variables,
4950
* the elements known so far.
5051
*/
5152
def elems: Refs
5253

54+
protected var myMut : Mutability = Ignored
55+
56+
/** The access kind of this CaptureSet. */
57+
def mutability(using Context): Mutability = myMut
58+
59+
def mutability_=(x: Mutability): Unit =
60+
myMut = x
61+
62+
/** Mark this capture set as belonging to a Mutable type. */
63+
def setMutable()(using Context): Unit
64+
5365
/** Is this capture set constant (i.e. not an unsolved capture variable)?
5466
* Solved capture variables count as constant.
5567
*/
@@ -127,6 +139,13 @@ sealed abstract class CaptureSet extends Showable:
127139
final def isExclusive(using Context): Boolean =
128140
elems.exists(_.isExclusive)
129141

142+
/** Similar to isExlusive, but also includes capture set variables
143+
* with unknown status.
144+
*/
145+
final def maybeExclusive(using Context): Boolean = reporting.trace(i"mabe exclusive $this"):
146+
if isConst then elems.exists(_.maybeExclusive)
147+
else mutability != ReadOnly
148+
130149
final def keepAlways: Boolean = this.isInstanceOf[EmptyWithProvenance]
131150

132151
def failWith(fail: TypeComparer.ErrorNote)(using Context): false =
@@ -164,6 +183,9 @@ sealed abstract class CaptureSet extends Showable:
164183
// through this method.
165184
newElems.forall(tryInclude(_, origin))
166185

186+
protected def mutableToReader(origin: CaptureSet)(using Context): Boolean =
187+
if mutability == Mutable then toReader() else true
188+
167189
/** Add an element to this capture set, assuming it is not already accounted for,
168190
* and omitting any mapping or filtering.
169191
*
@@ -188,6 +210,8 @@ sealed abstract class CaptureSet extends Showable:
188210
*/
189211
protected def addThisElem(elem: Capability)(using Context, VarState): Boolean
190212

213+
protected def toReader()(using Context): Boolean
214+
191215
protected def addIfHiddenOrFail(elem: Capability)(using ctx: Context, vs: VarState): Boolean =
192216
elems.exists(_.maxSubsumes(elem, canAddHidden = true))
193217
|| failWith(IncludeFailure(this, elem))
@@ -258,7 +282,12 @@ sealed abstract class CaptureSet extends Showable:
258282

259283
/** The subcapturing test, using a given VarState */
260284
final def subCaptures(that: CaptureSet)(using ctx: Context, vs: VarState = VarState()): Boolean =
261-
if that.tryInclude(elems, this) then
285+
val this1 = this.adaptMutability(that)
286+
if this1 == null then false
287+
else if this1 ne this then
288+
capt.println(i"WIDEN ro $this with ${this.mutability} <:< $that with ${that.mutability} to $this1")
289+
this1.subCaptures(that, vs)
290+
else if that.tryInclude(elems, this) then
262291
addDependent(that)
263292
else
264293
varState.rollBack()
@@ -271,6 +300,14 @@ sealed abstract class CaptureSet extends Showable:
271300
this.subCaptures(that, VarState.Separate)
272301
&& that.subCaptures(this, VarState.Separate)
273302

303+
def adaptMutability(that: CaptureSet)(using Context): CaptureSet | Null =
304+
val m1 = this.mutability
305+
val m2 = that.mutability
306+
if m1 == Mutable && m2 == Reader then this.readOnly
307+
else if m1 == Reader && m2 == Mutable then
308+
if that.toReader() then this else null
309+
else this
310+
274311
/** The smallest capture set (via <:<) that is a superset of both
275312
* `this` and `that`
276313
*/
@@ -372,7 +409,10 @@ sealed abstract class CaptureSet extends Showable:
372409

373410
def maybe(using Context): CaptureSet = map(MaybeMap())
374411

375-
def readOnly(using Context): CaptureSet = map(ReadOnlyMap())
412+
def readOnly(using Context): CaptureSet =
413+
val res = map(ReadOnlyMap())
414+
if mutability != Ignored then res.mutability = Reader
415+
res
376416

377417
/** A bad root `elem` is inadmissible as a member of this set. What is a bad roots depends
378418
* on the value of `rootLimit`.
@@ -445,6 +485,25 @@ object CaptureSet:
445485
type Vars = SimpleIdentitySet[Var]
446486
type Deps = SimpleIdentitySet[CaptureSet]
447487

488+
enum Mutability:
489+
case Mutable, Reader, Ignored
490+
491+
def | (that: Mutability): Mutability =
492+
if this == that then this
493+
else if this == Ignored || that == Ignored then Ignored
494+
else if this == Reader || that == Reader then Reader
495+
else Mutable
496+
497+
def & (that: Mutability): Mutability =
498+
if this == that then this
499+
else if this == Ignored then that
500+
else if that == Ignored then this
501+
else if this == Reader then that
502+
else this
503+
504+
end Mutability
505+
import Mutability.*
506+
448507
/** If set to `true`, capture stack traces that tell us where sets are created */
449508
private final val debugSets = false
450509

@@ -496,6 +555,8 @@ object CaptureSet:
496555
false
497556
}
498557

558+
def toReader()(using Context) = false
559+
499560
def addDependent(cs: CaptureSet)(using Context, VarState) = true
500561

501562
def upperApprox(origin: CaptureSet)(using Context): CaptureSet = this
@@ -506,6 +567,17 @@ object CaptureSet:
506567

507568
def owner = NoSymbol
508569

570+
private var isComplete = true
571+
572+
def setMutable()(using Context): Unit =
573+
isComplete = false // delay computation of Mutability status
574+
575+
override def mutability(using Context): Mutability =
576+
if !isComplete then
577+
myMut = if maybeExclusive then Mutable else Reader
578+
isComplete = true
579+
myMut
580+
509581
override def toString = elems.toString
510582
end Const
511583

@@ -524,6 +596,7 @@ object CaptureSet:
524596
object Fluid extends Const(emptyRefs):
525597
override def isAlwaysEmpty(using Context) = false
526598
override def addThisElem(elem: Capability)(using Context, VarState) = true
599+
override def toReader()(using Context) = true
527600
override def accountsFor(x: Capability)(using Context)(using VarState): Boolean = true
528601
override def mightAccountFor(x: Capability)(using Context): Boolean = true
529602
override def toString = "<fluid>"
@@ -563,6 +636,9 @@ object CaptureSet:
563636
*/
564637
var deps: Deps = SimpleIdentitySet.empty
565638

639+
def setMutable()(using Context): Unit =
640+
mutability = Mutable
641+
566642
def isConst(using Context) = solved >= ccState.iterationId
567643
def isAlwaysEmpty(using Context) = isConst && elems.isEmpty
568644
def isProvisionallySolved(using Context): Boolean = solved > 0 && solved != Int.MaxValue
@@ -640,6 +716,13 @@ object CaptureSet:
640716
case note: IncludeFailure => note.addToTrace(this)
641717
res
642718

719+
final def toReader()(using Context) =
720+
if isConst then false // TODO add error note when failing?
721+
else
722+
mutability = Reader
723+
TypeComparer.logUndoAction(() => mutability = Mutable)
724+
deps.forall(_.mutableToReader(this))
725+
643726
private def isPartOf(binder: Type)(using Context): Boolean =
644727
val find = new TypeAccumulator[Boolean]:
645728
def apply(b: Boolean, t: Type) =
@@ -744,6 +827,8 @@ object CaptureSet:
744827
def markSolved(provisional: Boolean)(using Context): Unit =
745828
solved = if provisional then ccState.iterationId else Int.MaxValue
746829
deps.foreach(_.propagateSolved(provisional))
830+
if mutability == Mutable && !maybeExclusive then mutability = Reader
831+
747832

748833
var skippedMaps: Set[TypeMap] = Set.empty
749834

@@ -803,8 +888,14 @@ object CaptureSet:
803888
/** The variable from which this variable is derived */
804889
def source: Var
805890

891+
mutability = source.mutability
892+
806893
addAsDependentTo(source)
807894

895+
override def mutableToReader(origin: CaptureSet)(using Context): Boolean =
896+
super.mutableToReader(origin)
897+
&& ((origin eq source) || source.mutableToReader(this))
898+
808899
override def propagateSolved(provisional: Boolean)(using Context) =
809900
if source.isConst && !isConst then markSolved(provisional)
810901

@@ -904,6 +995,7 @@ object CaptureSet:
904995
extends Var(initialElems = cs1.elems ++ cs2.elems):
905996
addAsDependentTo(cs1)
906997
addAsDependentTo(cs2)
998+
mutability = cs1.mutability | cs2.mutability
907999

9081000
override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean =
9091001
if accountsFor(elem) then true
@@ -918,6 +1010,15 @@ object CaptureSet:
9181010
else res
9191011
else res
9201012

1013+
override def mutableToReader(origin: CaptureSet)(using Context): Boolean =
1014+
super.mutableToReader(origin)
1015+
&& {
1016+
if (origin eq cs1) || (origin eq cs2) then true
1017+
else if cs1.isConst && cs1.mutability == Mutable then cs2.mutableToReader(this)
1018+
else if cs2.isConst && cs2.mutability == Mutable then cs1.mutableToReader(this)
1019+
else true
1020+
}
1021+
9211022
override def propagateSolved(provisional: Boolean)(using Context) =
9221023
if cs1.isConst && cs2.isConst && !isConst then markSolved(provisional)
9231024
end Union
@@ -928,6 +1029,7 @@ object CaptureSet:
9281029
addAsDependentTo(cs2)
9291030
deps += cs1
9301031
deps += cs2
1032+
mutability = cs1.mutability & cs2.mutability
9311033

9321034
override def tryInclude(elem: Capability, origin: CaptureSet)(using Context, VarState): Boolean =
9331035
val inIntersection =
@@ -940,6 +1042,11 @@ object CaptureSet:
9401042
&& ((origin eq cs1) || cs1.tryInclude(elem, this))
9411043
&& ((origin eq cs2) || cs2.tryInclude(elem, this))
9421044

1045+
override def mutableToReader(origin: CaptureSet)(using Context): Boolean =
1046+
super.mutableToReader(origin)
1047+
&& ((origin eq cs1) || cs1.mutableToReader(this))
1048+
&& ((origin eq cs2) || cs2.mutableToReader(this))
1049+
9431050
override def computeApprox(origin: CaptureSet)(using Context): CaptureSet =
9441051
if (origin eq cs1) || (origin eq cs2) then
9451052
// it's a combination of origin with some other set, so not a superset of `origin`,

compiler/src/dotty/tools/dotc/cc/CapturingType.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ object CapturingType:
3939
case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed =>
4040
apply(parent1, refs ++ refs1, boxed)
4141
case _ =>
42+
if parent.derivesFromMutable then refs.setMutable()
4243
AnnotatedType(parent, CaptureAnnotation(refs, boxed)(defn.RetainsAnnot))
4344

4445
/** An extractor for CapturingTypes. Capturing types are recognized if
@@ -84,4 +85,9 @@ object CapturingType:
8485

8586
end CapturingType
8687

88+
object CapturingOrRetainsType:
89+
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
90+
if ctx.mode.is(Mode.IgnoreCaptures) then None
91+
else CapturingType.decomposeCapturingType(tp, alsoRetains = true)
92+
8793

compiler/src/dotty/tools/dotc/cc/ccConfig.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ object ccConfig:
5555
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`)
5656

5757
/** Not used currently. Handy for trying out new features */
58-
def newScheme(using Context): Boolean =
59-
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
58+
def newScheme(using ctx: Context): Boolean =
59+
ctx.settings.XdropComments.value
6060

6161
end ccConfig

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import scala.annotation.switch
1717
import config.{Config, Feature}
1818
import ast.tpd
1919
import cc.*
20+
import CaptureSet.Mutability
2021
import Capabilities.*
2122

2223
class PlainPrinter(_ctx: Context) extends Printer {
@@ -173,7 +174,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
173174
val core: Text =
174175
if !cs.isConst && cs.elems.isEmpty then "?"
175176
else "{" ~ Text(cs.processElems(_.toList.map(toTextCapability)), ", ") ~ "}"
176-
// ~ Str("?").provided(!cs.isConst)
177+
~ Str(".reader").provided(ccVerbose && cs.mutability == Mutability.Reader)
178+
~ Str("?").provided(ccVerbose && !cs.isConst)
179+
~ Str(s"#${cs.asVar.id}").provided(showUniqueIds && !cs.isConst)
177180
core ~ cs.optionalInfo
178181

179182
private def toTextRetainedElem(ref: Type): Text = ref match
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import caps.Mutable
2+
import caps.cap
3+
import language.`3.7` // fails separation checking right now
4+
5+
trait Rdr[T]:
6+
def get: T
7+
8+
class Ref[T](init: T) extends Rdr[T], Mutable:
9+
private var current = init
10+
def get: T = current
11+
mut def put(x: T): Unit = current = x
12+
13+
abstract class IMatrix:
14+
def apply(i: Int, j: Int): Double
15+
16+
class Matrix(nrows: Int, ncols: Int) extends IMatrix, Mutable:
17+
val arr = Array.fill(nrows, ncols)(0.0)
18+
def apply(i: Int, j: Int): Double = arr(i)(j)
19+
mut def update(i: Int, j: Int, x: Double): Unit = arr(i)(j) = x
20+
21+
22+
def mul(x: Matrix, y: Matrix, z: Matrix^): Unit = ???
23+
def mul1(x: Matrix^{cap.rd}, y: Matrix^{cap.rd}, z: Matrix^): Unit = ???
24+
25+
def Test(c: Object^): Unit =
26+
val m1 = Matrix(10, 10)
27+
val m2 = Matrix(10, 10)
28+
mul(m1, m2, m2) // error: will fail separation checking
29+
mul(m1, m1, m2) // should be ok
30+
31+
mul1(m1, m2, m2) // error: will fail separation checking
32+
mul(m1, m1, m2) // should be ok
33+
34+
def f2(): Matrix^ = Matrix(10, 10)
35+
36+
val i1: IMatrix^{cap.rd} = m1
37+
val i2: IMatrix^{cap.rd} = f2()

0 commit comments

Comments
 (0)