Skip to content

Commit 6bf4483

Browse files
authored
Refine rules for capture parameters and members (#22000)
This PR refines rules for capture set variables (parameters and members). Fix #21999, #22005, #22030 ## Add requirements for capture set variables When a capture set is encoded as a type, the type must refer to `CapSet` and bounded by `>: CapSet <: CapSet^`. An unbounded capture parameter would be `C >: CapSet <: CapSet^`, which can be desugared from `C^`. ```scala def f[C^](io: IO^{C^}) = ??? // becomes def f[C >: CapSet <: CapSet^](io: IO^{C^}) = ??? ``` We may consider the similar desugaring for type member in the future: ```scala class A: type C^ // becomes class A: type C >: CapSet <: CapSet^ ``` Then, constaints between capture variables become possible: ```scala def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) = ??? // Z is still bounded by >: CapSet <: CapSet^ ``` Update definitions in the library `caps.scala`, such that a type following the rule can be used inside a capture set. ```scala // Rule out C^{(Nothing)^} during typer def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] ``` ## Add cases to handle `CapSet` in `subsumes` ``` * X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y * Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y * X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y * Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y * Contains[X, y] ==> X subsumes y ``` ## Fix some issues related to overriding When deciding whether a class has a non-trivial self type, we look at the underlying type without capture set. [test_scala2_library_tasty]
2 parents 34f3993 + 6866bd1 commit 6bf4483

22 files changed

+193
-63
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
527527
def makeCapsOf(tp: RefTree)(using Context): Tree =
528528
TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil)
529529

530-
def makeCapsBound()(using Context): Tree =
531-
makeRetaining(
530+
// Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]`
531+
def makeCapsBound()(using Context): TypeBoundsTree =
532+
TypeBoundsTree(
532533
Select(scalaDot(nme.caps), tpnme.CapSet),
533-
Nil, tpnme.retainsCap)
534+
makeRetaining(
535+
Select(scalaDot(nme.caps), tpnme.CapSet),
536+
Nil, tpnme.retainsCap))
534537

535538
def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
536539
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte
4242
case cr: TermRef => ref(cr)
4343
case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr)
4444
case cr: ThisType => This(cr.cls)
45+
// TODO: Will crash if the type is an annotated type, for example `cap?`
4546
}
4647
val arg = repeated(elems, TypeTree(defn.AnyType))
4748
New(symbol.typeRef, arg :: Nil)

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,19 @@ trait CaptureRef extends TypeProxy, ValueType:
9393
final def invalidateCaches() =
9494
myCaptureSetRunId = NoRunId
9595

96-
/** x subsumes x
97-
* this subsumes this.f
96+
/** x subsumes x
97+
* x =:= y ==> x subsumes y
98+
* x subsumes y ==> x subsumes y.f
9899
* x subsumes y ==> x* subsumes y, x subsumes y?
99100
* x subsumes y ==> x* subsumes y*, x? subsumes y?
100101
* x: x1.type /\ x1 subsumes y ==> x subsumes y
101-
* TODO: Document path cases
102+
* X = CapSet^cx, exists rx in cx, rx subsumes y ==> X subsumes y
103+
* Y = CapSet^cy, forall ry in cy, x subsumes ry ==> x subsumes Y
104+
* X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y
105+
* Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y
106+
* Contains[X, y] ==> X subsumes y
107+
*
108+
* TODO: Document cases with more comments.
102109
*/
103110
final def subsumes(y: CaptureRef)(using Context): Boolean =
104111

