Skip to content

Commit 54de369

Browse files
committed
Revert "Represent read-only with a classifier"
This reverts commit 9abbb03. This reverts commit 0a0e666. This reverts commit 67ba9b5. Read-only cannot be a classifier anymore, since it would overlap with Unscoped, but neither subsumes the other.
1 parent 1d48e30 commit 54de369

File tree

8 files changed

+59
-27
lines changed

8 files changed

+59
-27
lines changed

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

Lines changed: 31 additions & 17 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: CoreCapability | RootCapability | Reach | Restricted)
100+
extends DerivedCapability
101+
93102
/** The restricted capability `x.only[C]`. We have {x.only[C]} <: {x}.
94103
*
95-
* Restricted capabilities cannot wrap maybe capabilities
104+
* Restricted capabilities cannot wrap maybe capabilities or read-only capabilities
96105
* but they can wrap reach capabilities. We have
97106
* (x?).restrict[T] = (x.restrict[T])?
98107
* (x.rd).restrict[T] = (x.restrict[T]).rd
99108
*/
100109
case class Restricted(underlying: CoreCapability | RootCapability | Reach, cls: ClassSymbol)
101110
extends DerivedCapability
102111

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 restricted capabilities or maybe capabilities.
119+
* Reach capabilities cannot wrap read-only 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 def readOnly(using Context) = ReadOnly(this)
135+
override val readOnly = 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
@@ -347,21 +347,23 @@ object Capabilities:
347347
case self: Maybe => self
348348
case _ => cached(Maybe(this))
349349

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

355-
def restrict(cls: ClassSymbol)(using Context): Restricted | Maybe = this match
355+
def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match
356356
case Maybe(ref1) => Maybe(ref1.restrict(cls))
357+
case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted])
357358
case self @ Restricted(ref1, prevCls) =>
358359
val combinedCls = leastClassifier(prevCls, cls)
359360
if combinedCls == prevCls then self
360361
else cached(Restricted(ref1, combinedCls))
361362
case self: (CoreCapability | RootCapability | Reach) => cached(Restricted(self, cls))
362363

363-
def reach: Reach | Restricted | Maybe = this match
364+
def reach: Reach | Restricted | ReadOnly | Maybe = this match
364365
case Maybe(ref1) => Maybe(ref1.reach)
366+
case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted])
365367
case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls)
366368
case self: Reach => self
367369
case self: ObjectCapability => cached(Reach(self))
@@ -382,6 +384,7 @@ object Capabilities:
382384
*/
383385
final def classifier(using Context): Symbol = this match
384386
case Restricted(_, cls) => cls
387+
case ReadOnly(ref1) => ref1.classifier
385388
case Maybe(ref1) => ref1.classifier
386389
case self: FreshCap => self.hiddenSet.classifier
387390
case _ => NoSymbol
@@ -400,9 +403,10 @@ object Capabilities:
400403
case Maybe(ref1) => ref1.stripReadOnly.maybe
401404
case _ => this
402405

403-
/** Drop restrictions with class `cls` or a superclass of `cls` */
406+
/** Drop restrictions with clss `cls` or a superclass of `cls` */
404407
final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match
405408
case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1
409+
case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly
406410
case Maybe(ref1) => ref1.stripRestricted(cls).maybe
407411
case _ => this
408412

@@ -411,6 +415,7 @@ object Capabilities:
411415

412416
final def stripReach(using Context): Capability = this match
413417
case Reach(ref1) => ref1
418+
case ReadOnly(ref1) => ref1.stripReach.readOnly
414419
case Restricted(ref1, cls) => ref1.stripReach.restrict(cls)
415420
case Maybe(ref1) => ref1.stripReach.maybe
416421
case _ => this
@@ -596,6 +601,7 @@ object Capabilities:
596601
def computeHiddenSet(f: Refs => Refs)(using Context): Refs = this match
597602
case self: FreshCap => f(self.hiddenSet.elems)
598603
case Restricted(elem1, cls) => elem1.computeHiddenSet(f).map(_.restrict(cls))
604+
case ReadOnly(elem1) => elem1.computeHiddenSet(f).map(_.readOnly)
599605
case _ => emptyRefs
600606

601607
/** The transitive classifiers of this capability. */
@@ -613,6 +619,8 @@ object Capabilities:
613619
assert(cls != defn.AnyClass)
614620
if cls == defn.NothingClass then ClassifiedAs(Nil)
615621
else ClassifiedAs(cls :: Nil)
622+
case ReadOnly(ref1) =>
623+
ref1.transClassifiers
616624
case Maybe(ref1) =>
617625
ref1.transClassifiers
618626
case Reach(_) =>
@@ -636,6 +644,8 @@ object Capabilities:
636644
case Restricted(_, cls1) =>
637645
assert(cls != defn.AnyClass)
638646
cls1.isSubClass(cls)
647+
case ReadOnly(ref1) =>
648+
ref1.tryClassifyAs(cls)
639649
case Maybe(ref1) =>
640650
ref1.tryClassifyAs(cls)
641651
case Reach(_) =>
@@ -656,6 +666,7 @@ object Capabilities:
656666
cs.forall(c => leastClassifier(c, cls) == defn.NothingClass)
657667
case _ => false
658668
isEmpty || ref1.isKnownEmpty
669+
case ReadOnly(ref1) => ref1.isKnownEmpty
659670
case Maybe(ref1) => ref1.isKnownEmpty
660671
case _ => false
661672

