Skip to content

Commit bc5f15e

Browse files
authored
Add Unscoped classifier (#24470)
At the same time, drop Read classifier and the idea that .rd is a kind of restriction. That does not work anymore since `.rd` has to be combinable with classified capabilities like `Unscoped`.
2 parents 9056e42 + df8c615 commit bc5f15e

40 files changed

+763
-221
lines changed

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

Lines changed: 61 additions & 36 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
@@ -276,9 +276,10 @@ object Capabilities:
276276
* if separation checks are turned off).
277277
* @pre The capability's origin was not yet set.
278278
*/
279-
def setOrigin(freshOrigin: FreshCap | GlobalCap.type): Unit =
279+
def setOrigin(freshOrigin: FreshCap | GlobalCap.type): this.type =
280280
assert(myOrigin eq GlobalCap)
281281
myOrigin = freshOrigin
282+
this
282283

283284
/** If the current capability was created via a chain of `derivedResult` calls
284285
* from an original ResultCap `r`, that `r`. Otherwise `this`.
@@ -346,21 +347,23 @@ object Capabilities:
346347
case self: Maybe => self
347348
case _ => cached(Maybe(this))
348349

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

354-
def restrict(cls: ClassSymbol)(using Context): Restricted | Maybe = this match
355+
def restrict(cls: ClassSymbol)(using Context): Restricted | ReadOnly | Maybe = this match
355356
case Maybe(ref1) => Maybe(ref1.restrict(cls))
357+
case ReadOnly(ref1) => ReadOnly(ref1.restrict(cls).asInstanceOf[Restricted])
356358
case self @ Restricted(ref1, prevCls) =>
357359
val combinedCls = leastClassifier(prevCls, cls)
358360
if combinedCls == prevCls then self
359361
else cached(Restricted(ref1, combinedCls))
360362
case self: (CoreCapability | RootCapability | Reach) => cached(Restricted(self, cls))
361363

362-
def reach: Reach | Restricted | Maybe = this match
364+
def reach: Reach | Restricted | ReadOnly | Maybe = this match
363365
case Maybe(ref1) => Maybe(ref1.reach)
366+
case ReadOnly(ref1) => ReadOnly(ref1.reach.asInstanceOf[Reach | Restricted])
364367
case Restricted(ref1, cls) => Restricted(ref1.reach.asInstanceOf[Reach], cls)
365368
case self: Reach => self
366369
case self: ObjectCapability => cached(Reach(self))
@@ -381,6 +384,7 @@ object Capabilities:
381384
*/
382385
final def classifier(using Context): Symbol = this match
383386
case Restricted(_, cls) => cls
387+
case ReadOnly(ref1) => ref1.classifier
384388
case Maybe(ref1) => ref1.classifier
385389
case self: FreshCap => self.hiddenSet.classifier
386390
case _ => NoSymbol
@@ -399,9 +403,10 @@ object Capabilities:
399403
case Maybe(ref1) => ref1.stripReadOnly.maybe
400404
case _ => this
401405

402-
/** Drop restrictions with class `cls` or a superclass of `cls` */
406+
/** Drop restrictions with clss `cls` or a superclass of `cls` */
403407
final def stripRestricted(cls: ClassSymbol)(using Context): Capability = this match
404408
case Restricted(ref1, cls1) if cls.isSubClass(cls1) => ref1
409+
case ReadOnly(ref1) => ref1.stripRestricted(cls).readOnly
405410
case Maybe(ref1) => ref1.stripRestricted(cls).maybe
406411
case _ => this
407412

@@ -410,6 +415,7 @@ object Capabilities:
410415

411416
final def stripReach(using Context): Capability = this match
412417
case Reach(ref1) => ref1
418+
case ReadOnly(ref1) => ref1.stripReach.readOnly
413419
case Restricted(ref1, cls) => ref1.stripReach.restrict(cls)
414420
case Maybe(ref1) => ref1.stripReach.maybe
415421
case _ => this
@@ -506,35 +512,44 @@ object Capabilities:
506512

507513
final def isParamPath(using Context): Boolean = paramPathRoot.exists
508514

509-
final def ccOwner(using Context): Symbol = this match
515+
/** Compute ccOwner or (part of level owner).
516+
* @param mapUnscoped if true, return the nclosing toplevel class for FreshCaps
517+
* classified as Unscoped that don't have a prefix
518+
*/
519+
private def computeOwner(mapUnscoped: Boolean)(using Context): Symbol = this match
510520
case self: ThisType => self.cls
511-
case TermRef(prefix: Capability, _) => prefix.ccOwner
521+
case TermRef(prefix: Capability, _) => prefix.computeOwner(mapUnscoped)
512522
case self: NamedType => self.symbol
513-
case self: DerivedCapability => self.underlying.ccOwner
523+
case self: DerivedCapability => self.underlying.computeOwner(mapUnscoped)
514524
case self: FreshCap =>
515525
val setOwner = self.hiddenSet.owner
516526
self.prefix match
517527
case prefix: ThisType if setOwner.isTerm && setOwner.owner == prefix.cls =>
518528
setOwner
519-
case prefix: Capability => prefix.ccOwner
529+
case prefix: Capability => prefix.computeOwner(mapUnscoped)
530+
case NoPrefix if mapUnscoped && classifier.derivesFrom(defn.Caps_Unscoped) =>
531+
ctx.owner.topLevelClass
520532
case _ => setOwner
521533
case _ /* : GlobalCap | ResultCap | ParamRef */ => NoSymbol
522534

