Skip to content

Commit ad11819

Browse files
committed
Implement fresh capabilities
These are represented as Fresh.Cap(hidden) where hidden is the set of capabilities subsumed by a fresh. The underlying representation is as an annotated type `T @annotation.internal.freshCapability`. Require -source `3.7` for caps to be converted to Fresh.Cap Also: - Refacture and document CaputureSet - Make SimpleIdentitySets showable - Refactor VarState - Drop Frozen enum - Make VarState subclasses inner classes of companion object - Rename them - Give implicit parameter VarState of subCapture method a default value - Fix printing of capturesets containing cap and some other capability - Revise handing of @uncheckedAnnotation
1 parent e4cc265 commit ad11819

18 files changed

+539
-192
lines changed

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ import config.Feature
1616
import collection.mutable
1717
import CCState.*
1818
import reporting.Message
19+
import CaptureSet.VarState
1920

21+
/** Attachment key for capturing type trees */
2022
private val Captures: Key[CaptureSet] = Key()
2123

24+
/** Context property to print Fresh.Cap as "fresh" instead of "cap" */
25+
val PrintFresh: Key[Unit] = Key()
26+
2227
object ccConfig:
2328

2429
/** If true, allow mapping capture set variables under captureChecking with maps that are neither
@@ -47,6 +52,10 @@ object ccConfig:
4752
def useSealed(using Context) =
4853
Feature.sourceVersion.stable != SourceVersion.`3.5`
4954

55+
/** If true, turn on separation checking */
56+
def useFresh(using Context): Boolean =
57+
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`future`)
58+
5059
end ccConfig
5160

5261
/** Are we at checkCaptures phase? */
@@ -193,10 +202,7 @@ extension (tp: Type)
193202
case tp: TypeParamRef =>
194203
tp.derivesFrom(defn.Caps_CapSet)
195204
case AnnotatedType(parent, annot) =>
196-
(annot.symbol == defn.ReachCapabilityAnnot
197-
|| annot.symbol == defn.MaybeCapabilityAnnot
198-
|| annot.symbol == defn.ReadOnlyCapabilityAnnot
199-
) && parent.isTrackableRef
205+
defn.capabilityWrapperAnnots.contains(annot.symbol) && parent.isTrackableRef
200206
case _ =>
201207
false
202208

@@ -244,7 +250,7 @@ extension (tp: Type)
244250
* the two capture sets are combined.
245251
*/
246252
def capturing(cs: CaptureSet)(using Context): Type =
247-
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, frozen = true).isOK)
253+
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, VarState.Separate).isOK)
248254
&& !cs.keepAlways
249255
then tp
250256
else tp match
@@ -421,6 +427,10 @@ extension (tp: Type)
421427
mapOver(t)
422428
tm(tp)
423429

430+
def hasUseAnnot(using Context): Boolean = tp match
431+
case AnnotatedType(_, ann) => ann.symbol == defn.UseAnnot
432+
case _ => false
433+
424434
/** If `x` is a capture ref, its maybe capability `x?`, represented internally
425435
* as `x @maybeCapability`. `x?` stands for a capability `x` that might or might
426436
* not be part of a capture set. We have `{} <: {x?} <: {x}`. Maybe capabilities
@@ -512,6 +522,24 @@ extension (tp: Type)
512522
tp
513523
case _ =>
514524
tp
525+
end withReachCaptures
526+
527+
/** Does this type contain no-flip covariant occurrences of `cap`? */
528+
def containsCap(using Context): Boolean =
529+
val acc = new TypeAccumulator[Boolean]:
530+
def apply(x: Boolean, t: Type) =
531+
x
532+
|| variance > 0 && t.dealiasKeepAnnots.match
533+
case t @ CapturingType(p, cs) if cs.containsCap =>
534+
true
535+
case t @ AnnotatedType(parent, ann) =>
536+
// Don't traverse annotations, which includes capture sets
537+
this(x, parent)
538+
case Existential(_, _) =>
539+
false
540+
case _ =>
541+
foldOver(x, t)
542+
acc(false, tp)
515543

516544
def level(using Context): Level =
517545
tp match
@@ -690,7 +718,7 @@ abstract class AnnotatedCapability(annot: Context ?=> ClassSymbol):
690718
case _ =>
691719
AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan))
692720
def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match
693-
case AnnotatedType(parent: CaptureRef, ann) if ann.symbol == annot => Some(parent)
721+
case AnnotatedType(parent: CaptureRef, ann) if ann.hasSymbol(annot) => Some(parent)
694722
case _ => None
695723
protected def unwrappable(using Context): Set[Symbol]
696724

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

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import CCState.*
1313
import Periods.NoRunId
1414
import compiletime.uninitialized
1515
import StdNames.nme
16+
import CaptureSet.VarState
1617

1718
/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs,
1819
* as well as three kinds of AnnotatedTypes representing readOnly, reach, and maybe capabilities.
@@ -78,15 +79,24 @@ trait CaptureRef extends TypeProxy, ValueType:
7879
case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot
7980
case _ => false
8081