@@ -716,6 +727,7 @@ object Capabilities:
716727
case _ => false
717728
|| viaInfo(y.info)(subsumingRefs(this, _))
718729
case Maybe(y1) => this.stripMaybe.subsumes(y1)
730+
case ReadOnly(y1) => this.stripReadOnly.subsumes(y1)
719731
case Restricted(y1, cls) => this.stripRestricted(cls).subsumes(y1)
720732
case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) =>
721733
// The upper and lower bounds don't have to be in the form of `CapSet^{...}`.
@@ -799,6 +811,7 @@ object Capabilities:
799811
y.isKnownClassifiedAs(cls) && x1.maxSubsumes(y, canAddHidden)
800812
case _ =>
801813
y match
814+
case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
802815
case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden)
803816
case _ => false
804817

@@ -886,6 +899,7 @@ object Capabilities:
886899
case c: DerivedCapability =>
887900
val c1 = c.underlying.toType
888901
c match
902+
case _: ReadOnly => ReadOnlyCapability(c1)
889903
case Restricted(_, cls) => OnlyCapability(c1, cls)
890904
case _: Reach => ReachCapability(c1)
891905
case _: Maybe => MaybeCapability(c1)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,8 @@ object CaptureSet:
16251625
case Restricted(c1, cls) =>
16261626
if cls == defn.NothingClass then CaptureSet.empty
16271627
else c1.captureSetOfInfo.restrict(cls) // todo: should we simplify using subsumption here?
1628+
case ReadOnly(c1) =>
1629+
c1.captureSetOfInfo.readOnly
16281630
case Maybe(c1) =>
16291631
c1.captureSetOfInfo.maybe
16301632
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 match
183+
else newElem.stripRestricted.stripReadOnly 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.coversFresh(coreRef)) then
223+
if refs2.exists(_.stripRestricted.stripReadOnly.coversFresh(coreRef)) then
224224
acc += coreRef
225225
acc
226226
assert(refs.forall(_.isTerminalCapability))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
955955
case Nil =>
956956
recur(cls.baseClasses.filter(_.isClassifiedCapabilityClass).distinct)
957957
if cls.derivesFrom(defn.Caps_SharedCapability) && cls.derivesFrom(defn.Caps_Mutable) then
958-
report.error(em"$cls cannot inheit from both SharaedCapability and Mutable", cls.srcPos)
958+
report.error(em"$cls cannot inheit from both SharedCapability and Mutable", cls.srcPos)
959959

960960
// ------ Checks to run after main capture checking --------------------------
961961

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6402,6 +6402,11 @@ 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)
64056410
case Maybe(c1) =>
64066411
assert(!deep)
64076412
mapCapability(c1) match

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@
4545
-- Error: tests/neg-custom-args/captures/check-inferred.scala:49:15 ----------------------------------------------------
4646
49 | private val y = ??? : (() => A^{cap.only[caps.Read]}) // error
4747
| ^
48-
| value y needs an explicit type because it captures a root capability in its type () => test.A^{cap.rd}.
49-
| Fields capturing a root capability need to be given an explicit type unless the capability is already
50-
| subsumed by the computed capability of the enclosing class.
48+
| value y needs an explicit type because it captures a root capability in its type () => test.A^{cap.only[Read]}.
49+
| Fields capturing a root capability need to be given an explicit type unless the capability is already
50+
| subsumed by the computed capability of the enclosing class.
5151
|
52-
| where: => refers to a fresh root capability in the type of value y
53-
| cap is a fresh root capability created in value y
52+
| where: => refers to a fresh root capability in the type of value y
53+
| cap is a fresh root capability created in value y
5454
-- Error: tests/neg-custom-args/captures/check-inferred.scala:29:21 ----------------------------------------------------
5555
29 | private val count = Ref() // error
5656
| ^
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
-- Error: tests/neg-custom-args/captures/classified-inheritance2.scala:4:6 ---------------------------------------------
22
4 |class Logger extends SharedCapability, Mutable: // error (1) does this make sense?
33
| ^
4-
| class Logger cannot inheit from both SharaedCapability and Mutable
4+
| class Logger cannot inheit from both SharedCapability and Mutable
5+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/classified-inheritance2.scala:12:27 ----------------------
6+
12 | val t: Logger^{} = Logger() // error
7+
| ^^^^^^^^
8+
|Found: Logger^{cap.rd}
9+
|Required: Logger^{}
10+
|
11+
|Note that capability cap.rd is not included in capture set {}.
12+
|
13+
|where: cap is a fresh root capability classified as SharedCapability created in value t when constructing instance Logger
14+
|
15+
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/classified-inheritance2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ def onlyShared(x: Object^{cap.only[SharedCapability]}): Unit = ()
99

1010
def main(): Unit =
1111
onlyShared(Logger()) // even if we allow (1), why would this type check?
12-
val t: Logger^{} = Logger() // and this type checks too, thus the above line I guess
12+
val t: Logger^{} = Logger() // error

0 commit comments

Comments
 (0)