535+
final def ccOwner(using Context): Symbol = computeOwner(mapUnscoped = false)
536+
523537
final def visibility(using Context): Symbol = this match
524-
case self: FreshCap => adjustOwner(ccOwner)
538+
case self: FreshCap => adjustOwner(computeOwner(mapUnscoped = true))
525539
case _ =>
526-
val vis = ccOwner
540+
val vis = computeOwner(mapUnscoped = true)
527541
if vis.is(Param) then vis.owner else vis
528542

529543
/** The symbol that represents the level closest-enclosing ccOwner.
530544
* Symbols representing levels are
531545
* - class symbols, but not inner (non-static) module classes
532546
* - method symbols, but not accessors or constructors
547+
* For Unscoped FreshCaps the level owner is the top-level class.
533548
*/
534549
final def levelOwner(using Context): Symbol =
535-
adjustOwner(ccOwner)
550+
adjustOwner(computeOwner(mapUnscoped = true))
536551

537-
private def adjustOwner(owner: Symbol)(using Context): Symbol =
552+
final def adjustOwner(owner: Symbol)(using Context): Symbol =
538553
if !owner.exists
539554
|| owner.isClass && (!owner.is(Flags.Module) || owner.isStatic)
540555
|| owner.is(Flags.Method, butNot = Flags.Accessor)
@@ -593,6 +608,7 @@ object Capabilities:
593608
def computeHiddenSet(f: Refs => Refs)(using Context): Refs = this match
594609
case self: FreshCap => f(self.hiddenSet.elems)
595610
case Restricted(elem1, cls) => elem1.computeHiddenSet(f).map(_.restrict(cls))
611+
case ReadOnly(elem1) => elem1.computeHiddenSet(f).map(_.readOnly)
596612
case _ => emptyRefs
597613

598614
/** The transitive classifiers of this capability. */
@@ -610,6 +626,8 @@ object Capabilities:
610626
assert(cls != defn.AnyClass)
611627
if cls == defn.NothingClass then ClassifiedAs(Nil)
612628
else ClassifiedAs(cls :: Nil)
629+
case ReadOnly(ref1) =>
630+
ref1.transClassifiers
613631
case Maybe(ref1) =>
614632
ref1.transClassifiers
615633
case Reach(_) =>
@@ -633,6 +651,8 @@ object Capabilities:
633651
case Restricted(_, cls1) =>
634652
assert(cls != defn.AnyClass)
635653
cls1.isSubClass(cls)
654+
case ReadOnly(ref1) =>
655+
ref1.tryClassifyAs(cls)
636656
case Maybe(ref1) =>
637657
ref1.tryClassifyAs(cls)
638658
case Reach(_) =>
@@ -653,6 +673,7 @@ object Capabilities:
653673
cs.forall(c => leastClassifier(c, cls) == defn.NothingClass)
654674
case _ => false
655675
isEmpty || ref1.isKnownEmpty
676+
case ReadOnly(ref1) => ref1.isKnownEmpty
656677
case Maybe(ref1) => ref1.isKnownEmpty
657678
case _ => false
658679

