Skip to content

Commit 2d37938

Browse files
committed
Polishings
1 parent 3ed82a3 commit 2d37938

File tree

18 files changed

+133
-77
lines changed

18 files changed

+133
-77
lines changed

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,9 @@ extension (tp: Type)
232232
case tp @ ReachCapability(_) =>
233233
tp.singletonCaptureSet
234234
case ReadOnlyCapability(ref) =>
235-
ref.deepCaptureSet(includeTypevars)
235+
val refDcs = ref.deepCaptureSet(includeTypevars)
236+
if refDcs.isConst then CaptureSet(refDcs.elems.map(_.readOnly))
237+
else refDcs // this case should not happen for correct programs
236238
case tp: SingletonCaptureRef if tp.isTrackableRef =>
237239
tp.reach.singletonCaptureSet
238240
case _ =>
@@ -279,17 +281,19 @@ extension (tp: Type)
279281
case _ =>
280282
tp
281283

282-
/** The first element of this path type */
284+
/** The first element of this path type. Note that class parameter references
285+
* are of the form this.C but their pathroot is still this.C, not this.
286+
*/
283287
final def pathRoot(using Context): Type = tp.dealias match
284288
case tp1: TermRef if tp1.symbol.maybeOwner.isClass => tp1.prefix.pathRoot
285289
case tp1: TypeRef if !tp1.symbol.is(Param) => tp1.prefix.pathRoot
286290
case tp1 => tp1
287291

288292
/** The first element of a path type, but stop at references extending
289-
* SharableCapability
293+
* SharedCapability.
290294
*/
291295
final def pathRootOrShared(using Context): Type =
292-
if tp.derivesFrom(defn.Caps_SharedCapability) then tp
296+
if tp.derivesFromSharedCapability then tp
293297
else tp.dealias match
294298
case tp1: TermRef if tp1.symbol.maybeOwner.isClass => tp1.prefix.pathRoot
295299
case tp1: TypeRef if !tp1.symbol.is(Param) => tp1.prefix.pathRoot
@@ -427,6 +431,7 @@ extension (tp: Type)
427431

428432
def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability)
429433
def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable)
434+
def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability)
430435

