Skip to content

Commit 331ab8a

Browse files
committed
Desugar capture vars in typer instead of parser
* Refrain from using a flag and in favor of an attachment. * Rule out context bounds entirely for capture vars.
1 parent 5e94ee9 commit 331ab8a

14 files changed

+116
-79
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ object desugar {
642642
.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
643643
}
644644

645-
/** Desugar type def (not param): Under x.moduliity this can expand
645+
/** Desugar type def (not param): Under x.modularity this can expand
646646
* context bounds, which are expanded to evidence ValDefs. These will
647647
* ultimately map to deferred givens.
648648
*/

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

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ object Trees {
3434

3535
val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey()
3636

37+
/** Property key for marking capture-set variables and members */
38+
val CaptureVar: Property.StickyKey[Unit] = Property.StickyKey()
39+
3740
/** Trees take a parameter indicating what the type of their `tpe` field
3841
* is. Two choices: `Type` or `Untyped`.
3942
* Untyped trees have type `Tree[Untyped]`.

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

+29-41
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import config.SourceVersion.*
3535
import config.SourceVersion
3636
import dotty.tools.dotc.config.MigrationVersion
3737
import dotty.tools.dotc.util.chaining.*
38-
import dotty.tools.dotc.config.Printers.variances
3938

4039
object Parsers {
4140

@@ -2288,49 +2287,28 @@ object Parsers {
22882287
* TypeBound ::= Type
22892288
* | CaptureSet -- under captureChecking
22902289
*/
2291-
def typeBounds(isCapParamOrMem: Boolean = false): TypeBoundsTree =
2292-
def isCapsBound(t: Tree): Boolean =
2293-
t match
2294-
case Select(qual, tpnme.CapSet) => true
2295-
case Annotated(Select(qual, tpnme.CapSet), _) => true
2296-
case _ => false
2297-
2290+
def typeBounds(): TypeBoundsTree =
22982291
atSpan(in.offset):
2299-
var lbound = bound(SUPERTYPE, isCapParamOrMem)
2300-
var ubound = bound(SUBTYPE, isCapParamOrMem)
2301-
if Feature.ccEnabled && !isCapParamOrMem then
2302-
/* We haven't annotated the `^` to a type parameter/member,
2303-
but an explicit capture-set bound makes it a capture parameter, so we make sure
2304-
to add the missing other CapSet bound. */
2305-
if lbound.isEmpty && isCapsBound(ubound) then
2306-
lbound = capsBound(Nil, isLowerBound = true)
2307-
if ubound.isEmpty && isCapsBound(lbound) then
2308-
ubound = capsBound(Nil, isLowerBound = false)
2309-
end if
2310-
TypeBoundsTree(lbound, ubound)
2311-
end typeBounds
2312-
2313-
private def bound(tok: Int, isCapParamOrMem: Boolean = false): Tree =
2292+
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
2293+
2294+
private def bound(tok: Int): Tree =
23142295
if (in.token == tok) then
23152296
in.nextToken()
23162297
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then
23172298
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
23182299
else toplevelTyp()
2319-
else if Feature.ccEnabled && isCapParamOrMem then
2320-
// we hit this case if we have annotated a post-fix `^` but no bounds to a type parameter/member
2321-
capsBound(Nil, isLowerBound = tok == SUPERTYPE)
23222300
else EmptyTree
23232301

23242302
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
23252303
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
23262304
Select(scalaDot(nme.caps), tpnme.CapSet)
23272305
else
2328-
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
2306+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
23292307

23302308
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
23312309
*/
2332-
def typeAndCtxBounds(pname: TypeName, isCapParamOrMem: Boolean = false): Tree = {
2333-
val t = typeBounds(isCapParamOrMem)
2310+
def typeAndCtxBounds(pname: TypeName): Tree = {
2311+
val t = typeBounds()
23342312
val cbs = contextBounds(pname)
23352313
if (cbs.isEmpty) t
23362314
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3580,16 +3558,23 @@ object Parsers {
35803558
WildcardParamName.fresh().toTypeName
35813559
else ident().toTypeName
35823560
val isCap = gobbleHat()
3583-
if isCap && mods.isOneOf(Covariant | Contravariant) then
3584-
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3585-
if isCap && in.token == LBRACKET then
3586-
syntaxError(em"capture parameters do not take type parameters")
3587-
in.nextToken()
3561+
if isCap then
3562+
if mods.isOneOf(Covariant | Contravariant) then
3563+
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3564+
if in.token == LBRACKET then
3565+
syntaxError(em"capture parameters do not take type parameters")
3566+
in.nextToken()
3567+
end if
35883568
val hkparams = typeParamClauseOpt(ParamOwner.Hk)
35893569
val bounds =
3590-
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name, isCap)
3591-
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name, isCap)
3592-
else typeBounds(isCap)
3570+
if !isCap && paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
3571+
else if !isCap && sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
3572+
else typeBounds()
3573+
if isCap then
3574+
bounds.pushAttachment(CaptureVar, ())
3575+
val t = contextBounds(name)
3576+
if t.nonEmpty then
3577+
syntaxError(em"capture parameters cannot have context bounds", t.head.span)
35933578
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
35943579
}
35953580
}
@@ -4135,8 +4120,11 @@ object Parsers {
41354120
def makeTypeDef(rhs: Tree): Tree = {
41364121
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
41374122
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
4138-
if (nameIdent.isBackquoted)
4123+
if nameIdent.isBackquoted then
41394124
tdef.pushAttachment(Backquoted, ())
4125+
if isCapDef then rhs.match
4126+
case ContextBounds(_, _) => syntaxError(em"capture-set member declarations cannot have context bounds", rhs.span)
4127+
case rhs => rhs.pushAttachment(CaptureVar, ())
41404128
finalizeDef(tdef, mods, start)
41414129
}
41424130

@@ -4145,7 +4133,7 @@ object Parsers {
41454133
in.nextToken()
41464134
makeTypeDef(typeDefRHS())
41474135
case SUBTYPE | SUPERTYPE =>
4148-
typeAndCtxBounds(tname, isCapDef) match
4136+
typeAndCtxBounds(tname) match
41494137
case bounds: TypeBoundsTree if in.token == EQUALS =>
41504138
val eqOffset = in.skipToken()
41514139
var rhs = typeDefRHS()
@@ -4166,10 +4154,10 @@ object Parsers {
41664154
makeTypeDef(rhs)
41674155
case bounds => makeTypeDef(bounds)
41684156
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4169-
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
4157+
makeTypeDef(typeAndCtxBounds(tname))
41704158
case _ if (staged & StageKind.QuotedPattern) != 0
41714159
|| sourceVersion.enablesNewGivens && in.isColon =>
4172-
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
4160+
makeTypeDef(typeAndCtxBounds(tname))
41734161
case _ =>
41744162
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
41754163
return EmptyTree // return to avoid setting the span to EmptyTree

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

+21-2
Original file line numberDiff line numberDiff line change
@@ -2524,6 +2524,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25242524
val tycon = typedType(tree.tycon)
25252525
def spliced(tree: Tree) = untpd.TypedSplice(tree)
25262526
val tparam = untpd.Ident(tree.paramName).withSpan(tree.span.withEnd(tree.span.point))
2527+
if Feature.ccEnabled && typed(tparam).tpe.derivesFrom(defn.Caps_CapSet) then
2528+
errorTree(tree, em"Capture variable `${tree.paramName}` cannot have a context bound.")
25272529
if tycon.tpe.typeParams.nonEmpty then
25282530
val tycon0 = tycon.withType(tycon.tpe.etaCollapse)
25292531
typed(untpd.AppliedTypeTree(spliced(tycon0), tparam :: Nil))
@@ -2742,13 +2744,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27422744
assignType(cpy.ByNameTypeTree(tree)(result1), result1)
27432745

27442746
def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(using Context): Tree =
2747+
lazy val CapSetBot = untpd.TypeTree(defn.Caps_CapSet.typeRef)
2748+
lazy val CapSetTop = untpd.makeRetaining(untpd.TypeTree(defn.Caps_CapSet.typeRef), Nil, tpnme.retainsCap).withSpan(tree.span)
2749+
27452750
val TypeBoundsTree(lo, hi, alias) = tree
27462751
val lo1 = typed(lo)
27472752
val hi1 = typed(hi)
27482753
val alias1 = typed(alias)
2749-
val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1
2750-
val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1
2754+
val isCap = tree.hasAttachment(CaptureVar)
2755+
val lo2 =
2756+
if lo1.isEmpty then
2757+
if Feature.ccEnabled && (isCap || hi1.tpe.derivesFrom(defn.Caps_CapSet)) then
2758+
typed(CapSetBot)
2759+
else typed(untpd.TypeTree(defn.NothingType))
2760+
else lo1
2761+
val hi2 =
2762+
if hi1.isEmpty then
2763+
if Feature.ccEnabled && (isCap || lo1.tpe.derivesFrom(defn.Caps_CapSet)) then
2764+
typed(CapSetTop)
2765+
else typed(untpd.TypeTree(defn.AnyType))
2766+
else hi1
27512767
assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1)
2768+
end typedTypeBoundsTree
27522769

27532770
def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = {
27542771
if !isFullyDefined(pt, ForceDegree.all) then
@@ -3044,6 +3061,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30443061
if sym.isOpaqueAlias then
30453062
checkFullyAppliedType(rhs1, "Opaque type alias must be fully applied, but ")
30463063
checkNoContextFunctionType(rhs1)
3064+
if Feature.ccEnabled && rhs1.tpe.derivesFrom(defn.Caps_CapSet) then
3065+
rhs1.putAttachment(CaptureVar, ())
30473066
assignType(cpy.TypeDef(tdef)(name, rhs1), sym)
30483067
}
30493068

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:10:74 ---------------------------------------------------
2+
10 | val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error
3+
| ^^^
4+
| capture parameters cannot have context bounds
5+
-- Error: tests/neg-custom-args/captures/cap-paramlists6.scala:11:54 ---------------------------------------------------
6+
11 | G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error
7+
| ^^^
8+
| Capture variable `H` cannot have a context bound.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import language.experimental.captureChecking
2+
3+
trait Ctx[T]
4+
5+
def test =
6+
val x: Any^ = ???
7+
val y: Any^ = ???
8+
object O:
9+
val z: Any^ = ???
10+
val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx, // error
11+
G >: {} <: {}, H >: {} <: {} : Ctx] => (x: Int) => 1 // error

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

+26
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,29 @@ class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]:
2828

