Skip to content

Commit af38bc0

Browse files
committed
Treat capability classes as special capturing types
1 parent a356581 commit af38bc0

File tree

15 files changed

+153
-29
lines changed

15 files changed

+153
-29
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension (tp: Type)
7070
* The identity for all other types.
7171
*/
7272
def boxed(using Context): Type = tp.dealias match
73-
case tp @ CapturingType(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty =>
73+
case tp @ CapturingType.Annotated(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty =>
7474
tp.annot match
7575
case ann: CaptureAnnotation =>
7676
ann.boxedType(tp)
@@ -79,6 +79,8 @@ extension (tp: Type)
7979
case None => ann.tree.putAttachment(BoxedType, BoxedTypeCache())
8080
case _ =>
8181
ann.tree.attachment(BoxedType)(tp)
82+
case CapturingType.Capability(parent, cs) =>
83+
CapturingType(parent, cs).boxed
8284
case tp: RealTypeBounds =>
8385
tp.derivedTypeBounds(tp.lo.boxed, tp.hi.boxed)
8486
case _ =>
@@ -190,6 +192,12 @@ extension (tp: Type)
190192
case _ =>
191193
false
192194

195+
def isCapabilityBase(using Context): Boolean = tp match
196+
case tp: NamedType =>
197+
val sym = tp.classSymbol
198+
sym.exists && sym.asClass.isCapabilityBase
199+
case _ => false
200+
193201
extension (cls: ClassSymbol)
194202

195203
def pureBaseClass(using Context): Option[Symbol] =
@@ -200,6 +208,9 @@ extension (cls: ClassSymbol)
200208
selfType.exists && selfType.captureSet.isAlwaysEmpty
201209
})
202210

211+
def isCapabilityBase(using Context): Boolean =
212+
cls.is(Flags.CapabilityBase)
213+
203214
extension (sym: Symbol)
204215

205216
/** A class is pure if:
@@ -250,6 +261,11 @@ extension (tp: AnnotatedType)
250261
case ann: CaptureAnnotation => ann.boxed
251262
case _ => false
252263

264+
extension (tp: Type)
265+
def isBoxed(using Context): Boolean = tp match
266+
case tp: AnnotatedType => tp.isBoxed
267+
case _ => false
268+
253269
extension (ts: List[Type])
254270
/** Equivalent to ts.mapconserve(_.boxedUnlessFun(tycon)) but more efficient where
255271
* it is the identity.

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

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package dotc
33
package cc
44

55
import core.*
6+
import Decorators.*
67
import Types.*, Symbols.*, Contexts.*
8+
import NameKinds.UniqueName
9+
import util.SimpleIdentityMap
710

811
/** A (possibly boxed) capturing type. This is internally represented as an annotated type with a @retains
912
* or @retainsByName annotation, but the extractor will succeed only at phase CheckCaptures.
@@ -40,12 +43,14 @@ object CapturingType:
4043
/** An extractor that succeeds only during CheckCapturingPhase. Boxing statis is
4144
* returned separately by CaptureOps.isBoxed.
4245
*/
43-
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
46+
def unapply(tp: Type)(using Context): Option[(Type, CaptureSet)] =
4447
if ctx.phase == Phases.checkCapturesPhase
45-
&& tp.annot.symbol == defn.RetainsAnnot
4648
&& !ctx.mode.is(Mode.IgnoreCaptures)
4749
then
48-
EventuallyCapturingType.unapply(tp)
50+
tp match
51+
case Annotated(parent, cs) => Some(parent, cs)
52+
case Capability(parent, cs) => Some(parent, cs)
53+
case _ => None
4954
else None
5055

5156
/** Check whether a type is uncachable when computing `baseType`.
@@ -58,15 +63,69 @@ object CapturingType:
5863
ctx.phase == Phases.checkCapturesPhase &&
5964
(Setup.isDuringSetup || ctx.mode.is(Mode.IgnoreCaptures) && tp.isEventuallyCapturingType)
6065

66+
object Annotated:
67+
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
68+
if ctx.phase == Phases.checkCapturesPhase
69+
&& !ctx.mode.is(Mode.IgnoreCaptures)
70+
&& tp.annot.symbol == defn.RetainsAnnot
71+
then
72+
EventuallyCapturingType.unapplyAnnot(tp)
73+
else None
74+
75+
object Capability:
76+
def unapply(tp: Type)(using Context): Option[(Type, CaptureSet)] =
77+
if ctx.phase == Phases.checkCapturesPhase
78+
&& !ctx.mode.is(Mode.IgnoreCaptures)
79+
then
80+
EventuallyCapturingType.unapplyCap(tp)
81+
else None
82+
6183
end CapturingType
6284

85+
6386
/** An extractor for types that will be capturing types at phase CheckCaptures. Also
6487
* included are types that indicate captures on enclosing call-by-name parameters
6588
* before phase ElimByName.
6689
*/
6790
object EventuallyCapturingType:
6891

