Skip to content

Commit 6e4cd60

Browse files
committed
Make reach capabilities annotated types
1 parent f6d7a91 commit 6e4cd60

File tree

12 files changed

+102
-50
lines changed

12 files changed

+102
-50
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,6 +1858,8 @@ object desugar {
18581858
Annotated(
18591859
AppliedTypeTree(ref(defn.SeqType), t),
18601860
New(ref(defn.RepeatedAnnot.typeRef), Nil :: Nil))
1861+
else if op.name == nme.CC_REACH then
1862+
Apply(ref(defn.Caps_reachCapability), t :: Nil)
18611863
else
18621864
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
18631865
Select(t, op.name)

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,14 @@ class NoCommonRoot(rs: Symbol*)(using Context) extends Exception(
7777
extension (tree: Tree)
7878

7979
/** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */
80-
def toCaptureRef(using Context): CaptureRef = tree.tpe match
81-
case ref: CaptureRef if ref.isTrackableRef => ref
82-
case tpe => throw IllegalCaptureRef(tpe) // if this was compiled from cc syntax, problem should have been reported at Typer
80+
def toCaptureRef(using Context): CaptureRef = tree match
81+
case ReachCapabilityApply(arg) =>
82+
arg.toCaptureRef.reach
83+
case _ => tree.tpe match
84+
case ref: CaptureRef if ref.isTrackableRef =>
85+
ref
86+
case tpe =>
87+
throw IllegalCaptureRef(tpe) // if this was compiled from cc syntax, problem should have been reported at Typer
8388

8489
/** Convert a @retains or @retainsByName annotation tree to the capture set it represents.
8590
* For efficience, the result is cached as an Attachment on the tree.
@@ -209,15 +214,15 @@ extension (tp: Type)
209214
tm(tp)
210215

211216
/** If `x` is a capture ref, its reach capability `x*`, represented internally
212-
* as `x.$reach`. `x*` stands for all capabilities reachable through `x`".
217+
* as `x @reachCapability`. `x*` stands for all capabilities reachable through `x`".
213218
* We have `{x} <: {x*} <: dcs(x)}` where the deep capture set `dcs(x)` of `x`
214219
* is the union of all capture sets that appear in covariant position in the
215220
* type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}`
216221
* are unrelated.
217222
*/
218-
def reach(using Context): TermRef =
223+
def reach(using Context): CaptureRef =
219224
assert(tp.isTrackableRef)
220-
TermRef(tp, nme.CC_REACH, defn.Any_ccReach)
225+
AnnotatedType(tp, Annotation(defn.ReachCapabilityAnnot, util.Spans.NoSpan))
221226

222227
/** If `ref` is a trackable capture ref, and `tp` has only covariant occurrences of a
223228
* universal capture set, replace all these occurrences by `{ref*}`. This implements
@@ -365,3 +370,21 @@ extension (tp: AnnotatedType)
365370
def isBoxed(using Context): Boolean = tp.annot match
366371
case ann: CaptureAnnotation => ann.boxed
367372
case _ => false
373+
374+
/** An extractor for `caps.reachCapability(ref)`, which is used to express a reach
375+
* capability as a tree in a @retains annotation.
376+
*/
377+
object ReachCapabilityApply:
378+
def unapply(tree: Apply)(using Context): Option[Tree] = tree match
379+
case Apply(reach, arg :: Nil) if reach.symbol == defn.Caps_reachCapability => Some(arg)
380+
case _ => None
381+
382+
/** An extractor for `ref @annotation.internal.reachCapability`, which is used to express
383+
* the reach capability `ref*` as a type.
384+
*/
385+
object ReachCapability:
386+
def unapply(tree: AnnotatedType)(using Context): Option[SingletonCaptureRef] = tree match
387+
case AnnotatedType(parent: SingletonCaptureRef, ann)
388+
if ann.symbol == defn.ReachCapabilityAnnot => Some(parent)
389+
case _ => None
390+

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

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ sealed abstract class CaptureSet extends Showable:
156156
case y: TermRef => !y.isReach && (y.prefix eq x)
157157
case _ => false
158158
|| x.match
159-
case x: TermRef if x.isReach => x.stripReach.subsumes(y.stripReach)
159+
case ReachCapability(x1) => x1.subsumes(y.stripReach)
160160
case _ => false
161161

162162
/** {x} <:< this where <:< is subcapturing, but treating all variables
@@ -500,18 +500,15 @@ object CaptureSet:
500500
private def levelOK(elem: CaptureRef)(using Context): Boolean =
501501
if elem.isRootCapability then !noUniversal
502502
else elem match
503-
case elem: TermRef =>
504-
if elem.isReach then levelOK(elem.stripReach)
505-
else if levelLimit.exists then
506-
var sym = elem.symbol
507-
if sym.isLevelOwner then sym = sym.owner
508-
levelLimit.isContainedIn(sym.levelOwner)
509-
else true
510-
case elem: ThisType =>
511-
if levelLimit.exists then
512-
levelLimit.isContainedIn(elem.cls.levelOwner)
513-
else true
514-
case elem: TermParamRef =>
503+
case elem: TermRef if levelLimit.exists =>
504+
var sym = elem.symbol
505+
if sym.isLevelOwner then sym = sym.owner
506+
levelLimit.isContainedIn(sym.levelOwner)
507+
case elem: ThisType if levelLimit.exists =>
508+
levelLimit.isContainedIn(elem.cls.levelOwner)
509+
case ReachCapability(elem1) =>
510+
levelOK(elem1)
511+
case _ =>
515512
true
516513

517514
def addDependent(cs: CaptureSet)(using Context, VarState): CompareResult =
@@ -1015,8 +1012,8 @@ object CaptureSet:
10151012
/** The capture set of the type underlying CaptureRef */
10161013
def ofInfo(ref: CaptureRef)(using Context): CaptureSet = ref match
10171014
case ref: TermRef if ref.isRootCapability => ref.singletonCaptureSet
1018-
case ref: TermRef if ref.isReach => deepCaptureSet(ref.prefix.widen)
1019-
.showing(i"Deep capture set of $ref: ${ref.prefix.widen} = $result", capt)
1015+
case ReachCapability(ref1) => deepCaptureSet(ref1.widen)
1016+
.showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt)
10201017
case _ => ofType(ref.underlying, followResult = true)
10211018

10221019
/** Capture set of a type */

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ object CheckCaptures:
130130
report.error(em"Singleton type $parent cannot have capture set", parent.srcPos)
131131
case _ =>
132132
for elem <- ann.retainedElems do
133-
elem.tpe match
133+
val elem1 = elem match
134+
case ReachCapabilityApply(arg) => arg
135+
case _ => elem
136+
elem1.tpe match
134137
case ref: CaptureRef =>
135138
if !ref.isTrackableRef then
136139
report.error(em"$elem cannot be tracked since it is not a parameter or local value", elem.srcPos)
@@ -1249,7 +1252,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12491252
val checker = new TypeTraverser:
12501253
private var allowed: SimpleIdentitySet[TermParamRef] = SimpleIdentitySet.empty
12511254

1252-
private def isAllowed(ref: CaptureRef): Boolean = ref.stripReach match
1255+
private def isAllowed(ref: CaptureRef): Boolean = ref match
12531256
case ref: TermParamRef => allowed.contains(ref)
12541257
case _ => true
12551258

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,8 @@ class Definitions {
312312
Final,
313313
bounds = TypeBounds.lower(AnyClass.thisType))
314314

315-
@tu lazy val Any_ccReach = enterMethod(AnyClass, nme.CC_REACH, AnyType, SyntheticArtifact | Final)
316-
317315
def AnyMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
318-
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest,
319-
Any_typeCast, Any_ccReach)
316+
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest, Any_typeCast)
320317

