Skip to content

Commit c8ef6d1

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 1cc8590 commit c8ef6d1

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

@@ -2278,49 +2277,28 @@ object Parsers {
22782277
* TypeBound ::= Type
22792278
* | CaptureSet -- under captureChecking
22802279
*/
2281-
def typeBounds(isCapParamOrMem: Boolean = false): TypeBoundsTree =
2282-
def isCapsBound(t: Tree): Boolean =
2283-
t match
2284-
case Select(qual, tpnme.CapSet) => true
2285-
case Annotated(Select(qual, tpnme.CapSet), _) => true
2286-
case _ => false
2287-
2280+
def typeBounds(): TypeBoundsTree =
22882281
atSpan(in.offset):
2289-
var lbound = bound(SUPERTYPE, isCapParamOrMem)
2290-
var ubound = bound(SUBTYPE, isCapParamOrMem)
2291-
if Feature.ccEnabled && !isCapParamOrMem then
2292-
/* We haven't annotated the `^` to a type parameter/member,
2293-
but an explicit capture-set bound makes it a capture parameter, so we make sure
2294-
to add the missing other CapSet bound. */
2295-
if lbound.isEmpty && isCapsBound(ubound) then
2296-
lbound = capsBound(Nil, isLowerBound = true)
2297-
if ubound.isEmpty && isCapsBound(lbound) then
2298-
ubound = capsBound(Nil, isLowerBound = false)
2299-
end if
2300-
TypeBoundsTree(lbound, ubound)
2301-
end typeBounds
2302-
2303-
private def bound(tok: Int, isCapParamOrMem: Boolean = false): Tree =
2282+
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
2283+
2284+
private def bound(tok: Int): Tree =
23042285
if (in.token == tok) then
23052286
in.nextToken()
23062287
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then
23072288
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
23082289
else toplevelTyp()
2309-
else if Feature.ccEnabled && isCapParamOrMem then
2310-
// we hit this case if we have annotated a post-fix `^` but no bounds to a type parameter/member
2311-
capsBound(Nil, isLowerBound = tok == SUPERTYPE)
23122290
else EmptyTree
23132291

23142292
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
23152293
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
23162294
Select(scalaDot(nme.caps), tpnme.CapSet)
23172295
else
2318-
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
2296+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
23192297

23202298
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
23212299
*/
2322-
def typeAndCtxBounds(pname: TypeName, isCapParamOrMem: Boolean = false): Tree = {
2323-
val t = typeBounds(isCapParamOrMem)
2300+
def typeAndCtxBounds(pname: TypeName): Tree = {
2301+
val t = typeBounds()
23242302
val cbs = contextBounds(pname)
23252303
if (cbs.isEmpty) t
23262304
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3570,16 +3548,23 @@ object Parsers {
35703548
WildcardParamName.fresh().toTypeName
35713549
else ident().toTypeName
35723550
val isCap = gobbleHat()
3573-
if isCap && mods.isOneOf(Covariant | Contravariant) then
3574-
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3575-
if isCap && in.token == LBRACKET then
3576-
syntaxError(em"capture parameters do not take type parameters")
3577-
in.nextToken()
3551+
if isCap then
3552+
if mods.isOneOf(Covariant | Contravariant) then
3553+
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3554+
if in.token == LBRACKET then
3555+
syntaxError(em"capture parameters do not take type parameters")
3556+
in.nextToken()
3557+
end if
35783558
val hkparams = typeParamClauseOpt(ParamOwner.Hk)
35793559
val bounds =
3580-
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name, isCap)
3581-
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name, isCap)
3582-
else typeBounds(isCap)
3560+
if !isCap && paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
3561+
else if !isCap && sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
3562+
else typeBounds()
3563+
if isCap then
3564+
bounds.pushAttachment(CaptureVar, ())
3565+
val t = contextBounds(name)
3566+
if t.nonEmpty then
3567+
syntaxError(em"capture parameters cannot have context bounds", t.head.span)
35833568
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
35843569
}
35853570
}
@@ -4125,8 +4110,11 @@ object Parsers {
41254110
def makeTypeDef(rhs: Tree): Tree = {
41264111
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
41274112
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
4128-
if (nameIdent.isBackquoted)
4113+
if nameIdent.isBackquoted then
41294114
tdef.pushAttachment(Backquoted, ())
4115+
if isCapDef then rhs.match
4116+
case ContextBounds(_, _) => syntaxError(em"capture-set member declarations cannot have context bounds", rhs.span)
4117+
case rhs => rhs.pushAttachment(CaptureVar, ())
41304118
finalizeDef(tdef, mods, start)
41314119
}
41324120

@@ -4135,7 +4123,7 @@ object Parsers {
41354123
in.nextToken()
41364124
makeTypeDef(typeDefRHS())
41374125
case SUBTYPE | SUPERTYPE =>
4138-
typeAndCtxBounds(tname, isCapDef) match
4126+
typeAndCtxBounds(tname) match
41394127
case bounds: TypeBoundsTree if in.token == EQUALS =>
41404128
val eqOffset = in.skipToken()
41414129
var rhs = typeDefRHS()
@@ -4156,10 +4144,10 @@ object Parsers {
41564144
makeTypeDef(rhs)
41574145
case bounds => makeTypeDef(bounds)
41584146
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4159-
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
4147+
makeTypeDef(typeAndCtxBounds(tname))
41604148
case _ if (staged & StageKind.QuotedPattern) != 0
41614149
|| sourceVersion.enablesNewGivens && in.isColon =>
4162-
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
4150+
makeTypeDef(typeAndCtxBounds(tname))
41634151
case _ =>
41644152
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
41654153
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))
@@ -2734,13 +2736,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27342736
assignType(cpy.ByNameTypeTree(tree)(result1), result1)
27352737

27362738
def typedTypeBoundsTree(tree: untpd.TypeBoundsTree, pt: Type)(using Context): Tree =
2739+
lazy val CapSetBot = untpd.TypeTree(defn.Caps_CapSet.typeRef)
2740+
lazy val CapSetTop = untpd.makeRetaining(untpd.TypeTree(defn.Caps_CapSet.typeRef), Nil, tpnme.retainsCap).withSpan(tree.span)
2741+
27372742
val TypeBoundsTree(lo, hi, alias) = tree
27382743
val lo1 = typed(lo)
27392744
val hi1 = typed(hi)
27402745
val alias1 = typed(alias)
2741-
val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(defn.NothingType)) else lo1
2742-
val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(defn.AnyType)) else hi1
2746+
val isCap = tree.hasAttachment(CaptureVar)
2747+
val lo2 =
2748+
if lo1.isEmpty then
2749+
if Feature.ccEnabled && (isCap || hi1.tpe.derivesFrom(defn.Caps_CapSet)) then
2750+
typed(CapSetBot)
2751+
else typed(untpd.TypeTree(defn.NothingType))
2752+
else lo1
2753+
val hi2 =
2754+
if hi1.isEmpty then
2755+
if Feature.ccEnabled && (isCap || lo1.tpe.derivesFrom(defn.Caps_CapSet)) then
2756+
typed(CapSetTop)
2757+
else typed(untpd.TypeTree(defn.AnyType))
2758+
else hi1
27432759
assignType(cpy.TypeBoundsTree(tree)(lo2, hi2, alias1), lo2, hi2, alias1)
2760+
end typedTypeBoundsTree
27442761

27452762
def typedBind(tree: untpd.Bind, pt: Type)(using Context): Tree = {
27462763
if !isFullyDefined(pt, ForceDegree.all) then
@@ -3036,6 +3053,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30363053
if sym.isOpaqueAlias then
30373054
checkFullyAppliedType(rhs1, "Opaque type alias must be fully applied, but ")
30383055
checkNoContextFunctionType(rhs1)
3056+
if Feature.ccEnabled && rhs1.tpe.derivesFrom(defn.Caps_CapSet) then
3057+
rhs1.putAttachment(CaptureVar, ())
30393058
assignType(cpy.TypeDef(tdef)(name, rhs1), sym)
30403059
}
30413060

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)