2929
class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[{a}]:
3030
def boom(): AnyRef^{b} = b // error
31+
32+
33+
trait Ctx[T]
34+
35+
def test =
36+
val x: Any^ = ???
37+
val y: Any^ = ???
38+
object O:
39+
val z: Any^ = ???
40+
trait CaptureSet:
41+
type A^ >: {y} <: {x}
42+
type B = {x}
43+
type C <: {x}
44+
type D^ : Ctx // error
45+
type E <: {C}
46+
type F <: {C}
47+
type G <: {x, y}
48+
type H >: {x} <: {x,y} : Ctx // error
49+
type I^ = {y, G, H}
50+
type J = {O.z}
51+
type K^ = {x, O.z}
52+
type L <: {x, y, O.z}
53+
type M >: {x, y, O.z} <: {C}
54+
type N >: {x} <: {x}
55+
type O >: {O.z} <: {O.z}
56+
type P >: {B,D} // error

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

+2-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import language.experimental.captureChecking
2-
import language.experimental.modularity
32
import caps.*
43

54
def test =
@@ -8,19 +7,11 @@ def test =
87
val z: Any^ = ???
98
def onlyWithZ[C^](using c: Contains[C, z.type]) = ???
109

11-
trait IncludesZ[C^]:
12-
val c: Contains[C, z.type]
13-
1410
trait Foo:
15-
type C >: {x} <: {x,y,z} : IncludesZ
11+
type C >: {z,x} <: {x,y,z}
1612