321318
@tu lazy val ObjectClass: ClassSymbol = {
322319
val cls = requiredClass("java.lang.Object")
@@ -981,6 +978,7 @@ class Definitions {
981978
@tu lazy val CapsModule: Symbol = requiredModule("scala.caps")
982979
@tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap")
983980
@tu lazy val Caps_Cap: TypeSymbol = CapsModule.requiredType("Cap")
981+
@tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability")
984982
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
985983
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
986984
@tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox")
@@ -1055,6 +1053,7 @@ class Definitions {
10551053
@tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName")
10561054
@tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs")
10571055
@tu lazy val SinceAnnot: ClassSymbol = requiredClass("scala.annotation.since")
1056+
@tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability")
10581057
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
10591058
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
10601059
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")

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

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,7 +2163,7 @@ object Types {
21632163
}
21642164

21652165
/** A trait for references in CaptureSets. These can be NamedTypes, ThisTypes or ParamRefs */
2166-
trait CaptureRef extends SingletonType:
2166+
trait CaptureRef extends TypeProxy, ValueType:
21672167
private var myCaptureSet: CaptureSet | Null = uninitialized
21682168
private var myCaptureSetRunId: Int = NoRunId
21692169
private var mySingletonCaptureSet: CaptureSet.Const | Null = null
@@ -2175,9 +2175,9 @@ object Types {
21752175
isTrackableRef && (isRootCapability || !captureSetOfInfo.isAlwaysEmpty)
21762176

21772177
/** Is this a reach reference of the form `x*`? */
2178-
def isReach(using Context): Boolean = false // overridden in TermRef
2178+
def isReach(using Context): Boolean = false // overridden in AnnotatedType
21792179

2180-
def stripReach(using Context): CaptureRef = this // overridden in TermRef
2180+
def stripReach(using Context): CaptureRef = this // overridden in AnnotatedType
21812181

21822182
/** Is this reference the generic root capability `cap` ? */
21832183
def isRootCapability(using Context): Boolean = false
@@ -2214,6 +2214,8 @@ object Types {
22142214

22152215
end CaptureRef
22162216

2217+
trait SingletonCaptureRef extends SingletonType, CaptureRef
2218+
22172219
/** A trait for types that bind other types that refer to them.
22182220
* Instances are: LambdaType, RecType.
22192221
*/
@@ -2877,7 +2879,7 @@ object Types {
28772879
*/
28782880
abstract case class TermRef(override val prefix: Type,
28792881
private var myDesignator: Designator)
2880-
extends NamedType, ImplicitRef, CaptureRef {
2882+
extends NamedType, ImplicitRef, SingletonCaptureRef {
28812883

28822884
type ThisType = TermRef
28832885
type ThisName = TermName
@@ -2913,21 +2915,12 @@ object Types {
29132915
|| symbol.is(ParamAccessor) && (prefix eq symbol.owner.thisType)
29142916
|| isRootCapability
29152917
) && !symbol.isOneOf(UnstableValueFlags)
2916-
|| isReach
2917-
2918-
override def isReach(using Context): Boolean =
2919-
name == nme.CC_REACH && symbol == defn.Any_ccReach
2920-
2921-
override def stripReach(using Context): CaptureRef =
2922-
if isReach then prefix.asInstanceOf[CaptureRef] else this
29232918

29242919
override def isRootCapability(using Context): Boolean =
29252920
name == nme.CAPTURE_ROOT && symbol == defn.captureRoot
29262921

29272922
override def normalizedRef(using Context): CaptureRef =
2928-
if isReach then TermRef(stripReach.normalizedRef, name, denot)
2929-
else if isTrackableRef then symbol.termRef
2930-
else this
2923+
if isTrackableRef then symbol.termRef else this
29312924
}
29322925

29332926
abstract case class TypeRef(override val prefix: Type,
@@ -3066,7 +3059,8 @@ object Types {
30663059
* Note: we do not pass a class symbol directly, because symbols
30673060
* do not survive runs whereas typerefs do.
30683061
*/
3069-
abstract case class ThisType(tref: TypeRef) extends CachedProxyType, CaptureRef {
3062+
abstract case class ThisType(tref: TypeRef)
3063+
extends CachedProxyType, SingletonCaptureRef {
30703064
def cls(using Context): ClassSymbol = tref.stableInRunSymbol match {
30713065
case cls: ClassSymbol => cls
30723066
case _ if ctx.mode.is(Mode.Interactive) => defn.AnyClass // was observed to happen in IDE mode
@@ -4679,7 +4673,8 @@ object Types {
46794673
/** Only created in `binder.paramRefs`. Use `binder.paramRefs(paramNum)` to
46804674
* refer to `TermParamRef(binder, paramNum)`.
46814675
*/
4682-
abstract case class TermParamRef(binder: TermLambda, paramNum: Int) extends ParamRef, CaptureRef {
4676+
abstract case class TermParamRef(binder: TermLambda, paramNum: Int)
4677+
extends ParamRef, SingletonCaptureRef {
46834678
type BT = TermLambda
46844679
def kindString: String = "Term"
46854680
def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum)
@@ -5386,7 +5381,7 @@ object Types {
53865381
// ----- Annotated and Import types -----------------------------------------------
53875382

53885383
/** An annotated type tpe @ annot */
5389-
abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, ValueType {
5384+
abstract case class AnnotatedType(parent: Type, annot: Annotation) extends CachedProxyType, CaptureRef {
53905385

53915386
override def underlying(using Context): Type = parent
53925387

@@ -5415,6 +5410,23 @@ object Types {
54155410
isRefiningCache
54165411
}
54175412

5413+
override def isTrackableRef(using Context) =
5414+
isReach && parent.isTrackableRef
5415+
5416+
/** Is this a reach reference of the form `x*`? */
5417+
override def isReach(using Context): Boolean =
5418+
annot.symbol == defn.ReachCapabilityAnnot
5419+
5420+
override def stripReach(using Context): SingletonCaptureRef =
5421+
(if isReach then parent else this).asInstanceOf[SingletonCaptureRef]
5422+
5423+
override def normalizedRef(using Context): CaptureRef =
5424+
if isReach then AnnotatedType(stripReach.normalizedRef, annot) else this
5425+
5426+
override def captureSet(using Context): CaptureSet =
5427+
if isReach then super.captureSet
5428+
else CaptureSet.ofType(this, followResult = false)
5429+
54185430
// equals comes from case class; no matching override is needed
54195431

54205432
override def computeHash(bs: Binders): Int =

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,7 @@ object Parsers {
14791479
if isIdent(nme.raw.STAR) then
14801480
in.nextToken()
14811481
atSpan(startOffset(id)):
1482-
Select(id, nme.CC_REACH)
1482+
PostfixOp(id, Ident(nme.CC_REACH))
14831483
else id
14841484

14851485
/** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import util.SourcePosition
1515
import scala.util.control.NonFatal
1616
import scala.annotation.switch
1717
import config.{Config, Feature}
18-
import cc.{CapturingType, RetainingType, CaptureSet, isBoxed, levelOwner, retainedElems}
18+
import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, isBoxed, levelOwner, retainedElems}
1919

2020
class PlainPrinter(_ctx: Context) extends Printer {
2121

@@ -377,8 +377,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
377377
def toTextRef(tp: SingletonType): Text = controlled {
378378
tp match {
379379
case tp: TermRef =>
380-
if tp.isReach then toTextRef(tp.stripReach) ~ "*"
381-
else toTextPrefixOf(tp) ~ selectionString(tp)
380+
toTextPrefixOf(tp) ~ selectionString(tp)
382381
case tp: ThisType =>
383382
nameString(tp.cls) + ".this"
384383
case SuperType(thistpe: SingletonType, _) =>
@@ -404,6 +403,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
404403
homogenize(tp) match
405404
case tp: TermRef if tp.symbol == defn.captureRoot => Str("cap")
406405
case tp: SingletonType => toTextRef(tp)
406+
case ReachCapability(tp1) => toTextRef(tp1) ~ "*"
407407
case _ => toText(tp)
408408

409409
protected def isOmittablePrefix(sym: Symbol): Boolean =

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
703703
val opPrec = parsing.precedence(op.name)
704704
changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) }
705705
case PostfixOp(l, op) =>
706-
changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) }
706+
if op.name == nme.CC_REACH then
707+
changePrec(DotPrec) { toText(l) ~ "*" }
708+
else
709+
changePrec(InfixPrec) { toText(l) ~ " " ~ toText(op) }
707710
case PrefixOp(op, r) =>
708711
changePrec(DotPrec) { toText(op) ~ " " ~ toText(r) }
709712
case Parens(t) =>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scala.annotation
2+
package internal
3+
4+
/** An annotation that marks a capture ref as a reach capability.
5+
* `x*` is encoded as `x.type @reachCapability`
6+
*/
7+
class reachCapability extends StaticAnnotation
8+

library/src/scala/caps.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import annotation.experimental
1515

1616
given Cap = cap
1717

18+
/** Reach capabilities x* which appear as terms in @retains annotations are encoded
19+
* as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets
20+
* they are represented as `x.type @annotation.internal.reachCapability`.
21+
*/
22+
extension (x: Any) def reachCapability: Any = x
23+
1824
object unsafe:
1925

2026
extension [T](x: T)

tests/run-macros/i6518.check

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
##
33
$asInstanceOf$
44
$isInstanceOf$
5-
$reach
65
==
76
andThen
87
apply

0 commit comments

Comments
 (0)