@@ -135,14 +142,29 @@ trait CaptureRef extends TypeProxy, ValueType:
135142
case _ => false
136143
|| viaInfo(y.info)(subsumingRefs(this, _))
137144
case MaybeCapability(y1) => this.stripMaybe.subsumes(y1)
145+
case y: TypeRef if y.derivesFrom(defn.Caps_CapSet) =>
146+
// The upper and lower bounds don't have to be in the form of `CapSet^{...}`.
147+
// They can be other capture set variables, which are bounded by `CapSet`,
148+
// like `def test[X^, Y^, Z >: X <: Y]`.
149+
y.info match
150+
case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi)
151+
case _ => y.captureSetOfInfo.elems.forall(this.subsumes)
152+
case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) =>
153+
refs.elems.forall(this.subsumes)
138154
case _ => false
139155
|| this.match
140156
case ReachCapability(x1) => x1.subsumes(y.stripReach)
141157
case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y))
142158
case x: TermParamRef => subsumesExistentially(x, y)
143-
case x: TypeRef if x.symbol.info.derivesFrom(defn.Caps_CapSet) =>
144-
x.captureSetOfInfo.elems.exists(_.subsumes(y))
145-
case x: TypeRef => assumedContainsOf(x).contains(y)
159+
case x: TypeRef if assumedContainsOf(x).contains(y) => true
160+
case x: TypeRef if x.derivesFrom(defn.Caps_CapSet) =>
161+
x.info match
162+
case TypeBounds(lo: CaptureRef, _) =>
163+
lo.subsumes(y)
164+
case _ =>
165+
x.captureSetOfInfo.elems.exists(_.subsumes(y))
166+
case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) =>
167+
refs.elems.exists(_.subsumes(y))
146168
case _ => false
147169
end subsumes
148170

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ sealed abstract class CaptureSet extends Showable:
161161
def debugInfo(using Context) = i"$this accountsFor $x, which has capture set ${x.captureSetOfInfo}"
162162
def test(using Context) = reporting.trace(debugInfo):
163163
elems.exists(_.subsumes(x))
164-
|| !x.isMaxCapability && x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
164+
|| !x.isMaxCapability
165+
&& !x.derivesFrom(defn.Caps_CapSet)
166+
&& x.captureSetOfInfo.subCaptures(this, frozen = true).isOK
165167
comparer match
166168
case comparer: ExplainingTypeComparer => comparer.traceIndented(debugInfo)(test)
167169
case _ => test

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
866866
if others.accountsFor(ref) then
867867
report.warning(em"redundant capture: $dom already accounts for $ref", pos)
868868

