Skip to content

Commit b1a5500

Browse files
authored
Represent read-only with a classifier (#24295)
`x.rd` is now a shorthand for `x.only[caps.Read]`.
2 parents c9c7187 + a5b4a51 commit b1a5500

File tree

7 files changed

+31
-47
lines changed

7 files changed

+31
-47
lines changed

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

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -90,33 +90,33 @@ object Capabilities:
9090
*/
9191
case class Maybe(underlying: Capability) extends DerivedCapability
9292

93-
/** The readonly capability `x.rd`. We have {x.rd} <: {x}.
94-
*
95-
* Read-only capabilities cannot wrap maybe capabilities
96-
* but they can wrap reach capabilities. We have
97-
* (x?).readOnly = (x.rd)?
98-
*/
99-
case class ReadOnly(underlying: ObjectCapability | RootCapability | Reach | Restricted)
100-
extends DerivedCapability
101-
10293
/** The restricted capability `x.only[C]`. We have {x.only[C]} <: {x}.
10394
*
104-
* Restricted capabilities cannot wrap maybe capabilities or read-only capabilities
95+
* Restricted capabilities cannot wrap maybe capabilities
10596
* but they can wrap reach capabilities. We have
10697
* (x?).restrict[T] = (x.restrict[T])?
10798
* (x.rd).restrict[T] = (x.restrict[T]).rd
10899
*/
109-
case class Restricted(underlying: ObjectCapability | RootCapability | Reach, cls: ClassSymbol)
100+
case class Restricted(underlying: CoreCapability | RootCapability | Reach, cls: ClassSymbol)
110101
extends DerivedCapability
111102

103+
/** An extractor for the read-only capability `x.rd`. `x.rd` is represented as
104+
* `c.only[caps.Read]`.
105+
*/
106+
object ReadOnly:
107+
def apply(underlying: CoreCapability | RootCapability | Reach | Restricted)(using Context): Restricted =
108+
Restricted(underlying.stripRestricted.asInstanceOf, defn.Caps_Read)
109+
def unapply(ref: Restricted)(using Context): Option[CoreCapability | RootCapability | Reach] =
110+
if ref.cls == defn.Caps_Read then Some(ref.underlying) else None
111+
112112
/** If `x` is a capability, its reach capability `x*`. `x*` stands for all
113113
* capabilities reachable through `x`.
114114
* We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x`
115115
* is the union of all capture sets that appear in covariant position in the
116116
* type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}`
117117
* are unrelated.
118118
*
119-
* Reach capabilities cannot wrap read-only capabilities or maybe capabilities.
119+
* Reach capabilities cannot wrap restricted capabilities or maybe capabilities.
120120
* We have
121121
* (x?).reach = (x.reach)?
122122
* (x.rd).reach = (x.reach).rd
@@ -132,7 +132,7 @@ object Capabilities:
132132
object GlobalCap extends RootCapability:
133133
def descr(using Context) = "the universal root capability"
134134
override val maybe = Maybe(this)
135-
override val readOnly = ReadOnly(this)
135+
override def readOnly(using Context) = ReadOnly(this)
136136
override def restrict(cls: ClassSymbol)(using Context) = Restricted(this, cls)
137137
override def reach = unsupported("cap.reach")
138138
override def singletonCaptureSet(using Context) = CaptureSet.universal
@@ -346,23 +346,21 @@ object Capabilities:
346346
case self: Maybe => self
347347
case _ => cached(Maybe(this))
348348

349-
def readOnly: ReadOnly | Maybe = this match
349+
def readOnly(using Context): Restricted | Maybe = this match
350350
case Maybe(ref1) => Maybe(ref1.readOnly)
351-
case self: ReadOnly => self
352-
case self: (ObjectCapability | RootCapability | Reach | Restricted) => cached(ReadOnly(self))
351+
case self @ ReadOnly(_) => self
352+
case self: (CoreCapability | RootCapability | Reach | Restricted) => cached(ReadOnly(self))
353353

354-
def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match
354+
def restrict(cls: ClassSymbol)(using Context): Restricted | Maybe = this match
355355
case Maybe(ref1) => Maybe(ref1.restrict(cls))
356-
case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted])
357356
case self @ Restricted(ref1, prevCls) =>
358357
val combinedCls = leastClassifier(prevCls, cls)
359358
if combinedCls == prevCls then self
360359
else cached(Restricted(ref1, combinedCls))
361-
case self: (ObjectCapability | RootCapability | Reach) => cached(Restricted(self, cls))
360+
case self: (CoreCapability | RootCapability | Reach) => cached(Restricted(self, cls))
362361

363-
def reach: Reach | Restricted | ReadOnly | Maybe = this match
362+
def reach: Reach | Restricted | Maybe = this match
364363
case Maybe(ref1) => Maybe(ref1.reach)
365-
case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted])
366364
case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls)
367365
case self: Reach => self
368366
case self: ObjectCapability => cached(Reach(self))
@@ -377,9 +375,9 @@ object Capabilities:
377375
case tp: SetCapability => tp.captureSetOfInfo.isReadOnly
378376
case _ => this ne stripReadOnly
379377

378+
/** The restriction of this capability or NoSymbol if there is none */
380379
final def restriction(using Context): Symbol = this match
381380
case Restricted(_, cls) => cls
382-
case ReadOnly(ref1) => ref1.restriction
383381
case Maybe(ref1) => ref1.restriction
384382
case _ => NoSymbol
385383

@@ -397,10 +395,9 @@ object Capabilities:
397395
case Maybe(ref1) => ref1.stripReadOnly.maybe
398396
case _ => this
399397

400-
/** Drop restrictions with clss `cls` or a superclass of `cls` */
398+
/** Drop restrictions with class `cls` or a superclass of `cls` */
401399
final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match
402400
case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1
403-
case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly
404401
case Maybe(ref1) => ref1.stripRestricted(cls).maybe
405402
case _ => this
406403

@@ -409,7 +406,6 @@ object Capabilities:
409406

410407
final def stripReach(using Context): Capability = this match
411408
case Reach(ref1) => ref1
412-
case ReadOnly(ref1) => ref1.stripReach.readOnly
413409
case Restricted(ref1, cls) => ref1.stripReach.restrict(cls)
414410
case Maybe(ref1) => ref1.stripReach.maybe
415411
case _ => this
@@ -591,7 +587,6 @@ object Capabilities:
591587
def computeHiddenSet(f: Refs => Refs)(using Context): Refs = this match
592588
case self: FreshCap => f(self.hiddenSet.elems)
593589
case Restricted(elem1, cls) => elem1.computeHiddenSet(f).map(_.restrict(cls))
594-
case ReadOnly(elem1) => elem1.computeHiddenSet(f).map(_.readOnly)
595590
case _ => emptyRefs
596591

597592
/** The transitive classifiers of this capability. */
@@ -609,8 +604,6 @@ object Capabilities:
609604
assert(cls != defn.AnyClass)
610605
if cls == defn.NothingClass then ClassifiedAs(Nil)
611606
else ClassifiedAs(cls :: Nil)
612-
case ReadOnly(ref1) =>
613-
ref1.transClassifiers
614607
case Maybe(ref1) =>
615608
ref1.transClassifiers
616609
case Reach(_) =>
@@ -634,8 +627,6 @@ object Capabilities:
634627
case Restricted(_, cls1) =>
635628
assert(cls != defn.AnyClass)
636629
cls1.isSubClass(cls)
637-
case ReadOnly(ref1) =>
638-
ref1.tryClassifyAs(cls)
639630
case Maybe(ref1) =>
640631
ref1.tryClassifyAs(cls)
641632
case Reach(_) =>
@@ -656,7 +647,6 @@ object Capabilities:
656647
cs.forall(c => leastClassifier(c, cls) == defn.NothingClass)
657648
case _ => false
658649
isEmpty || ref1.isKnownEmpty
659-
case ReadOnly(ref1) => ref1.isKnownEmpty
660650
case Maybe(ref1) => ref1.isKnownEmpty
661651
case _ => false
662652

@@ -717,7 +707,6 @@ object Capabilities:
717707
case _ => false
718708
|| viaInfo(y.info)(subsumingRefs(this, _))
719709
case Maybe(y1) => this.stripMaybe.subsumes(y1)
720-
case ReadOnly(y1) => this.stripReadOnly.subsumes(y1)
721710
case Restricted(y1, cls) => this.stripRestricted(cls).subsumes(y1)
722711
case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) =>
723712
// The upper and lower bounds don't have to be in the form of `CapSet^{...}`.
@@ -801,7 +790,6 @@ object Capabilities:
801790
y.isKnownClassifiedAs(cls) && x1.maxSubsumes(y, canAddHidden)
802791
case _ =>
803792
y match
804-
case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
805793
case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden)
806794
case _ => false
807795