69-
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
92+
object Annotated:
93+
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] = unapplyAnnot(tp)
94+
95+
object Capability:
96+
def unapply(tp: TypeRef)(using Context): Option[(Type, CaptureSet)] = unapplyCap(tp)
97+
98+
private var pureCapClassSymCache: SimpleIdentityMap[ClassSymbol, ClassSymbol] = SimpleIdentityMap.empty
99+
100+
private def createPureSymbolOf(csym: ClassSymbol)(using Context): ClassSymbol =
101+
csym.copy(flags = csym.flags | Flags.CapabilityBase).asClass
102+
103+
private def pureSymbolOf(csym: ClassSymbol)(using Context): ClassSymbol =
104+
pureCapClassSymCache(csym) match
105+
case psym: ClassSymbol => psym
106+
case null =>
107+
val sym = createPureSymbolOf(csym)
108+
pureCapClassSymCache = pureCapClassSymCache.updated(csym, sym)
109+
sym
110+
111+
def unapply(tp: Type)(using Context): Option[(Type, CaptureSet)] =
112+
tp match
113+
case tp: AnnotatedType => unapplyAnnot(tp)
114+
case _ => unapplyCap(tp)
115+
116+
def unapplyCap(tp: Type)(using Context): Option[(Type, CaptureSet)] =
117+
if tp.classSymbol.hasAnnotation(defn.CapabilityAnnot) && !tp.classSymbol.is(Flags.CapabilityBase) then
118+
val sym = tp.classSymbol
119+
val psym = pureSymbolOf(sym.asClass)
120+
tp match
121+
case tp: TypeRef => Some((psym.typeRef, CaptureSet.universal))
122+
case tp: AppliedType =>
123+
Some((tp.derivedAppliedType(psym.typeRef, tp.args), CaptureSet.universal))
124+
case _ => None
125+
else None
126+
// None
127+
128+
def unapplyAnnot(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
70129
val sym = tp.annot.symbol
71130
if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then
72131
tp.annot match

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ class CheckCaptures extends Recheck, SymTransformer:
656656
expected
657657

658658
/** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions
659-
*
659+
*
660660
* @param alwaysConst always make capture set variables constant after adaptation
661661
*/
662662
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, alwaysConst: Boolean = false)(using Context): Type =

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import transform.Recheck.*
1212
import CaptureSet.IdentityCaptRefMap
1313
import Synthetics.isExcluded
1414
import util.Property
15+
import reporting.trace
1516

