Skip to content

Commit ea079a7

Browse files
committed
Drop @capability annotations
Replace with references that inherit trait `Capability`.
1 parent 67101ca commit ea079a7

Some content is hidden

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

62 files changed

+382
-130
lines changed

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

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

206+
/** Does type derive from caps.Capability?, which means it references of this
207+
* 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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,8 @@ object CaptureSet:
10511051
case tp: TermParamRef =>
10521052
tp.captureSet
10531053
case tp: TypeRef =>
1054-
if tp.typeSymbol == defn.Caps_Cap then universal else empty
1054+
if tp.derivesFromCapability then universal // TODO: maybe return another value that indicates that the underltinf ref is maximal?
1055+
else empty
10551056
case _: TypeParamRef =>
10561057
empty
10571058
case CapturingType(parent, refs) =>

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,8 @@ class CheckCaptures extends Recheck, SymTransformer:
537537
*/
538538
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
539539
var refined: Type = core
540-
var allCaptures: CaptureSet = if setup.isCapabilityClassRef(core)
541-
then CaptureSet.universal else initCs
540+
var allCaptures: CaptureSet =
541+
if core.derivesFromCapability then CaptureSet.universal else initCs
542542
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
543543
val getter = cls.info.member(getterName).suchThat(_.is(ParamAccessor)).symbol
544544
if getter.termRef.isTracked && !getter.is(Private) then
@@ -572,8 +572,10 @@ class CheckCaptures extends Recheck, SymTransformer:
572572
val TypeApply(fn, args) = tree
573573
val polyType = atPhase(thisPhase.prev):
574574
fn.tpe.widen.asInstanceOf[TypeLambda]
575+
def isExempt(sym: Symbol) =
576+
sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue
575577
for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do
576-
if !tree.symbol.isTypeTestOrCast then
578+
if !isExempt(tree.symbol) then
577579
def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else ""
578580
disallowRootCapabilitiesIn(arg.knownType, NoSymbol,
579581
i"Sealed type variable $pname", "be instantiated to",
@@ -1305,7 +1307,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13051307
case ref: TermParamRef
13061308
if !allowed.contains(ref) && !seen.contains(ref) =>
13071309
seen += ref
1308-
if ref.underlying.isRef(defn.Caps_Cap) then
1310+
if ref.underlying.isRef(defn.Caps_Capability) then
13091311
report.error(i"escaping local reference $ref", tree.srcPos)
13101312
else
13111313
val widened = ref.captureSetOfInfo

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
7777
def isCapabilityClassRef(tp: Type)(using Context): Boolean = tp.dealiasKeepAnnots match
7878
case _: TypeRef | _: AppliedType =>
7979
val sym = tp.classSymbol
80-
def checkSym: Boolean =
81-
sym.hasAnnotation(defn.CapabilityAnnot)
82-
|| sym.info.parents.exists(hasUniversalCapability)
80+
def checkSym: Boolean = sym.info.parents.exists(hasUniversalCapability)
8381
sym.isClass && capabilityClassMap.getOrElseUpdate(sym, checkSym)
8482
case _ => false
8583

@@ -594,7 +592,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
594592
if sym.isClass then
595593
!sym.isPureClass
596594
else
597-
sym != defn.Caps_Cap && instanceCanBeImpure(tp.superType)
595+
sym != defn.Caps_Capability && instanceCanBeImpure(tp.superType)
598596
case tp: (RefinedOrRecType | MatchType) =>
599597
instanceCanBeImpure(tp.underlying)
600598
case tp: AndType =>

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

Lines changed: 2 additions & 3 deletions
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`.

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,7 +3014,8 @@ object Types extends TypeUtils {
30143014
name == nme.CAPTURE_ROOT && symbol == defn.captureRoot
30153015

30163016
override def isMaxCapability(using Context): Boolean =
3017-
widen.derivesFrom(defn.Caps_Cap) && symbol.isStableMember
3017+
import cc.*
3018+
this.derivesFromCapability && symbol.isStableMember
30183019

30193020
override def normalizedRef(using Context): CaptureRef =
30203021
if isTrackableRef then symbol.termRef else this
@@ -4815,7 +4816,9 @@ object Types extends TypeUtils {
48154816
def kindString: String = "Term"
48164817
def copyBoundType(bt: BT): Type = bt.paramRefs(paramNum)
48174818
override def isTrackableRef(using Context) = true
4818-
override def isMaxCapability(using Context) = widen.derivesFrom(defn.Caps_Cap)
4819+
override def isMaxCapability(using Context) =
4820+
import cc.*
4821+
this.derivesFromCapability
48194822
}
48204823

48214824
private final class TermParamRefImpl(binder: TermLambda, paramNum: Int) extends TermParamRef(binder, paramNum)

library/src/scala/CanThrow.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import annotation.{implicitNotFound, experimental, capability}
66
* experimental.saferExceptions feature, a `throw Ex()` expression will require
77
* a given of class `CanThrow[Ex]` to be available.
88
*/
9-
@experimental @capability
9+
@experimental
1010
@implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - Adding a using clause `(using CanThrow[${E}])` to the definition of the enclosing method\n - Adding `throws ${E}` clause after the result type of the enclosing method\n - Wrapping this piece of code with a `try` block that catches ${E}")
11-
erased class CanThrow[-E <: Exception]
11+
erased class CanThrow[-E <: Exception] extends caps.Capability
1212

1313
@experimental
1414
object unsafeExceptions:

library/src/scala/annotation/capability.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ import annotation.experimental
1111
* THere, the capture set of any instance of `CanThrow` is assumed to be
1212
* `{*}`.
1313
*/
14-
@experimental final class capability extends StaticAnnotation
14+
@experimental
15+
@deprecated("To make a class a capability, let it derive from the `Capability` trait instead")
16+
final class capability extends StaticAnnotation

library/src/scala/caps.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import annotation.experimental
44

55
@experimental object caps:
66

7-
class Cap // should be @erased
7+
trait Capability // should be @erased
8+
9+
/** The universal capture reference */
10+
val cap: Capability = new Capability() {}
811

912
/** The universal capture reference (deprecated) */
1013
@deprecated("Use `cap` instead")
11-
val `*`: Cap = cap
14+
val `*`: Capability = cap
1215

13-
/** The universal capture reference */
14-
val cap: Cap = Cap()
16+
@deprecated("Use `Capability` instead")
17+
type Cap = Capability
1518

1619
/** Reach capabilities x* which appear as terms in @retains annotations are encoded
1720
* as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets

tests/disabled/pos/lazylist.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ object LazyNil extends LazyList[Nothing]:
3434
def map[A, B](xs: {*} LazyList[A], f: {*} A => B): {f, xs} LazyList[B] =
3535
xs.map(f)
3636

37-
@annotation.capability class Cap
37+
class Cap extends caps.Capability
3838

3939
def test(cap1: Cap, cap2: Cap, cap3: Cap) =
4040
def f[T](x: LazyList[T]): LazyList[T] = if cap1 == cap1 then x else LazyNil

tests/neg-custom-args/captures/box-unsoundness.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@annotation.capability
21
class CanIO { def use(): Unit = () }
32
def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x)
43
def test(io: CanIO^): Unit =

tests/neg-custom-args/captures/byname.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Error: tests/neg-custom-args/captures/byname.scala:19:5 -------------------------------------------------------------
22
19 | h(g()) // error
33
| ^^^
4-
| reference (cap2 : Cap^) is not included in the allowed capture set {cap1}
4+
| reference (cap2 : Cap) is not included in the allowed capture set {cap1}
55
| of an enclosing function literal with expected type () ?->{cap1} I
66
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 -----------------------------------------
77
4 | def f() = if cap1 == cap1 then g else g // error

tests/neg-custom-args/captures/byname.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class Cap
1+
class Cap extends caps.Capability
22

33
def test(cap1: Cap, cap2: Cap) =
44
def f() = if cap1 == cap1 then g else g // error

tests/neg-custom-args/captures/capt-box-env.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class Cap
1+
class Cap extends caps.Capability
22

33
class Pair[+A, +B](x: A, y: B):
44
def fst: A = x

tests/neg-custom-args/captures/capt-box.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class Cap
1+
class Cap extends caps.Capability
22

33
def test(x: Cap) =
44

tests/neg-custom-args/captures/capt-wf2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class C
1+
class C extends caps.Capability
22

33
def test(c: C) =
44
var x: Any^{c} = ???

tests/neg-custom-args/captures/caseclass/Test_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class C
1+
class C extends caps.Capability
22
def test(c: C) =
33
val pure: () -> Unit = () => ()
44
val impure: () => Unit = pure

tests/neg-custom-args/captures/cc-this.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class Cap
1+
class Cap extends caps.Capability
22

33
def eff(using Cap): Unit = ()
44

tests/neg-custom-args/captures/cc-this2.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
-- Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:3:8 --------------------------------------------------------
33
3 | this: D^ => // error
44
| ^^
5-
|reference (caps.cap : caps.Cap) captured by this self type is not included in the allowed capture set {} of pure base class class C
5+
|reference (caps.cap : caps.Capability) captured by this self type is not included in the allowed capture set {} of pure base class class C
66
-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 -----------------------------------
77
2 |class D extends C: // error
88
| ^

tests/neg-custom-args/captures/cc-this3.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@annotation.capability class Cap
1+
class Cap extends caps.Capability
22

33
def eff(using Cap): Unit = ()
44

tests/neg-custom-args/captures/cc-this5.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ----------------------------------------------------------
22
16 | def f = println(c) // error
33
| ^
4-
| (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {}
4+
| (c : Cap) cannot be referenced here; it is not included in the allowed capture set {}
55
| of the enclosing class A
66
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 -------------------------------------
77
21 | val x: A = this // error

tests/neg-custom-args/captures/cc-this5.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class C:
22
val x: C = this
33

4-
@annotation.capability class Cap
4+
class Cap extends caps.Capability
55

66
def foo(c: Cap) =
77
object D extends C: // error
@@ -17,5 +17,5 @@ def test(c: Cap) =
1717

1818
def test2(c: Cap) =
1919
class A:
20-
def f = println(c)
20+
def f = println(c)
2121
val x: A = this // error

tests/neg-custom-args/captures/class-constr.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import annotation.{capability, constructorOnly}
22

3-
@capability class Cap
3+
class Cap extends caps.Capability
44

55
class C(x: Cap, @constructorOnly y: Cap)
66

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:64:8 -------------------------
2+
63 | Result:
3+
64 | Future: // error, type mismatch
4+
| ^
5+
| Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}]
6+
| Required: Result[Future[T], Nothing]
7+
65 | fr.await.ok
8+
|--------------------------------------------------------------------------------------------------------------------
9+
|Inline stack trace
10+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11+
|This location contains code that was inlined from effect-swaps-explicit.scala:41
12+
41 | boundary(Ok(body))
13+
| ^^^^^^^^
14+
--------------------------------------------------------------------------------------------------------------------
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:74:10 ------------------------
18+
74 | Future: fut ?=> // error: type mismatch
19+
| ^
20+
| Found: Future[box T^?]^{fr, lbl}
21+
| Required: Future[box T^?]^?
22+
75 | fr.await.ok
23+
|
24+
| longer explanation available when compiling with `-explain`
25+
-- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 ---------------------------------------------
26+
68 | Result.make: //lbl ?=> // error, escaping label from Result
27+
| ^^^^^^^^^^^
28+
|local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^):
29+
| box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import annotation.capability
2+
3+
object boundary:
4+
5+
final class Label[-T] // extends caps.Capability
6+
7+
/** Abort current computation and instead return `value` as the value of
8+
* the enclosing `boundary` call that created `label`.
9+
*/
10+
def break[T](value: T)(using label: Label[T]^): Nothing = ???
11+
12+
def apply[T](body: Label[T]^ ?=> T): T = ???
13+
end boundary
14+
15+
import boundary.{Label, break}
16+
17+
trait Async extends caps.Capability
18+
object Async:
19+
def blocking[T](body: Async ?=> T): T = ???
20+
21+
class Future[+T]:
22+
this: Future[T]^ =>
23+
def await(using Async): T = ???
24+
object Future:
25+
def apply[T](op: Async ?=> T)(using Async): Future[T]^{op} = ???
26+
27+
enum Result[+T, +E]:
28+
case Ok[+T](value: T) extends Result[T, Nothing]
29+
case Err[+E](error: E) extends Result[Nothing, E]
30+
31+
32+
object Result:
33+
extension [T, E](r: Result[T, E]^)(using Label[Err[E]]^)
34+
35+
/** `_.ok` propagates Err to current Label */
36+
def ok: T = r match
37+
case Ok(value) => value
38+
case Err(value) => break[Err[E]](Err(value))
39+
40+
transparent inline def apply[T, E](inline body: Label[Result[T, E]]^ ?=> T): Result[T, E] =
41+
boundary(Ok(body))
42+
43+
// same as apply, but not an inline method
44+
def make[T, E](body: Label[Result[T, E]]^ ?=> T): Result[T, E] =
45+
boundary(Ok(body))
46+
47+
end Result
48+
49+
def test[T, E](using Async) =
50+
import Result.*
51+
Async.blocking: async ?=>
52+
val good1: List[Future[Result[T, E]]] => Future[Result[List[T], E]] = frs =>
53+
Future:
54+
Result:
55+
frs.map(_.await.ok) // OK
56+
57+
val good2: Result[Future[T], E] => Future[Result[T, E]] = rf =>
58+
Future:
59+
Result:
60+
rf.ok.await // OK, Future argument has type Result[T]
61+
62+
def fail3(fr: Future[Result[T, E]]^) =
63+
Result:
64+
Future: // error, type mismatch
65+
fr.await.ok
66+
67+
def fail4[T, E](fr: Future[Result[T, E]]^) =
68+
Result.make: //lbl ?=> // error, escaping label from Result
69+
Future: fut ?=>
70+
fr.await.ok
71+
72+
def fail5[T, E](fr: Future[Result[T, E]]^) =
73+
Result.make[Future[T], E]: lbl ?=>
74+
Future: fut ?=> // error: type mismatch
75+
fr.await.ok
76+

0 commit comments

Comments
 (0)