431436
/** Drop @retains annotations everywhere */
432437
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
@@ -466,6 +471,11 @@ extension (tp: Type)
466471
* is the union of all capture sets that appear in covariant position in the
467472
* type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}`
468473
* are unrelated.
474+
*
475+
* Reach capabilities cannot wrap read-only capabilities or maybe capabilities.
476+
* We have
477+
* (x.rd).reach = x*.rd
478+
* (x.rd)? = (x*)?
469479
*/
470480
def reach(using Context): CaptureRef = tp match
471481
case tp @ AnnotatedType(tp1: CaptureRef, annot)
@@ -483,6 +493,10 @@ extension (tp: Type)
483493
/** If `x` is a capture ref, its read-only capability `x.rd`, represented internally
484494
* as `x @readOnlyCapability`. We have {x.rd} <: {x}. If `x` is a reach capability `y*`,
485495
* then its read-only version is `x.rd*`.
496+
*
497+
* Read-only capabilities cannot wrap maybe capabilities
498+
* but they can wrap reach capabilities. We have
499+
* (x?).readOnly = (x.rd)?
486500
*/
487501
def readOnly(using Context): CaptureRef = tp match
488502
case tp @ AnnotatedType(tp1: CaptureRef, annot)

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,25 @@ trait CaptureRef extends TypeProxy, ValueType:
100100
/** Is this reference the generic root capability `cap` or a Fresh.Cap instance? */
101101
final def isCapOrFresh(using Context): Boolean = isCap || isFresh
102102

103-
/** Is this reference one the generic root capabilities `cap` or `cap.rd` ? */
103+
/** Is this reference one of the generic root capabilities `cap` or `cap.rd` ? */
104104
final def isRootCapability(using Context): Boolean = this match
105105
case ReadOnlyCapability(tp1) => tp1.isCapOrFresh
106106
case _ => isCapOrFresh
107107

108-
/** Is this reference capability that does not derive from another capability ? */
108+
/** Is this reference a capability that does not derive from another capability?
109+
* Includes read-only versions of maximal capabilities.
110+
*/
109111
final def isMaxCapability(using Context): Boolean = this match
110112
case tp: TermRef => tp.isCap || tp.info.derivesFrom(defn.Caps_Exists)
111113
case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists)
112114
case Fresh.Cap(_) => true
113115
case ReadOnlyCapability(tp1) => tp1.isMaxCapability
114116
case _ => false
115117

118+
/** An exclusive capability is a capability that derives
119+
* indirectly from a maximal capability without goinh through
120+
* a read-only capability first.
121+
*/
116122
final def isExclusive(using Context): Boolean =
117123
!isReadOnly && (isMaxCapability || captureSetOfInfo.isExclusive)
118124

@@ -159,8 +165,6 @@ trait CaptureRef extends TypeProxy, ValueType:
159165
* X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y
160166
* Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y
161167
* Contains[X, y] ==> X subsumes y
162-
*
163-
* TODO: Move to CaptureSet
164168
*/
165169
final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = VarState.Separate): Boolean =
166170

@@ -239,7 +243,7 @@ trait CaptureRef extends TypeProxy, ValueType:
239243
end subsumes
240244

241245
/** This is a maximal capabaility that subsumes `y` in given context and VarState.
242-
* @param canAddHidden If true we allow maximal capabilties to subsume all other capabilities.
246+
* @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities.
243247
* We add those capabilities to the hidden set if this is Fresh.Cap
244248
* If false we only accept `y` elements that are already in the
245249
* hidden set of this Fresh.Cap. The idea is that in a VarState that

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,8 @@ object CaptureSet:
636636
*/
637637
def solve()(using Context): Unit =
638638
if !isConst then
639-
val approx = upperApprox(empty).map(Fresh.FromCap(NoSymbol).inverse)
639+
val approx = upperApprox(empty)
640+
.map(Fresh.FromCap(NoSymbol).inverse) // Fresh.Cap --> cap
640641
.showing(i"solve $this = $result", capt)
641642
//println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}")
642643
val newElems = approx.elems -- elems
@@ -1139,6 +1140,7 @@ object CaptureSet:
11391140

11401141
/** A template for maps on capabilities where f(c) <: c and f(f(c)) = c */
11411142
private abstract class NarrowingCapabilityMap(using Context) extends BiTypeMap:
1143+
11421144
def mapRef(ref: CaptureRef): CaptureRef
11431145

11441146
def apply(t: Type) = t match

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

+38-22
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,7 @@ class CheckCaptures extends Recheck, SymTransformer:
519519
def includeCallCaptures(sym: Symbol, resType: Type, tree: Tree)(using Context): Unit = resType match
520520
case _: MethodOrPoly => // wait until method is fully applied
521521
case _ =>
522-
if sym.exists then
523-
if curEnv.isOpen then markFree(capturedVars(sym), tree)
522+
if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), tree)
524523

525524
/** Under the sealed policy, disallow the root capability in type arguments.
526525
* Type arguments come either from a TypeApply node or from an AppliedType
@@ -556,16 +555,21 @@ class CheckCaptures extends Recheck, SymTransformer:
556555
if param.isUseParam then markFree(arg.nuType.deepCaptureSet, errTree)
557556
end disallowCapInTypeArgs
558557

558+
/** Rechecking idents involves:
559+
* - adding call captures for idents referring to methods
560+
* - marking as free the identifier with any selections or .rd
561+
* modifiers implied by the expected type
562+
*/
559563
override def recheckIdent(tree: Ident, pt: Type)(using Context): Type =
560564
val sym = tree.symbol
561565
if sym.is(Method) then
562566
// If ident refers to a parameterless method, charge its cv to the environment
563567
includeCallCaptures(sym, sym.info, tree)
564568
else if !sym.isStatic then
565-
// Otherwise charge its symbol, but add all selections implied by the e
566-
// expected type `pt`.
567-
// Example: If we have `x` and the expected type says we select that with `.a.b`,
568-
// we charge `x.a.b` instead of `x`.
569+
// Otherwise charge its symbol, but add all selections and also any `.rd`
570+
// modifier implied by the expected type `pt`.
571+
// Example: If we have `x` and the expected type says we select that with `.a.b`
572+
// where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
569573
def addSelects(ref: TermRef, pt: Type): CaptureRef = pt match
570574
case pt: PathSelectionProto if ref.isTracked =>
571575
if pt.sym.isReadOnlyMethod then
@@ -582,7 +586,8 @@ class CheckCaptures extends Recheck, SymTransformer:
582586
super.recheckIdent(tree, pt)
583587

584588
/** The expected type for the qualifier of a selection. If the selection
585-
* could be part of a capabaility path, we return a PathSelectionProto.
589+
* could be part of a capability path or is a a read-only method, we return
590+
* a PathSelectionProto.
586591
*/
587592
override def selectionProto(tree: Select, pt: Type)(using Context): Type =
588593
val sym = tree.symbol
@@ -616,6 +621,9 @@ class CheckCaptures extends Recheck, SymTransformer:
616621
}
617622
case _ => denot
618623

624+
// Don't allow update methods to be called unless the qualifier captures
625+
// contain an exclusive referenece. TODO This should probabkly rolled into
626+
// qualifier logic once we have it.
619627
if tree.symbol.isUpdateMethod && !qualType.captureSet.isExclusive then
620628
report.error(
621629
em"""cannot call update ${tree.symbol} from $qualType,
@@ -651,8 +659,8 @@ class CheckCaptures extends Recheck, SymTransformer:
651659
selType
652660
}//.showing(i"recheck sel $tree, $qualType = $result")
653661

654-
/** Hook for massaging a function before it is applied. Copies all @use annotations
655-
* on method parameter symbols to the corresponding paramInfo types.
662+
/** Hook for massaging a function before it is applied. Copies all @use and @consume
663+
* annotations on method parameter symbols to the corresponding paramInfo types.
656664
*/
657665
override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType =
658666
val paramInfosWithUses =
@@ -682,7 +690,8 @@ class CheckCaptures extends Recheck, SymTransformer:
682690
includeCallCaptures(meth, res, tree)
683691
res
684692

685-
/** Recheck argument, and, if formal parameter carries a `@use`,
693+
/** Recheck argument against a "freshened" version of `formal` where toplevel `cap`
694+
* occurrences are replaced by `Fresh.Cap`. Also, if formal parameter carries a `@use`,
686695
* charge the deep capture set of the actual argument to the environment.
687696
*/
688697
protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type =
@@ -773,16 +782,21 @@ class CheckCaptures extends Recheck, SymTransformer:
773782

774783
/** First half of result pair:
775784
* Refine the type of a constructor call `new C(t_1, ..., t_n)`
776-
* to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked
777-
* parameters of C and T_1, ..., T_m are the types of the corresponding arguments.
785+
* to C{val x_1: @refineOverride T_1, ..., x_m: @refineOverride T_m}
786+
* where x_1, ..., x_m are the tracked parameters of C and
787+
* T_1, ..., T_m are the types of the corresponding arguments. The @refineOveride
788+
* annotations avoid problematic intersections of capture sets when those
789+
* parameters are selected.
778790
*
779791
* Second half: union of initial capture set and all capture sets of arguments
780-
* to tracked parameters.
792+
* to tracked parameters. The initial capture set `initCs` is augmented with
793+
* - Fresh.Cap if `core` extends Mutable
794+
* - Fresh.Cap.rd if `core` extends Capability
781795
*/
782796
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
783797
var refined: Type = core
784798
var allCaptures: CaptureSet =
785-
if core.derivesFromMutable then CaptureSet.fresh()
799+
if core.derivesFromMutable then initCs ++ CaptureSet.fresh()
786800
else if core.derivesFromCapability then initCs ++ Fresh.Cap().readOnly.singletonCaptureSet
787801
else initCs
788802
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
@@ -1488,7 +1502,7 @@ class CheckCaptures extends Recheck, SymTransformer:
14881502
/** If actual is a capturing type T^C extending Mutable, and expected is an
14891503
* unboxed non-singleton value type not extending mutable, narrow the capture
14901504
* set `C` to `ro(C)`.
1491-
* The unboxed condition ensures that the expected is not a type variable
1505+
* The unboxed condition ensures that the expected type is not a type variable
14921506
* that's upper bounded by a read-only type. In this case it would not be sound
14931507
* to narrow to the read-only set, since that set can be propagated
14941508
* by the type variable instantiation.
@@ -1514,9 +1528,9 @@ class CheckCaptures extends Recheck, SymTransformer:
15141528
actual
15151529
else
15161530
val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual)
1517-
val improvedRO = improveReadOnly(improvedVAR, expected)
1531+
val improved = improveReadOnly(improvedVAR, expected)
15181532
val adapted = adaptBoxed(
1519-
improvedRO.withReachCaptures(actual), expected, tree,
1533+
improved.withReachCaptures(actual), expected, tree,
15201534
covariant = true, alwaysConst = false, boxErrors)
15211535
if adapted eq improvedVAR // no .rd improvement, no box-adaptation
15221536
then actual // might as well use actual instead of improved widened
@@ -1563,17 +1577,19 @@ class CheckCaptures extends Recheck, SymTransformer:
15631577

15641578
/** Check that overrides don't change the @use or @consume status of their parameters */
15651579
override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit =
1566-
def fail(msg: String) =
1567-
report.error(
1568-
OverrideError(msg, self, member, other, self.memberInfo(member), self.memberInfo(other)),
1569-
if member.owner == clazz then member.srcPos else clazz.srcPos)
15701580
for
15711581
(params1, params2) <- member.rawParamss.lazyZip(other.rawParamss)
15721582
(param1, param2) <- params1.lazyZip(params2)
15731583
do
15741584
def checkAnnot(cls: ClassSymbol) =
15751585
if param1.hasAnnotation(cls) != param2.hasAnnotation(cls) then
1576-
fail(i"has a parameter ${param1.name} with different @${cls.name} status than the corresponding parameter in the overridden definition")
1586+
report.error(
1587+
OverrideError(
1588+
i"has a parameter ${param1.name} with different @${cls.name} status than the corresponding parameter in the overridden definition",
1589+
self, member, other, self.memberInfo(member), self.memberInfo(other)
1590+
),
1591+
if member.owner == clazz then member.srcPos else clazz.srcPos)
1592+
15771593
checkAnnot(defn.UseAnnot)
15781594
checkAnnot(defn.ConsumeAnnot)
15791595
end OverridingPairsCheckerCC

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ object Existential:
252252
tp1.derivedAnnotatedType(toCap(parent), ann)
253253
case _ => tp
254254

255-
/** Map existentials at the top-level and in all nested result types to `cap`
255+
/** Map existentials at the top-level and in all nested result types to `Fresh.Cap`
256256
*/
257257
def toCapDeeply(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match
258258
case Existential(boundVar, unpacked) =>

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ import util.SimpleIdentitySet.empty
1616
import CaptureSet.{Refs, emptySet, NarrowingCapabilityMap}
1717
import dotty.tools.dotc.util.SimpleIdentitySet
1818

19-
/** Handling fresh in CC:
20-
21-
*/
19+
/** A module for handling Fresh types. Fresh.Cap instances are top type that keep
20+
* track of what they hide when capabilities get widened by subsumption to fresh.
21+
* The module implements operations to convert between regular caps.cap and
22+
* Fresh.Cap instances. Fresh.Cap is encoded as `caps.cap @freshCapability(...)` where
23+
* `freshCapability(...)` is a special kind of annotation of type `Fresh.Annot`
24+
* that contains a hidden set.
25+
*/
2226
object Fresh:
2327

28+
/** The annotation of a Fresh.Cap instance */
2429
case class Annot(hidden: CaptureSet.HiddenSet) extends Annotation:
2530
override def symbol(using Context) = defn.FreshCapabilityAnnot
2631
override def tree(using Context) = New(symbol.typeRef, Nil)
@@ -32,13 +37,17 @@ object Fresh:
3237
case _ => false
3338
end Annot
3439

40+
/** The initial elements (either 0 or 1) of a hidden set created for given `owner`.
41+
* If owner `x` is a trackable this is `x*` if reach` is true, or `x` otherwise.
42+
*/
3543
private def ownerToHidden(owner: Symbol, reach: Boolean)(using Context): Refs =
3644
val ref = owner.termRef
3745
if reach then
3846
if ref.isTrackableRef then SimpleIdentitySet(ref.reach) else emptySet
3947
else
4048
if ref.isTracked then SimpleIdentitySet(ref) else emptySet
4149

