Skip to content

Commit c51345f

Browse files
authored
Drop @capability annotations (#20396)
Replace with references that inherit trait `Capability`. Also: Handle tracked vals in classes. As a next step, addCaptureRefinements should use the tracked logic for all capturing parameters. Right now, we create a fresh capture set variable instead, but that is too loose.
2 parents c6fbe6f + 81cf8d8 commit c51345f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+565
-219
lines changed

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,23 @@ extension (tp: Type)
203203
case _ =>
204204
false
205205

206+
/** Tests whether the type derives from `caps.Capability`, which means
207+
* references of this type are maximal capabilities.
208+
*/
209+
def derivesFromCapability(using Context): Boolean = tp.dealias match
210+
case tp: (TypeRef | AppliedType) =>
211+
val sym = tp.typeSymbol
212+
if sym.isClass then sym.derivesFrom(defn.Caps_Capability)
213+
else tp.superType.derivesFromCapability
214+
case tp: TypeProxy =>
215+
tp.superType.derivesFromCapability
216+
case tp: AndType =>
217+
tp.tp1.derivesFromCapability || tp.tp2.derivesFromCapability
218+
case tp: OrType =>
219+
tp.tp1.derivesFromCapability && tp.tp2.derivesFromCapability
220+
case _ =>
221+
false
222+
206223
/** Drop @retains annotations everywhere */
207224
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
208225
val tm = new TypeMap:
@@ -408,7 +425,7 @@ extension (sym: Symbol)
408425
/** The owner of the current level. Qualifying owners are
409426
* - methods other than constructors and anonymous functions
410427
* - anonymous functions, provided they either define a local
411-
* root of type caps.Cap, or they are the rhs of a val definition.
428+
* root of type caps.Capability, or they are the rhs of a val definition.
412429
* - classes, if they are not staticOwners
413430
* - _root_
414431
*/

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ sealed abstract class CaptureSet extends Showable:
115115
* capture set.
116116
*/
117117
protected final def addNewElem(elem: CaptureRef)(using Context, VarState): CompareResult =
118-
if elem.isRootCapability || summon[VarState] == FrozenState then
118+
if elem.isMaxCapability || summon[VarState] == FrozenState then
119119
addThisElem(elem)
120120
else
121121
addThisElem(elem).orElse:
@@ -147,17 +147,26 @@ sealed abstract class CaptureSet extends Showable:
147147
* this subsumes this.f
148148
* x subsumes y ==> x* subsumes y, x subsumes y?
149149
* x subsumes y ==> x* subsumes y*, x? subsumes y?
150+
* x: x1.type /\ x1 subsumes y ==> x subsumes y
150151
*/
151152
extension (x: CaptureRef)
152153
private def subsumes(y: CaptureRef)(using Context): Boolean =
153154
(x eq y)
154155
|| x.isRootCapability
155156
|| y.match
156-
case y: TermRef => y.prefix eq x
157+
case y: TermRef =>
158+
(y.prefix eq x)
159+
|| y.info.match
160+
case y1: CaptureRef => x.subsumes(y1)
161+
case _ => false
157162
case MaybeCapability(y1) => x.stripMaybe.subsumes(y1)
158163
case _ => false
159164
|| x.match
160165
case ReachCapability(x1) => x1.subsumes(y.stripReach)
166+
case x: TermRef =>
167+
x.info match
168+
case x1: CaptureRef => x1.subsumes(y)
169+
case _ => false
161170
case _ => false
162171

163172
/** {x} <:< this where <:< is subcapturing, but treating all variables
@@ -167,11 +176,11 @@ sealed abstract class CaptureSet extends Showable:
167176
if comparer.isInstanceOf[ExplainingTypeComparer] then // !!! DEBUG
168177
reporting.trace.force(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true):
169178
elems.exists(_.subsumes(x))
170-
|| !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
179+
|| !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
171180
else
172181
reporting.trace(i"$this accountsFor $x, ${x.captureSetOfInfo}?", show = true):
173182
elems.exists(_.subsumes(x))
174-
|| !x.isRootCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
183+
|| !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
175184

176185
/** A more optimistic version of accountsFor, which does not take variable supersets
177186
* of the `x` reference into account. A set might account for `x` if it accounts
@@ -183,7 +192,7 @@ sealed abstract class CaptureSet extends Showable:
183192
def mightAccountFor(x: CaptureRef)(using Context): Boolean =
184193
reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true) {
185194
elems.exists(_.subsumes(x))
186-
|| !x.isRootCapability
195+
|| !x.isMaxCapability
187196
&& {
188197
val elems = x.captureSetOfInfo.elems
189198
!elems.isEmpty && elems.forall(mightAccountFor)
@@ -383,7 +392,7 @@ object CaptureSet:
383392

384393
def apply(elems: CaptureRef*)(using Context): CaptureSet.Const =
385394
if elems.isEmpty then empty
386-
else Const(SimpleIdentitySet(elems.map(_.normalizedRef)*))
395+
else Const(SimpleIdentitySet(elems.map(_.normalizedRef.ensuring(_.isTrackableRef))*))
387396

388397
def apply(elems: Refs)(using Context): CaptureSet.Const =
389398
if elems.isEmpty then empty else Const(elems)
@@ -491,6 +500,7 @@ object CaptureSet:
491500
CompareResult.LevelError(this, elem)
492501
else
493502
//if id == 34 then assert(!elem.isUniversalRootCapability)
503+
assert(elem.isTrackableRef, elem)
494504
elems += elem
495505
if elem.isRootCapability then
496506
rootAddedHandler()
@@ -1032,7 +1042,9 @@ object CaptureSet:
10321042

10331043
/** The capture set of the type underlying CaptureRef */
10341044
def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match
1035-
case ref: TermRef if ref.isRootCapability => ref.singletonCaptureSet
1045+
case ref: (TermRef | TermParamRef) if ref.isMaxCapability =>
1046+
if ref.isTrackableRef then ref.singletonCaptureSet
1047+
else CaptureSet.universal
10361048
case ReachCapability(ref1) => deepCaptureSet(ref1.widen)
10371049
.showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt)
10381050
case _ => ofType(ref.underlying, followResult = true)
@@ -1046,7 +1058,8 @@ object CaptureSet:
10461058
case tp: TermParamRef =>
10471059
tp.captureSet
10481060
case tp: TypeRef =>
1049-
if tp.typeSymbol == defn.Caps_Cap then universal else empty
1061+
if tp.derivesFromCapability then universal // TODO: maybe return another value that indicates that the underltinf ref is maximal?
1062+
else empty
10501063
case _: TypeParamRef =>
10511064
empty
10521065
case CapturingType(parent, refs) =>
@@ -1074,7 +1087,7 @@ object CaptureSet:
10741087
case _ =>
10751088
empty
10761089
recur(tp)
1077-
.showing(i"capture set of $tp = $result", captDebug)
1090+
//.showing(i"capture set of $tp = $result", captDebug)
10781091