82+
/** Is this reference a Fresh.Cap instance? */
83+
final def isFresh(using Context): Boolean = this match
84+
case Fresh.Cap(_) => true
85+
case _ => false
86+
87+
/** Is this reference the generic root capability `cap` or a Fresh.Cap instance? */
88+
final def isCapOrFresh(using Context): Boolean = isCap || isFresh
89+
8190
/** Is this reference one the generic root capabilities `cap` or `cap.rd` ? */
8291
final def isRootCapability(using Context): Boolean = this match
83-
case ReadOnlyCapability(tp1) => tp1.isCap
84-
case _ => isCap
92+
case ReadOnlyCapability(tp1) => tp1.isCapOrFresh
93+
case _ => isCapOrFresh
8594

8695
/** Is this reference capability that does not derive from another capability ? */
8796
final def isMaxCapability(using Context): Boolean = this match
8897
case tp: TermRef => tp.isCap || tp.info.derivesFrom(defn.Caps_Exists)
8998
case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists)
99+
case Fresh.Cap(_) => true
90100
case ReadOnlyCapability(tp1) => tp1.isMaxCapability
91101
case _ => false
92102

@@ -137,34 +147,36 @@ trait CaptureRef extends TypeProxy, ValueType:
137147
* Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y
138148
* Contains[X, y] ==> X subsumes y
139149
*
140-
* TODO: Document cases with more comments.
150+
* TODO: Move to CaptureSet
141151
*/
142-
final def subsumes(y: CaptureRef)(using Context): Boolean =
152+
final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = VarState.Separate): Boolean =
153+
143154
def subsumingRefs(x: Type, y: Type): Boolean = x match
144155
case x: CaptureRef => y match
145156
case y: CaptureRef => x.subsumes(y)
146157
case _ => false
147158
case _ => false
148159

149-
def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.match
160+
def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.dealias match
150161
case info: SingletonCaptureRef => test(info)
162+
case CapturingType(parent, _) =>
163+
if this.derivesFrom(defn.Caps_CapSet) then test(info)
164+
/*
165+
If `this` is a capture set variable `C^`, then it is possible that it can be
166+
reached from term variables in a reachability chain through the context.
167+
For instance, in `def test[C^](src: Foo^{C^}) = { val x: Foo^{src} = src; val y: Foo^{x} = x; y }`
168+
we expect that `C^` subsumes `x` and `y` in the body of the method
169+
(cf. test case cc-poly-varargs.scala for a more involved example).
170+
*/
171+
else viaInfo(parent)(test)
151172
case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test)
152173
case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test)
153-
case info @ CapturingType(_,_) if this.derivesFrom(defn.Caps_CapSet) =>
154-
/*
155-
If `this` is a capture set variable `C^`, then it is possible that it can be
156-
reached from term variables in a reachability chain through the context.
157-
For instance, in `def test[C^](src: Foo^{C^}) = { val x: Foo^{src} = src; val y: Foo^{x} = x; y }`
158-
we expect that `C^` subsumes `x` and `y` in the body of the method
159-
(cf. test case cc-poly-varargs.scala for a more involved example).
160-
*/
161-
test(info)
162174
case _ => false
163175

164176
(this eq y)
165-
|| this.isCap
177+
|| maxSubsumes(y, canAddHidden = !vs.isOpen)
166178
|| y.match
167-
case y: TermRef if !y.isRootCapability =>
179+
case y: TermRef if !y.isCap =>
168180
y.prefix.match
169181
case ypre: CaptureRef =>
170182
this.subsumes(ypre)
@@ -213,6 +225,27 @@ trait CaptureRef extends TypeProxy, ValueType:
213225
case _ => false
214226
end subsumes
215227

228+
/** This is a maximal capabaility that subsumes `y` in given context and VarState.
229+
* @param canAddHidden If true we allow maximal capabilties to subsume all other capabilities.
230+
* We add those capabilities to the hidden set if this is Fresh.Cap
231+
* If false we only accept `y` elements that are already in the
232+
* hidden set of this Fresh.Cap. The idea is that in a VarState that
233+
* accepts additions we first run `maxSubsumes` with `canAddHidden = false`
234+
* so that new variables get added to the sets. If that fails, we run
235+
* the test again with canAddHidden = true as a last effort before we
236+
* fail a comparison.
237+
*/
238+
def maxSubsumes(y: CaptureRef, canAddHidden: Boolean)(using ctx: Context, vs: VarState = VarState.Separate): Boolean =
239+
this.match
240+
case Fresh.Cap(hidden) =>
241+
vs.ifNotSeen(this)(hidden.elems.exists(_.subsumes(y)))
242+
|| !y.stripReadOnly.isCap && canAddHidden && vs.addHidden(hidden, y)
243+
case _ =>
244+
this.isCap && canAddHidden
245+
|| y.match
246+
case ReadOnlyCapability(y1) => this.stripReadOnly.maxSubsumes(y1, canAddHidden)
247+
case _ => false
248+
216249
def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[CaptureRef] =
217250
CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty)
218251

0 commit comments

Comments
 (0)