Skip to content

Commit 76f01d3

Browse files
committed
Polish cap members parsing and error messages
1 parent 6486ab8 commit 76f01d3

File tree

9 files changed

+73
-34
lines changed

9 files changed

+73
-34
lines changed

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

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ object Parsers {
222222
def isErased = isIdent(nme.erased) && in.erasedEnabled
223223
// Are we seeing an `erased` soft keyword that will not be an identifier?
224224
def isErasedKw = isErased && in.isSoftModifierInParamModifierPosition
225+
// Are we seeing a `cap` soft keyword for declaring a capture-set member or at the beginning a capture-variable parameter list?
226+
def isCapKw = Feature.ccEnabled && isIdent(nme.cap)
225227
def isSimpleLiteral =
226228
simpleLiteralTokens.contains(in.token)
227229
|| isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token)
@@ -258,7 +260,7 @@ object Parsers {
258260
|| defIntroTokens.contains(in.token)
259261
|| allowedMods.contains(in.token)
260262
|| in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
261-
|| Feature.ccEnabled && isIdent(nme.cap) //TODO have it as proper keyword/token instead?
263+
|| isCapKw
262264

263265
def isStatSep: Boolean = in.isStatSep
264266

@@ -2260,22 +2262,28 @@ object Parsers {
22602262
else
22612263
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
22622264

2265+
/** CaptureSetBounds ::= [`>:' CaptureSetOrRef ] [`<:' CaptureSetOrRef ] --- under captureChecking
2266+
*/
2267+
def captureSetBounds(): TypeBoundsTree =
2268+
atSpan(in.offset):
2269+
TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE))
2270+
22632271
private def bound(tok: Int): Tree =
22642272
if (in.token == tok) { in.nextToken(); toplevelTyp() }
22652273
else EmptyTree
22662274

2267-
private def capsType(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2268-
if isLowerBound && refs.isEmpty then
2275+
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2276+
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
22692277
Select(scalaDot(nme.caps), tpnme.CapSet)
22702278
else
22712279
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
22722280

22732281
private def capsBound(tok: Int): Tree =
22742282
if (in.token == tok) then
22752283
in.nextToken()
2276-
capsType(captureSetOrRef(), isLowerBound = tok == SUPERTYPE)
2284+
capsBound(captureSetOrRef(), isLowerBound = tok == SUPERTYPE)
22772285
else
2278-
capsType(Nil, isLowerBound = tok == SUPERTYPE)
2286+
capsBound(Nil, isLowerBound = tok == SUPERTYPE)
22792287

22802288
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
22812289
*/
@@ -2286,10 +2294,10 @@ object Parsers {
22862294
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
22872295
}
22882296

2289-
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds] -- under captureChecking
2297+
/** CaptureSetAndCtxBounds ::= CaptureSetBounds [`:` ContextBounds] -- under captureChecking
22902298
*/
22912299
def captureSetAndCtxBounds(pname: TypeName): Tree = {
2292-
val t = TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE))
2300+
val t = captureSetBounds()
22932301
val cbs = contextBounds(pname)
22942302
if (cbs.isEmpty) t
22952303
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3907,7 +3915,7 @@ object Parsers {
39073915
case CASE if inEnum =>
39083916
enumCase(start, mods)
39093917
case _ =>
3910-
if Feature.ccEnabled && isIdent(nme.cap) then //TODO do we want a dedicated CAP token? TokensCommon would need a Ctx to check if ccenabled
3918+
if isCapKw then
39113919
capDefOrDcl(start, in.skipToken(mods))
39123920
else tmplDef(start, mods)
39133921
}
@@ -4118,20 +4126,22 @@ object Parsers {
41184126
}
41194127
}
41204128