1713
val foo: Foo = ???
18-
/* new Foo {
19-
override given IncludesZ[C]: // FIXME: doesn't work yet
20-
val c: Contains[C, z.type] = summon
21-
cap type C = {x,z}
22-
} */
23-
onlyWithZ(using foo.C.c)
14+
onlyWithZ[{foo.C}]
2415
onlyWithZ[{z}]
2516
onlyWithZ[{x,z}]
2617
onlyWithZ[{x,y,z}]
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import language.experimental.captureChecking
22

3-
trait Ctx[T]
4-
53
def test =
64
val x: Any^ = ???
75
val y: Any^ = ???
@@ -10,14 +8,14 @@ def test =
108
def foo[A >: {y} <: {x},
119
B^,
1210
C <: {x},
13-
D^ : Ctx,
11+
D^,
1412
E <: {C},
1513
F <: {C},
1614
G <: {x, y},
17-
H >: {x} <: {x,y} : Ctx, T, U >: {x}]()[I^ <: {y, G, H},
18-
J <: {O.z},
19-
K <: {x, O.z},
20-
L <: {x, y, O.z},
21-
M >: {x, y, O.z} <: {C} : Ctx,
22-
N >: {x} <: {x},
23-
O >: {O.z} <: {O.z}] = ???
15+
H >: {x} <: {x,y}, T, U >: {x}]()[I^ <: {y, G, H},
16+
J <: {O.z},
17+
K <: {x, O.z},
18+
L <: {x, y, O.z},
19+
M >: {x, y, O.z} <: {C},
20+
N >: {x} <: {x},
21+
O >: {O.z} <: {O.z}] = ???
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import language.experimental.captureChecking
22