10791092
private def deepCaptureSet(tp: Type)(using Context): CaptureSet =
10801093
val collect = new TypeAccumulator[CaptureSet]:

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

+35-14
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,17 @@ object CheckCaptures:
157157
case _ =>
158158
case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot =>
159159
()
160-
case t =>
160+
case CapturingType(parent, refs) =>
161161
if variance >= 0 then
162-
t.captureSet.disallowRootCapability: () =>
162+
refs.disallowRootCapability: () =>
163163
def part = if t eq tp then "" else i"the part $t of "
164164
report.error(
165165
em"""$what cannot $have $tp since
166166
|${part}that type captures the root capability `cap`.
167167
|$addendum""",
168168
pos)
169+
traverse(parent)
170+
case t =>
169171
traverseChildren(t)
170172
check.traverse(tp)
171173
end disallowRootCapabilitiesIn
@@ -537,8 +539,8 @@ class CheckCaptures extends Recheck, SymTransformer:
537539
*/
538540
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
539541
var refined: Type = core
540-
var allCaptures: CaptureSet = if setup.isCapabilityClassRef(core)
541-
then CaptureSet.universal else initCs
542+
var allCaptures: CaptureSet =
543+
if core.derivesFromCapability then CaptureSet.universal else initCs
542544
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
543545
val getter = cls.info.member(getterName).suchThat(_.is(ParamAccessor)).symbol
544546
if getter.termRef.isTracked && !getter.is(Private) then
@@ -572,8 +574,10 @@ class CheckCaptures extends Recheck, SymTransformer:
572574
val TypeApply(fn, args) = tree
573575
val polyType = atPhase(thisPhase.prev):
574576
fn.tpe.widen.asInstanceOf[TypeLambda]
577+
def isExempt(sym: Symbol) =
578+
sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue
575579
for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do
576-
if !tree.symbol.isTypeTestOrCast then
580+
if !isExempt(tree.symbol) then
577581
def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else ""
578582
disallowRootCapabilitiesIn(arg.knownType, NoSymbol,
579583
i"Sealed type variable $pname", "be instantiated to",
@@ -1004,7 +1008,7 @@ class CheckCaptures extends Recheck, SymTransformer:
10041008
if (ares1 eq ares) && (aargs1 eq aargs) then actual
10051009
else reconstruct(aargs1, ares1)
10061010

1007-
(resTp, curEnv.captured)
1011+
(resTp, CaptureSet(curEnv.captured.elems))
10081012
end adaptFun
10091013

10101014
/** Adapt type function type `actual` to the expected type.
@@ -1026,14 +1030,14 @@ class CheckCaptures extends Recheck, SymTransformer:
10261030
if ares1 eq ares then actual
10271031
else reconstruct(ares1)
10281032

1029-
(resTp, curEnv.captured)
1033+
(resTp, CaptureSet(curEnv.captured.elems))
10301034
end adaptTypeFun
10311035

10321036
def adaptInfo(actual: Type, expected: Type, covariant: Boolean): String =
10331037
val arrow = if covariant then "~~>" else "<~~"
10341038
i"adapting $actual $arrow $expected"
10351039

1036-
def adapt(actual: Type, expected: Type, covariant: Boolean): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true) {
1040+
def adapt(actual: Type, expected: Type, covariant: Boolean): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true):
10371041
if expected.isInstanceOf[WildcardType] then actual
10381042
else
10391043
// Decompose the actual type into the inner shape type, the capture set and the box status
@@ -1113,7 +1117,22 @@ class CheckCaptures extends Recheck, SymTransformer:
11131117
adaptedType(!boxed)
11141118
else
11151119
adaptedType(boxed)
1116-
}
1120+
end adapt
1121+
1122+
/** If result derives from caps.Capability, yet is not a capturing type itself,
1123+
* make its capture set explicit.
1124+
*/
1125+
def makeCaptureSetExplicit(result: Type) = result match
1126+
case CapturingType(_, _) => result
1127+
case _ =>
1128+
if result.derivesFromCapability then
1129+
val cap: CaptureRef = actual match
1130+
case ref: CaptureRef if ref.isTracked =>
1131+
ref
1132+
case _ =>
1133+
defn.captureRoot.termRef // TODO: skolemize?
1134+
CapturingType(result, cap.singletonCaptureSet)
1135+
else result
11171136

11181137
if expected == LhsProto || expected.isSingleton && actual.isSingleton then
11191138
actual
@@ -1129,10 +1148,12 @@ class CheckCaptures extends Recheck, SymTransformer:
11291148
case _ =>
11301149
case _ =>
11311150
val adapted = adapt(actualw.withReachCaptures(actual), expected, covariant = true)
1132-
if adapted ne actualw then
1133-
capt.println(i"adapt boxed $actual vs $expected ===> $adapted")
1134-
adapted
1135-
else actual
1151+
makeCaptureSetExplicit:
1152+
if adapted ne actualw then
1153+
capt.println(i"adapt boxed $actual vs $expected ===> $adapted")
1154+
adapted
1155+
else
1156+
actual
11361157
end adaptBoxed
11371158

11381159
/** Check overrides again, taking capture sets into account.
@@ -1305,7 +1326,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13051326
case ref: TermParamRef
13061327
if !allowed.contains(ref) && !seen.contains(ref) =>
13071328
seen += ref
1308-
if ref.underlying.isRef(defn.Caps_Cap) then
1329+
if ref.underlying.isRef(defn.Caps_Capability) then
13091330
report.error(i"escaping local reference $ref", tree.srcPos)
13101331
else
13111332
val widened = ref.captureSetOfInfo

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

+20-34
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ trait SetupAPI:
2323
def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit
2424
def isPreCC(sym: Symbol)(using Context): Boolean
2525
def postCheck()(using Context): Unit
26-
def isCapabilityClassRef(tp: Type)(using Context): Boolean
2726

2827
object Setup:
2928

@@ -58,8 +57,21 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
5857
private val toBeUpdated = new mutable.HashSet[Symbol]
5958

6059
private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet =
61-
if symd.isAllOf(PrivateParamAccessor) && symd.owner.is(CaptureChecked) && !symd.hasAnnotation(defn.ConstructorOnlyAnnot)
62-
then symd.flags &~ Private | Recheck.ResetPrivate
60+
61+
object containsCovarRetains extends TypeAccumulator[Boolean]:
62+
def apply(x: Boolean, tp: Type): Boolean =
63+
if x then true
64+
else if tp.derivesFromCapability && variance >= 0 then true
65+
else tp match
66+
case AnnotatedType(_, ann) if ann.symbol.isRetains && variance >= 0 => true
67+
case _ => foldOver(x, tp)
68+
def apply(tp: Type): Boolean = apply(false, tp)
69+
70+
if symd.isAllOf(PrivateParamAccessor)
71+
&& symd.owner.is(CaptureChecked)
72+
&& !symd.hasAnnotation(defn.ConstructorOnlyAnnot)
73+
&& containsCovarRetains(symd.symbol.originDenotation.info)
74+
then symd.flags &~ Private
6375
else symd.flags
6476

6577
def isPreCC(sym: Symbol)(using Context): Boolean =
@@ -68,31 +80,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
6880
&& !sym.owner.is(CaptureChecked)
6981
&& !defn.isFunctionSymbol(sym.owner)
7082

71-
private val capabilityClassMap = new util.HashMap[Symbol, Boolean]
72-
73-
/** Check if the class is capability, which means:
74-
* 1. the class has a capability annotation,
75-
* 2. or at least one of its parent type has universal capability.
76-
*/
77-
def isCapabilityClassRef(tp: Type)(using Context): Boolean = tp.dealiasKeepAnnots match
78-
case _: TypeRef | _: AppliedType =>
79-
val sym = tp.classSymbol
80-
def checkSym: Boolean =
81-
sym.hasAnnotation(defn.CapabilityAnnot)
82-
|| sym.info.parents.exists(hasUniversalCapability)
83-
sym.isClass && capabilityClassMap.getOrElseUpdate(sym, checkSym)
84-
case _ => false
85-
86-
private def hasUniversalCapability(tp: Type)(using Context): Boolean = tp.dealiasKeepAnnots match
87-
case CapturingType(parent, refs) =>
88-
refs.isUniversal || hasUniversalCapability(parent)
89-
case AnnotatedType(parent, ann) =>
90-
if ann.symbol.isRetains then
91-
try ann.tree.toCaptureSet.isUniversal || hasUniversalCapability(parent)
92-
catch case ex: IllegalCaptureRef => false
93-
else hasUniversalCapability(parent)
94-
case tp => isCapabilityClassRef(tp)
95-
9683
private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap:
9784
def apply(t: Type): Type = t match
9885
case t: MethodType =>
@@ -196,7 +183,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
196183
case cls: ClassSymbol
197184
if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) =>
198185
cls.paramGetters.foldLeft(tp) { (core, getter) =>
199-
if atPhase(thisPhase.next)(getter.termRef.isTracked) then
186+
if atPhase(thisPhase.next)(getter.termRef.isTracked)
187+
&& !getter.is(Tracked)
188+
then
200189
val getterType =
201190
mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias
202191
RefinedType(core, getter.name,
@@ -317,10 +306,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
317306
case t: TypeVar =>
318307
this(t.underlying)
319308
case t =>
320-
// Map references to capability classes C to C^
321-
if isCapabilityClassRef(t)
322-
then CapturingType(t, defn.expandedUniversalSet, boxed = false)
323-
else recur(t)
309+
recur(t)
324310
end expandAliases
325311

326312
val tp1 = expandAliases(tp) // TODO: Do we still need to follow aliases?
@@ -592,7 +578,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
592578
if sym.isClass then
593579
!sym.isPureClass
594580
else
595-
sym != defn.Caps_Cap && instanceCanBeImpure(tp.superType)
581+
sym != defn.Caps_Capability && instanceCanBeImpure(tp.superType)
596582
case tp: (RefinedOrRecType | MatchType) =>
597583
instanceCanBeImpure(tp.underlying)
598584
case tp: AndType =>

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ class Definitions {
991991

992992
@tu lazy val CapsModule: Symbol = requiredModule("scala.caps")
993993
@tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap")
994-
@tu lazy val Caps_Cap: TypeSymbol = CapsModule.requiredType("Cap")
994+
@tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability")
995995
@tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability")
996996
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
997997
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
@@ -1014,7 +1014,6 @@ class Definitions {
10141014
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
10151015
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
10161016
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
1017-
@tu lazy val CapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.capability")
10181017
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
10191018
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")
10201019
@tu lazy val ProvisionalSuperClassAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ProvisionalSuperClass")
@@ -2033,7 +2032,7 @@ class Definitions {
20332032
*/
20342033
@tu lazy val ccExperimental: Set[Symbol] = Set(
20352034
CapsModule, CapsModule.moduleClass, PureClass,
2036-
CapabilityAnnot, RequiresCapabilityAnnot,
2035+
RequiresCapabilityAnnot,
20372036
RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot)
20382037

20392038
/** Experimental language features defined in `scala.runtime.stdLibPatches.language.experimental`.

0 commit comments

Comments
 (0)