50+
/** An extractor for "fresh" capabilities */
4251
object Cap:
4352

4453
def apply(initialHidden: Refs = emptySet)(using Context): CaptureRef =

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
411411
def currentOwner = role.dclSym.orElse(ctx.owner)
412412
for hiddenRef <- prune(refsToCheck, tpe, role) do
413413
val proot = hiddenRef.pathRootOrShared
414-
if !proot.widen.derivesFrom(defn.Caps_SharedCapability) then
414+
if !proot.widen.derivesFromSharedCapability then
415415
proot match
416416
case ref: TermRef =>
417417
val refSym = ref.symbol
@@ -448,7 +448,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
448448
role match
449449
case _: TypeRole.Argument | _: TypeRole.Qualifier =>
450450
for ref <- refsToCheck do
451-
if !ref.pathRootOrShared.derivesFrom(defn.Caps_SharedCapability) then
451+
if !ref.pathRootOrShared.derivesFromSharedCapability then
452452
consumed.put(ref, pos)
453453
case _ =>
454454
end checkConsumedRefs

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
345345
parent
346346
case _ => tp
347347

348+
/** Check that types extending SharedCapability don't have a `cap` in their capture set.
349+
* TODO This is not enough.
350+
* We need to also track that we cannot get exclusive capabilities in paths
351+
* where some prefix derives from SharedCapability. Also, can we just
352+
* exclude `cap`, or do we have to extend this to all exclusive capabilties?
353+
* The problem is that we know what is exclusive in general only after capture
354+
* checking, not before.
355+
*/
348356
def checkSharedOK(tp: Type): tp.type =
349357
tp match
350358
case CapturingType(parent, refs)
351-
if refs.isUniversal && parent.derivesFrom(defn.Caps_SharedCapability) =>
359+
if refs.isUniversal && parent.derivesFromSharedCapability =>
352360
fail(em"$tp extends SharedCapability, so it cannot capture `cap`")
353361
case _ =>
354362
tp

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