@@ -713,6 +734,7 @@ object Capabilities:
713734
case _ => false
714735
|| viaInfo(y.info)(subsumingRefs(this, _))
715736
case Maybe(y1) => this.stripMaybe.subsumes(y1)
737+
case ReadOnly(y1) => this.stripReadOnly.subsumes(y1)
716738
case Restricted(y1, cls) => this.stripRestricted(cls).subsumes(y1)
717739
case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) =>
718740
// The upper and lower bounds don't have to be in the form of `CapSet^{...}`.
@@ -796,6 +818,7 @@ object Capabilities:
796818
y.isKnownClassifiedAs(cls) && x1.maxSubsumes(y, canAddHidden)
797819
case _ =>
798820
y match
821+
case ReadOnly(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
799822
case Restricted(y1, cls) => this.stripRestricted(cls).maxSubsumes(y1, canAddHidden)
800823
case _ => false
801824

@@ -883,6 +906,7 @@ object Capabilities:
883906
case c: DerivedCapability =>
884907
val c1 = c.underlying.toType
885908
c match
909+
case _: ReadOnly => ReadOnlyCapability(c1)
886910
case Restricted(_, cls) => OnlyCapability(c1, cls)
887911
case _: Reach => ReachCapability(c1)
888912
case _: Maybe => MaybeCapability(c1)
@@ -1168,7 +1192,7 @@ object Capabilities:
11681192
case _ =>
11691193
super.mapOver(t)
11701194

1171-
class ToResult(localResType: Type, mt: MethodicType, fail: Message => Unit)(using Context) extends CapMap:
1195+
class ToResult(localResType: Type, mt: MethodicType, sym: Symbol, fail: Message => Unit)(using Context) extends CapMap:
11721196

11731197
def apply(t: Type) = t match
11741198
case defn.FunctionNOf(args, res, contextual) if t.typeSymbol.name.isImpureFunction =>
@@ -1183,11 +1207,12 @@ object Capabilities:
11831207
override def mapCapability(c: Capability, deep: Boolean) = c match
11841208
case c: (FreshCap | GlobalCap.type) =>
11851209
if variance > 0 then
1186-
val res = ResultCap(mt)
11871210
c match
1188-
case c: FreshCap => res.setOrigin(c)
1189-
case _ =>
1190-
res
1211+
case c: FreshCap =>
1212+
if sym.isAnonymousFunction && c.classifier.derivesFrom(defn.Caps_Unscoped)
1213+
then c
1214+
else ResultCap(mt).setOrigin(c)
1215+
case _ => ResultCap(mt)
11911216
else
11921217
if variance == 0 then
11931218
fail(em"""$localResType captures the root capability `cap` in invariant position.
@@ -1227,8 +1252,8 @@ object Capabilities:
12271252
* variable bound by `mt`.
12281253
* Stop at function or method types since these have been mapped before.
12291254
*/
1230-
def toResult(tp: Type, mt: MethodicType, fail: Message => Unit)(using Context): Type =
1231-
ToResult(tp, mt, fail)(tp)
1255+
def toResult(tp: Type, mt: MethodicType, sym: Symbol, fail: Message => Unit)(using Context): Type =
1256+
ToResult(tp, mt, sym, fail)(tp)
12321257

12331258
/** Map global roots in function results to result roots. Also,
12341259
* map roots in the types of def methods that are parameterless
@@ -1244,7 +1269,7 @@ object Capabilities:
12441269
else apply(mt))
12451270
case t: MethodType if variance > 0 && t.marksExistentialScope =>
12461271
val t1 = mapOver(t).asInstanceOf[MethodType]
1247-
t1.derivedLambdaType(resType = toResult(t1.resType, t1, fail))
1272+
t1.derivedLambdaType(resType = toResult(t1.resType, t1, sym, fail))
12481273
case CapturingType(parent, refs) =>
12491274
t.derivedCapturingType(this(parent), refs)
12501275
case t: (LazyRef | TypeVar) =>
@@ -1259,7 +1284,7 @@ object Capabilities:
12591284
m(tp) match
12601285
case tp1: ExprType if sym.is(Method, butNot = Accessor) =>
12611286
// Map the result of parameterless `def` methods.
1262-
tp1.derivedExprType(toResult(tp1.resType, tp1, fail))
1287+
tp1.derivedExprType(toResult(tp1.resType, tp1, sym, fail))
12631288
case tp1: PolyType if !tp1.resType.isInstanceOf[MethodicType] =>
12641289
// Map also the result type of method with only type parameters.
12651290
// This way, the `^` in the following method will be mapped to a `ResultCap`:
@@ -1269,7 +1294,7 @@ object Capabilities:
12691294
// ```
12701295
// This is more desirable than interpreting `^` as a `Fresh` at the level of `Buffer.empty`
12711296
// in most cases.
1272-
tp1.derivedLambdaType(resType = toResult(tp1.resType, tp1, fail))
1297+
tp1.derivedLambdaType(resType = toResult(tp1.resType, tp1, sym, fail))
12731298
case tp1 => tp1
12741299
end toResultInResults
12751300

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import tpd.*
1313
import Annotations.Annotation
1414
import CaptureSet.VarState
1515
import Capabilities.*
16+
import Mutability.isMutableType
1617
import StdNames.nme
1718
import config.Feature
1819
import NameKinds.TryOwnerName
@@ -529,6 +530,9 @@ extension (cls: ClassSymbol)
529530
.foldLeft(defn.AnyClass)(leastClassifier)
530531
else defn.AnyClass
531532

533+
def isSeparate(using Context): Boolean =
534+
cls.typeRef.isMutableType
535+
532536
extension (sym: Symbol)
533537

534538
/** This symbol is one of `retains` or `retainsCap` */
@@ -579,13 +583,16 @@ extension (sym: Symbol)
579583
&& !defn.isPolymorphicAfterErasure(sym)
580584
&& !defn.isTypeTestOrCast(sym)
581585

582-
/** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures */
586+
/** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures
587+
* and that is not a consume accessor.
588+
*/
583589
def isRefiningParamAccessor(using Context): Boolean =
584590
sym.is(ParamAccessor)
585591
&& {
586592
val param = sym.owner.primaryConstructor.paramNamed(sym.name)
587593
!param.hasAnnotation(defn.ConstructorOnlyAnnot)
588594
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot)
595+
&& !param.hasAnnotation(defn.ConsumeAnnot)
589596
}
590597

591598
def hasTrackedParts(using Context): Boolean =

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 =>

0 commit comments

Comments
 (0)