3-
trait Ctx[T]
4-
53
def test =
64
val x: Any^ = ???
75
val y: Any^ = ???
86
object O:
97
val z: Any^ = ???
10-
val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1
8+
val baz = () => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import language.experimental.captureChecking
22

3-
trait Ctx[T]
4-
53
def test =
64
val x: Any^ = ???
75
val y: Any^ = ???
86
object O:
97
val z: Any^ = ???
10-
val baz2 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1
8+
val baz2 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}, F^ >: {x,y} <: {C,E}] => (x: Int) => 1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import language.experimental.captureChecking
22

3-
trait Ctx[T]
4-
53
def test =
64
val x: Any^ = ???
75
val y: Any^ = ???
86
object O:
97
val z: Any^ = ???
10-
val baz3 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}] => () => [F^ >: {x,y} <: {C,E} : Ctx] => (x: Int) => 1
8+
val baz3 = (i: Int) => [C^, D^ <: {C}, E^ <: {C,x}] => () => [F^ >: {x,y} <: {C,E}] => (x: Int) => 1

tests/pos-custom-args/captures/capset-members.scala

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import language.experimental.captureChecking
22

3-
trait Ctx[T]
4-
53
def test =
64
val x: Any^ = ???
75
val y: Any^ = ???
@@ -11,11 +9,11 @@ def test =
119
type A^ >: {y} <: {x}
1210
type B = {x}
1311
type C <: {x}
14-
type D^ : Ctx
12+
type D^
1513
type E <: {C}
1614
type F <: {C}
1715
type G <: {x, y}
18-
type H >: {x} <: {x,y} : Ctx
16+
type H >: {x} <: {x,y}
1917
type I^ = {y, G, H}
2018
type J = {O.z}
2119
type K^ = {x, O.z}

tests/neg-custom-args/captures/i21868.scala renamed to tests/pos-custom-args/captures/i21868.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import language.experimental.captureChecking
12
import caps.*
23

34
trait AbstractWrong:
45
type C <: CapSet
5-
def f(): Unit^{C} // error
6+
def f(): Unit^{C}
67

78
trait Abstract1:
89
type C^

0 commit comments

Comments
 (0)