1617
/** A tree traverser that prepares a compilation unit to be capture checked.
1718
* It does the following:
@@ -408,8 +409,12 @@ extends tpd.TreeTraverser:
408409
traverse(tree.rhs)
409410
case tree @ TypeApply(fn, args) =>
410411
traverse(fn)
412+
val isErasedValue = fn match
413+
case Ident(tp) =>
414+
fn.symbol eq defn.Compiletime_erasedValue
415+
case _ => false
411416
for case arg: TypeTree <- args do
412-
transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed
417+
transformTT(arg, boxed = !isErasedValue, exact = false) // type arguments in type applications are boxed
413418
case _ =>
414419
traverseChildren(tree)
415420
tree match

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1185,7 +1185,7 @@ class Definitions {
11851185
*/
11861186
object ByNameFunction:
11871187
def apply(tp: Type)(using Context): Type = tp match
1188-
case tp @ EventuallyCapturingType(tp1, refs) if tp.annot.symbol == RetainsByNameAnnot =>
1188+
case tp @ EventuallyCapturingType.Annotated(tp1, refs) if tp.annot.symbol == RetainsByNameAnnot =>
11891189
CapturingType(apply(tp1), refs)
11901190
case _ =>
11911191
defn.ContextFunction0.typeRef.appliedTo(tp :: Nil)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ object Flags {
377377
/** Symbol cannot be found as a member during typer */
378378
val (Invisible @ _, _, _) = newFlags(45, "<invisible>")
379379

380+
/** Symbol represents the pure base class of a capability class */
381+
val (CapabilityBase @ _, _, _) = newFlags(46, "<capability-base>")
382+
380383
// ------------ Flags following this one are not pickled ----------------------------------
381384

382385
/** Symbol is not a member of its owner */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2238,7 +2238,7 @@ object SymDenotations {
22382238
case tp: TypeParamRef => // uncachable, since baseType depends on context bounds
22392239
recur(TypeComparer.bounds(tp).hi)
22402240

2241-
case CapturingType(parent, refs) =>
2241+
case CapturingType.Annotated(parent, refs) =>
22422242
tp.derivedCapturingType(recur(parent), refs)
22432243

22442244
case tp: TypeProxy =>

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import typer.ProtoTypes.constrained
2323
import typer.Applications.productSelectorTypes
2424
import reporting.trace
2525
import annotation.constructorOnly
26-
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure}
26+
import cc.{CapturingType, derivedCapturingType, CaptureSet, stripCapturing, isBoxedCapturing, boxed, boxedUnlessFun, boxedIfTypeParam, isAlwaysPure, isCapabilityBase}
2727

2828
/** Provides methods to compare types.
2929
*/
@@ -302,7 +302,14 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
302302
// For convenience we want X$ <:< X.type
303303
// This is safe because X$ self-type is X.type
304304
sym1 = sym1.companionModule
305-
if ((sym1 ne NoSymbol) && (sym1 eq sym2))
305+
306+
def isMatchingSymbols =
307+
(sym1 eq sym2) || ctx.phase == Phases.checkCapturesPhase && {
308+
sym1.name == sym2.name &&
309+
sym1.is(Flags.CapabilityBase) || sym2.is(Flags.CapabilityBase)
310+
}
311+
312+
if ((sym1 ne NoSymbol) && isMatchingSymbols)
306313
ctx.erasedTypes ||
307314
sym1.isStaticOwner ||
308315
isSubPrefix(tp1.prefix, tp2.prefix) ||
@@ -1137,7 +1144,17 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
11371144
/** Subtype test for the hk application `tp2 = tycon2[args2]`.
11381145
*/
11391146
def compareAppliedType2(tp2: AppliedType, tycon2: Type, args2: List[Type]): Boolean = {
1140-
val tparams = tycon2.typeParams
1147+
1148+
def getTypeParams: List[TypeParamInfo] =
1149+
if ctx.phase != Phases.checkCapturesPhase then
1150+
tycon2.typeParams
1151+
else tycon2.match
1152+
case tycon2: TypeRef if tycon2.isCapabilityBase =>
1153+
tycon2.symbol.info.typeParams
1154+
case _ =>
1155+
tycon2.typeParams
1156+
1157+
val tparams = getTypeParams
11411158
if (tparams.isEmpty) return false // can happen for ill-typed programs, e.g. neg/tcpoly_overloaded.scala
11421159

11431160
/** True if `tp1` and `tp2` have compatible type constructors and their
@@ -1215,8 +1232,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
12151232
&& ctx.gadt.contains(tycon2sym)
12161233
&& ctx.gadt.isLess(tycon1sym, tycon2sym)
12171234

1235+
def isMatchingSymbols =
1236+
(tycon1sym == tycon2sym) || {
1237+
ctx.phase == Phases.checkCapturesPhase &&
1238+
tycon1sym.name == tycon2sym.name &&
1239+
tycon1sym.is(Flags.CapabilityBase) || tycon2sym.is(Flags.CapabilityBase)
1240+
}
1241+
12181242
val res = (
1219-
tycon1sym == tycon2sym && isSubPrefix(tycon1.prefix, tycon2.prefix)
1243+
isMatchingSymbols && isSubPrefix(tycon1.prefix, tycon2.prefix)
12201244
|| tycon1sym.byGadtBounds(b => isSubTypeWhenFrozen(b.hi, tycon2))
12211245
|| tycon2sym.byGadtBounds(b => isSubTypeWhenFrozen(tycon1, b.lo))
12221246
|| byGadtOrdering

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ object TypeOps:
296296
tp1 match {
297297
case tp1: RecType =>
298298
return tp1.rebind(approximateOr(tp1.parent, tp2))
299-
case CapturingType(parent1, refs1) =>
299+
case CapturingType.Annotated(parent1, refs1) =>
300300
return tp1.derivedCapturingType(approximateOr(parent1, tp2), refs1)
301301
case err: ErrorType =>
302302
return err
@@ -305,7 +305,7 @@ object TypeOps:
305305
tp2 match {
306306
case tp2: RecType =>
307307
return tp2.rebind(approximateOr(tp1, tp2.parent))
308-
case CapturingType(parent2, refs2) =>
308+
case CapturingType.Annotated(parent2, refs2) =>
309309
return tp2.derivedCapturingType(approximateOr(tp1, parent2), refs2)
310310
case err: ErrorType =>
311311
return err

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4974,7 +4974,7 @@ object Types {
49744974
else if (clsd.is(Module)) givenSelf
49754975
else if (ctx.erasedTypes) appliedRef
49764976
else givenSelf match
4977-
case givenSelf @ EventuallyCapturingType(tp, _) =>
4977+
case givenSelf @ EventuallyCapturingType.Annotated(tp, _) =>
49784978
givenSelf.derivedAnnotatedType(tp & appliedRef, givenSelf.annot)
49794979
case _ =>
49804980
AndType(givenSelf, appliedRef)

0 commit comments

Comments
 (0)