-2
Original file line numberDiff line numberDiff line change
@@ -1118,8 +1118,6 @@ class Definitions {
11181118
@tu lazy val SilentAnnots: Set[Symbol] =
11191119
Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot)
11201120

1121-
@tu lazy val ccParamOnlyAnnotations: Set[Symbol] = Set(UseAnnot, ConsumeAnnot)
1122-
11231121
// A list of annotations that are commonly used to indicate that a field/method argument or return
11241122
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to
11251123
// improve the precision of type nullification.

compiler/src/dotty/tools/dotc/typer/Checking.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ object Checking {
606606
if sym.isWrappedToplevelDef && !sym.isType && sym.flags.is(Infix, butNot = Extension) then
607607
fail(ModifierNotAllowedForDefinition(Flags.Infix, s"A top-level ${sym.showKind} cannot be infix."))
608608
if sym.isUpdateMethod && !sym.owner.derivesFrom(defn.Caps_Mutable) then
609-
fail(em"Update methods can only be used as members of classes deriving from the `Mutable` trait")
609+
fail(em"Update methods can only be used as members of classes extending the `Mutable` trait")
610610
checkApplicable(Erased,
611611
!sym.is(Lazy, butNot = Given)
612612
&& !sym.isMutableVarOrAccessor

0 commit comments

Comments
 (0)