4121-
/** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSet] -- under capture checking
4129+
private def concreteCapsType(refs: List[Tree]): Tree =
4130+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
4131+
4132+
/** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSetOrRef] -- under capture checking
41224133
*/
41234134
def capDefOrDcl(start: Offset, mods: Modifiers): Tree =
41244135
newLinesOpt()
41254136
atSpan(start, nameStart) {
41264137
val nameIdent = typeIdent()
41274138
val tname = nameIdent.name.asTypeName
4128-
// val tparams = typeParamClauseOpt(ParamOwner.Hk) TODO: error message: type parameters not allowed
4129-
// val vparamss = funParamClauses()
4139+
if in.token == LBRACKET then syntaxError(em"'cap' declarations cannot have type parameters")
41304140

41314141
def makeCapDef(refs: List[Tree] | Tree): Tree = {
41324142
val tdef = TypeDef(nameIdent.name.toTypeName,
41334143
refs.match
4134-
case refs: List[Tree] => capsType(refs)
4144+
case refs: List[Tree] => concreteCapsType(refs)
41354145
case bounds: Tree => bounds)
41364146

41374147
if (nameIdent.isBackquoted)
@@ -4143,24 +4153,13 @@ object Parsers {
41434153
case EQUALS =>
41444154
in.nextToken()
41454155
makeCapDef(captureSetOrRef())
4146-
case SUBTYPE | SUPERTYPE =>
4147-
captureSetAndCtxBounds(tname) match
4148-
case bounds: TypeBoundsTree if in.token == EQUALS => //TODO ask Martin: can this case even happen?
4149-
val eqOffset = in.skipToken()
4150-
var rhs = capsType(captureSetOrRef())
4151-
if mods.is(Opaque) then
4152-
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
4153-
else
4154-
syntaxError(em"cannot combine bound and alias", eqOffset)
4155-
makeCapDef(rhs)
4156-
case bounds => makeCapDef(bounds)
4157-
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4156+
case SUBTYPE | SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
41584157
makeCapDef(captureSetAndCtxBounds(tname))
4159-
case _ if (staged & StageKind.QuotedPattern) != 0 //TODO not sure if we need this case for capsets
4158+
case _ if (staged & StageKind.QuotedPattern) != 0
41604159
|| sourceVersion.enablesNewGivens && in.isColon =>
41614160
makeCapDef(captureSetAndCtxBounds(tname))
41624161
case _ =>
4163-
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) //TODO change error message
4162+
syntaxErrorOrIncomplete(ExpectedCaptureBoundOrEquals(in.token))
41644163
return EmptyTree // return to avoid setting the span to EmptyTree
41654164
}
41664165

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
222222
case EnumMayNotBeValueClassesID // errorNumber: 206
223223
case IllegalUnrollPlacementID // errorNumber: 207
224224
case ExtensionHasDefaultID // errorNumber: 208
225+
case ExpectedCaptureBoundOrEqualsID // errorNumber: 209
225226

226227
def errorNumber = ordinal - 1
227228

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,23 @@ class ExpectedTypeBoundOrEquals(found: Token)(using Context)
19171917
|"""
19181918
}
19191919

1920+
class ExpectedCaptureBoundOrEquals(found: Token)(using Context)
1921+
extends SyntaxMsg(ExpectedCaptureBoundOrEqualsID) {
1922+
def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found"
1923+
1924+
def explain(using Context) =
1925+
i"""Capture parameters and abstract captures may be constrained by a capture bound.
1926+
|Such capture bounds limit the concrete values of the capture variables and possibly
1927+
|reveal more information about the members of such captures.
1928+
|
1929+
|A lower type bound ${hl("B >: A")} expresses that the capture variable ${hl("B")}
1930+
|refers to a super capture of capture ${hl("A")}.
1931+
|
1932+
|An upper capture bound ${hl("T <: A")} declares that capture variable ${hl("T")}
1933+
|refers to a subcapture of ${hl("A")}.
1934+
|"""
1935+
}
1936+
19201937
class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context)
19211938
extends NamingMsg(ClassAndCompanionNameClashID) {
19221939
def msg(using Context) =
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
import caps.*
22

33
trait Abstract[X^]:
4-
type C >: X <: CapSet^
4+
cap C >: X
55
// Don't test the return type using Unit, because it is a pure type.
66
def boom(): AnyRef^{C}
77

88
class Concrete extends Abstract[CapSet^{}]:
9-
type C = CapSet^{}
9+
cap C = {}
1010
// TODO: Why do we get error without the return type here?
1111
def boom(): AnyRef = new Object
1212

1313
class Concrete2 extends Abstract[CapSet^{}]:
14-
type C = CapSet^{}
14+
cap C = {}
1515
def boom(): AnyRef^ = new Object // error
1616

1717
class Concrete3 extends Abstract[CapSet^{}]:
1818
def boom(): AnyRef = new Object
1919

2020
class Concrete4(a: AnyRef^) extends Abstract[CapSet^{a}]:
21-
type C = CapSet // error
21+
cap C = {} // error
2222
def boom(): AnyRef^{a} = a // error
2323

2424
class Concrete5(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]:
25-
type C = CapSet^{a}
25+
cap C = a
2626
def boom(): AnyRef^{b} = b // error
2727

2828
class Concrete6(a: AnyRef^, b: AnyRef^) extends Abstract[CapSet^{a}]:
29-
def boom(): AnyRef^{b} = b // error
29+
def boom(): AnyRef^{b} = b // error
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg-custom-args/captures/capset-members2.scala:4:7 -----------------------------------------------------
2+
4 | cap C[T] // error
3+
| ^
4+
| 'cap' declarations cannot have type parameters
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import caps.*
2+
3+
trait Foo:
4+
cap C[T] // error
5+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E209] Syntax Error: tests/neg-custom-args/captures/capset-members3.scala:4:8 ---------------------------------------
2+
4 | cap C _ // error
3+
| ^
4+
| =, >:, or <: expected, but '_' found
5+
|
6+
| longer explanation available when compiling with `-explain`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import caps.*
2+
3+
trait Foo:
4+
cap C _ // error
5+

tests/pos-custom-args/captures/cap_members.scala renamed to tests/pos-custom-args/captures/capset-members.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import language.experimental.captureChecking
22

3+
trait Ctx[T]
4+
35
def test =
46
val x: Any^ = ???
57
val y: Any^ = ???
@@ -9,11 +11,11 @@ def test =
911
cap A >: y <: x
1012
cap B = x
1113
cap C <: {x}
12-
cap D
14+
cap D : Ctx
1315
cap E <: C
1416
cap F <: {C}
1517
cap G <: {x, y}
16-
cap H >: {x} <: {x,y}
18+
cap H >: {x} <: {x,y} : Ctx
1719
cap I = {y, G, H}
1820
cap J = {O.z}
1921
cap K = {x, O.z}

0 commit comments

Comments
 (0)