869-
if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then
869+
if ref.captureSetOfInfo.elems.isEmpty
870+
&& !ref.derivesFrom(defn.Caps_Capability)
871+
&& !ref.derivesFrom(defn.Caps_CapSet) then
870872
val deepStr = if ref.isReach then " deep" else ""
871873
report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos)
872874
check(parent.captureSet, parent)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2240,7 +2240,7 @@ object Parsers {
22402240
atSpan(in.offset):
22412241
if in.isIdent(nme.UPARROW) && Feature.ccEnabled then
22422242
in.nextToken()
2243-
TypeBoundsTree(EmptyTree, makeCapsBound())
2243+
makeCapsBound()
22442244
else
22452245
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
22462246

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import config.MigrationVersion
2121
import config.Printers.refcheck
2222
import reporting.*
2323
import Constants.Constant
24+
import cc.stripCapturing
2425

2526
object RefChecks {
2627
import tpd.*
@@ -84,7 +85,7 @@ object RefChecks {
8485
* (Forwarding tends to hide problems by binding parameter names).
8586
*/
8687
private def upwardsThisType(cls: Symbol)(using Context) = cls.info match {
87-
case ClassInfo(_, _, _, _, tp: Type) if (tp ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) =>
88+
case ClassInfo(_, _, _, _, tp: Type) if (tp.stripCapturing ne cls.typeRef) && !cls.isOneOf(FinalOrModuleClass) =>
8889
SkolemType(cls.appliedRef).withName(nme.this_)
8990
case _ =>
9091
cls.thisType

library/src/scala/caps.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@ import annotation.{experimental, compileTimeOnly, retainsCap}
2222
/** A type constraint expressing that the capture set `C` needs to contain
2323
* the capability `R`
2424
*/
25-
sealed trait Contains[C <: CapSet @retainsCap, R <: Singleton]
25+
sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton]
2626

2727
/** The only implementation of `Contains`. The constraint that `{R} <: C` is
2828
* added separately by the capture checker.
2929
*/
30-
given containsImpl[C <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]()
30+
given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]()
3131

3232
/** A wrapper indicating a type variable in a capture argument list of a
3333
* @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`.
3434
*/
3535
@compileTimeOnly("Should be be used only internally by the Scala compiler")
36-
def capsOf[CS]: Any = ???
36+
def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ???
3737

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

scala2-library-cc/src/scala/collection/Stepper.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] {
5353
*
5454
* See method `trySplit` in [[java.util.Spliterator]].
5555
*/
56-
def trySplit(): Stepper[A]
56+
def trySplit(): Stepper[A]^{this}
5757

5858
/** Returns an estimate of the number of elements of this Stepper, or [[Long.MaxValue]]. See
5959
* method `estimateSize` in [[java.util.Spliterator]].
@@ -71,15 +71,15 @@ trait Stepper[@specialized(Double, Int, Long) +A] {
7171
* a [[java.util.Spliterator.OfInt]] (which is a `Spliterator[Integer]`) in the subclass [[IntStepper]]
7272
* (which is a `Stepper[Int]`).
7373
*/
74-
def spliterator[B >: A]: Spliterator[_]
74+
def spliterator[B >: A]: Spliterator[_]^{this}
7575

7676
/** Returns a Java [[java.util.Iterator]] corresponding to this Stepper.
7777
*
7878
* Note that the return type is `Iterator[_]` instead of `Iterator[A]` to allow returning
7979
* a [[java.util.PrimitiveIterator.OfInt]] (which is a `Iterator[Integer]`) in the subclass
8080
* [[IntStepper]] (which is a `Stepper[Int]`).
8181
*/
82-
def javaIterator[B >: A]: JIterator[_]
82+
def javaIterator[B >: A]: JIterator[_]^{this}
8383

8484
/** Returns an [[Iterator]] corresponding to this Stepper. Note that Iterators corresponding to
8585
* primitive Steppers box the elements.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import caps.*
2+
3+
class IO
4+
5+
def f[C^](io: IO^{C^}) = ???
6+
7+
def test =
8+
f[CapSet](???)
9+
f[CapSet^{}](???)
10+
f[CapSet^](???)
11+
f[Nothing](???) // error
12+
f[String](???) // error
13+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import caps.*
2+
3+
trait Abstract[X^]:
4+
type C >: X <: CapSet^
5+
// Don't test the return type using Unit, because it is a pure type.
6+
def boom(): AnyRef^{C^}
7+
8+
class Concrete extends Abstract[CapSet^{}]:
9+
type C = CapSet^{}
10+
// TODO: Why do we get error without the return type here?
11+
def boom(): AnyRef = new Object
12+
13+
class Concrete2 extends Abstract[CapSet^{}]:
14+
type C = CapSet^{}
15+
def boom(): AnyRef^ = new Object // error
16+
17+
class Concrete3 extends Abstract[CapSet^{}]:
18+
def boom(): AnyRef = new Object
19+
20+
class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]:
21+
type C = CapSet // error
22+
def boom(): AnyRef^{a} = a // error
23+
24+
class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]:
25+
type C = CapSet^{a}
26+
def boom(): AnyRef^{b} = b // error
27+
28+
class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]:
29+
def boom(): AnyRef^{b} = b // error
30+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import caps.*
2+
3+
class C
4+
5+
def test[X^, Y^, Z >: X <: Y](x: C^{X^}, y: C^{Y^}, z: C^{Z^}) =
6+
val x2z: C^{Z^} = x
7+
val z2y: C^{Y^} = z
8+
val x2y: C^{Y^} = x // error
9+

tests/neg-custom-args/captures/capture-poly.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import caps.*
33
trait Foo extends Capability
44

55
trait CaptureSet:
6-
type C <: CapSet^
6+
type C >: CapSet <: CapSet^
77

88
def capturePoly[C^](a: Foo^{C^}): Foo^{C^} = a
99
def capturePoly2(c: CaptureSet)(a: Foo^{c.C^}): Foo^{c.C^} = a
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import caps.*
22

33
trait AbstractWrong:
4-
type C <: CapSet
5-
def boom(): Unit^{C^} // error
4+
type C <: CapSet
5+
def f(): Unit^{C^} // error
66

7-
trait Abstract:
8-
type C <: CapSet^
9-
def boom(): Unit^{C^}
10-
11-
class Concrete extends Abstract:
12-
type C = Nothing
13-
def boom() = () // error
7+
trait Abstract1:
8+
type C >: CapSet <: CapSet^
9+
def f(): Unit^{C^}
1410

11+
// class Abstract2:
12+
// type C^
13+
// def f(): Unit^{C^}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import language.experimental.modularity
2+
import caps.*
3+
4+
class IO
5+
6+
class File
7+
8+
trait Abstract:
9+
type C >: CapSet <: CapSet^
10+
def f(file: File^{C^}): Unit
11+
12+
class Concrete1 extends Abstract:
13+
type C = CapSet
14+
def f(file: File) = ()
15+
16+
class Concrete2(io: IO^) extends Abstract:
17+
type C = CapSet^{io}
18+
def f(file: File^{io}) = ()
19+
20+
class Concrete3(io: IO^) extends Abstract:
21+
type C = CapSet^{io}
22+
def f(file: File) = () // error
23+
24+
trait Abstract2(tracked val io: IO^):
25+
type C >: CapSet <: CapSet^{io}
26+
def f(file: File^{C^}): Unit
27+
28+
class Concrete4(io: IO^) extends Abstract2(io):
29+
type C = CapSet
30+
def f(file: File) = ()
31+
32+
class Concrete5(io1: IO^, io2: IO^) extends Abstract2(io1):
33+
type C = CapSet^{io2} // error
34+
def f(file: File^{io2}) = ()
35+
36+
trait Abstract3[X^]:
37+
type C >: CapSet <: X
38+
def f(file: File^{C^}): Unit
39+
40+
class Concrete6(io: IO^) extends Abstract3[CapSet^{io}]:
41+
type C = CapSet
42+
def f(file: File) = ()
43+
44+
class Concrete7(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]:
45+
type C = CapSet^{io2} // error
46+
def f(file: File^{io2}) = ()
47+
48+
class Concrete8(io1: IO^, io2: IO^) extends Abstract3[CapSet^{io1}]:
49+
def f(file: File^{io2}) = () // error
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import caps.*
2+
3+
class IO
4+
class File(io: IO^)
5+
6+
class Handler[C^]:
7+
def f(file: File^): File^{C^} = file // error
8+
def g(file: File^{C^}): File^ = file // ok
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
-- Error: tests/neg-custom-args/captures/use-capset.scala:7:50 ---------------------------------------------------------
2-
7 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error
1+
-- Error: tests/neg-custom-args/captures/use-capset.scala:5:50 ---------------------------------------------------------
2+
5 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error
33
| ^^^^^^^
44
| Capture set parameter C leaks into capture scope of method g.
55
| To allow this, the type C should be declared with a @use annotation
6-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:22 -----------------------------------
7-
13 | val _: () -> Unit = h // error: should be ->{io}
6+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:11:22 -----------------------------------
7+
11 | val _: () -> Unit = h // error: should be ->{io}
88
| ^
99
| Found: (h : () ->{io} Unit)
1010
| Required: () -> Unit
1111
|
1212
| longer explanation available when compiling with `-explain`
13-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 -----------------------------------
14-
15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io}
13+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:50 -----------------------------------
14+
13 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io}
1515
| ^^
16-
| Found: (h2 : () ->? (x$0: List[box Object^]^{}) ->{io} Object^{io})
17-
| Required: () -> List[box Object^{io}] -> Object^{io}
16+
| Found: (h2 : () ->? (x$0: List[box Object^{io}]^{}) ->{io} Object^{io})
17+
| Required: () -> List[box Object^{io}] -> Object^{io}
1818
|
1919
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/use-capset.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import caps.{use, CapSet}
22

3-
4-
53
def f[C^](@use xs: List[Object^{C^}]): Unit = ???
64

75
private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error

tests/neg/cc-poly-2.check

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
-- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:13:15 ---------------------------------------------------------
2-
13 | f[Nothing](d) // error
3-
| ^
4-
| Found: (d : Test.D^)
5-
| Required: Test.D
6-
|
7-
| longer explanation available when compiling with `-explain`
81
-- [E007] Type Mismatch Error: tests/neg/cc-poly-2.scala:14:19 ---------------------------------------------------------
92
14 | f[CapSet^{c1}](d) // error
103
| ^

tests/neg/cc-poly-2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object Test:
1010

1111
def test(c1: C, c2: C) =
1212
val d: D^ = D()
13-
f[Nothing](d) // error
13+
// f[Nothing](d) // already ruled out at typer
1414
f[CapSet^{c1}](d) // error
1515
val x = f(d)
1616
val _: D^{c1} = x // error

0 commit comments

Comments
 (0)