@@ -889,7 +877,6 @@ object Capabilities:
889877
case c: DerivedCapability =>
890878
val c1 = c.underlying.toType
891879
c match
892-
case _: ReadOnly => ReadOnlyCapability(c1)
893880
case Restricted(_, cls) => OnlyCapability(c1, cls)
894881
case _: Reach => ReachCapability(c1)
895882
case _: Maybe => MaybeCapability(c1)

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,8 +1615,6 @@ object CaptureSet:
16151615
case Restricted(c1, cls) =>
16161616
if cls == defn.NothingClass then CaptureSet.empty
16171617
else c1.captureSetOfInfo.restrict(cls) // todo: should we simplify using subsumption here?
1618-
case ReadOnly(c1) =>
1619-
c1.captureSetOfInfo.readOnly
16201618
case Maybe(c1) =>
16211619
c1.captureSetOfInfo.maybe
16221620
case c: RootCapability =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ object SepCheck:
180180
case newElem :: newElems1 =>
181181
if seen.contains(newElem) then
182182
recur(seen, acc, newElems1)
183-
else newElem.stripRestricted.stripReadOnly match
183+
else newElem.stripRestricted match
184184
case _: FreshCap if !newElem.isKnownClassifiedAs(defn.Caps_SharedCapability) =>
185185
val hiddens = if followHidden then newElem.hiddenSet.toList else Nil
186186
recur(seen + newElem, acc + newElem, hiddens ++ newElems1)
@@ -220,7 +220,7 @@ object SepCheck:
220220
refs1.foreach: ref =>
221221
if !ref.isReadOnly then
222222
val coreRef = ref.stripRestricted
223-
if refs2.exists(_.stripRestricted.stripReadOnly.coversFresh(coreRef)) then
223+
if refs2.exists(_.stripRestricted.coversFresh(coreRef)) then
224224
acc += coreRef
225225
acc
226226
assert(refs.forall(_.isTerminalCapability))

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6402,11 +6402,6 @@ object Types extends TypeUtils {
64026402
mapCapability(c1) match
64036403
case c2: Capability => c2.restrict(cls)
64046404
case (cs: CaptureSet, exact) => (cs.restrict(cls), exact)
6405-
case ReadOnly(c1) =>
6406-
assert(!deep)
6407-
mapCapability(c1) match
6408-
case c2: Capability => c2.readOnly
6409-
case (cs: CaptureSet, exact) => (cs.readOnly, exact)
64106405
case Maybe(c1) =>
64116406
assert(!deep)
64126407
mapCapability(c1) match

tests/neg-custom-args/captures/mutability.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,5 @@
115115
-- Error: tests/neg-custom-args/captures/mutability.scala:45:9 ---------------------------------------------------------
116116
45 | r6().x.set(33) // error
117117
| ^^^^^^^^^^
118-
| Cannot call update method set of Ref[Int]^{cap.rd}
119-
| since its capture set {cap.rd} is read-only.
118+
| Cannot call update method set of Ref[Int]^{r6*.rd}
119+
| since its capture set {r6*.rd} is read-only.

tests/neg-custom-args/captures/mutvars.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,5 @@
9494
-- Error: tests/neg-custom-args/captures/mutvars.scala:44:13 -----------------------------------------------------------
9595
44 | r6().x.fld = 33 // error
9696
| ^^^^^^^^^^^^^^^
97-
| Cannot assign to field fld of Ref[Int]^{cap.rd}
98-
| since its capture set {cap.rd} is read-only.
97+
| Cannot assign to field fld of Ref[Int]^{r6*.rd}
98+
| since its capture set {r6*.rd} is read-only.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import language.experimental.captureChecking
2+
class Ref extends caps.Mutable
3+
def f1[C1^](a: Ref^{C1}): Unit =
4+
val t = a

0 commit comments

Comments
 (0)