Skip to content

Commit cc8079a

Browse files
committed
Polish cap members parsing and error messages
1 parent 6a2b7c7 commit cc8079a

File tree

9 files changed

+73
-34
lines changed

9 files changed

+73
-34
lines changed

Diff for: compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+25-26
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

@@ -2253,22 +2255,28 @@ object Parsers {
22532255
else
22542256
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
22552257

2258+
/** CaptureSetBounds ::= [`>:' CaptureSetOrRef ] [`<:' CaptureSetOrRef ] --- under captureChecking
2259+
*/
2260+
def captureSetBounds(): TypeBoundsTree =
2261+
atSpan(in.offset):
2262+
TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE))
2263+
22562264
private def bound(tok: Int): Tree =
22572265
if (in.token == tok) { in.nextToken(); toplevelTyp() }
22582266
else EmptyTree
22592267

2260-
private def capsType(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2261-
if isLowerBound && refs.isEmpty then
2268+
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2269+
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
22622270
Select(scalaDot(nme.caps), tpnme.CapSet)
22632271
else
22642272
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
22652273

22662274
private def capsBound(tok: Int): Tree =
22672275
if (in.token == tok) then
22682276
in.nextToken()
2269-
capsType(captureSetOrRef(), isLowerBound = tok == SUPERTYPE)
2277+
capsBound(captureSetOrRef(), isLowerBound = tok == SUPERTYPE)
22702278
else
2271-
capsType(Nil, isLowerBound = tok == SUPERTYPE)
2279+
capsBound(Nil, isLowerBound = tok == SUPERTYPE)
22722280

22732281
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
22742282
*/
@@ -2279,10 +2287,10 @@ object Parsers {
22792287
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
22802288
}
22812289

2282-
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds] -- under captureChecking
2290+
/** CaptureSetAndCtxBounds ::= CaptureSetBounds [`:` ContextBounds] -- under captureChecking
22832291
*/
22842292
def captureSetAndCtxBounds(pname: TypeName): Tree = {
2285-
val t = TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE))
2293+
val t = captureSetBounds()
22862294
val cbs = contextBounds(pname)
22872295
if (cbs.isEmpty) t
22882296
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3900,7 +3908,7 @@ object Parsers {
39003908
case CASE if inEnum =>
39013909
enumCase(start, mods)
39023910
case _ =>
3903-
if Feature.ccEnabled && isIdent(nme.cap) then //TODO do we want a dedicated CAP token? TokensCommon would need a Ctx to check if ccenabled
3911+
if isCapKw then
39043912
capDefOrDcl(start, in.skipToken(mods))
39053913
else tmplDef(start, mods)
39063914
}
@@ -4111,20 +4119,22 @@ object Parsers {
41114119
}
41124120
}
41134121

4114-
/** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSet] -- under capture checking
4122+
private def concreteCapsType(refs: List[Tree]): Tree =
4123+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
4124+
4125+
/** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSetOrRef] -- under capture checking
41154126
*/
41164127
def capDefOrDcl(start: Offset, mods: Modifiers): Tree =
41174128
newLinesOpt()
41184129
atSpan(start, nameStart) {
41194130
val nameIdent = typeIdent()
41204131
val tname = nameIdent.name.asTypeName
4121-
// val tparams = typeParamClauseOpt(ParamOwner.Hk) TODO: error message: type parameters not allowed
4122-
// val vparamss = funParamClauses()
4132+
if in.token == LBRACKET then syntaxError(em"'cap' declarations cannot have type parameters")
41234133

41244134
def makeCapDef(refs: List[Tree] | Tree): Tree = {
41254135
val tdef = TypeDef(nameIdent.name.toTypeName,
41264136
refs.match
4127-
case refs: List[Tree] => capsType(refs)
4137+
case refs: List[Tree] => concreteCapsType(refs)
41284138
case bounds: Tree => bounds)
41294139

41304140
if (nameIdent.isBackquoted)
@@ -4136,24 +4146,13 @@ object Parsers {
41364146
case EQUALS =>
41374147
in.nextToken()
41384148
makeCapDef(captureSetOrRef())
4139-
case SUBTYPE | SUPERTYPE =>
4140-
captureSetAndCtxBounds(tname) match
4141-
case bounds: TypeBoundsTree if in.token == EQUALS => //TODO ask Martin: can this case even happen?
4142-
val eqOffset = in.skipToken()
4143-
var rhs = capsType(captureSetOrRef())
4144-
if mods.is(Opaque) then
4145-
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
4146-
else
4147-
syntaxError(em"cannot combine bound and alias", eqOffset)
4148-
makeCapDef(rhs)
4149-
case bounds => makeCapDef(bounds)
4150-
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4149+
case SUBTYPE | SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
41514150
makeCapDef(captureSetAndCtxBounds(tname))
4152-
case _ if (staged & StageKind.QuotedPattern) != 0 //TODO not sure if we need this case for capsets
4151+
case _ if (staged & StageKind.QuotedPattern) != 0
41534152
|| sourceVersion.enablesNewGivens && in.isColon =>
41544153
makeCapDef(captureSetAndCtxBounds(tname))
41554154
case _ =>
4156-
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) //TODO change error message
4155+
syntaxErrorOrIncomplete(ExpectedCaptureBoundOrEquals(in.token))
41574156
return EmptyTree // return to avoid setting the span to EmptyTree
41584157
}
41594158

Diff for: compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1
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

Diff for: compiler/src/dotty/tools/dotc/reporting/messages.scala

+17
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) =

Diff for: tests/neg-custom-args/captures/capset-members.scala

+6-6
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

Diff for: tests/neg-custom-args/captures/capset-members2.check

+4
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

Diff for: tests/neg-custom-args/captures/capset-members2.scala

+5
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+

Diff for: tests/neg-custom-args/captures/capset-members3.check

+6
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`

Diff for: tests/neg-custom-args/captures/capset-members3.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import caps.*
2+
3+
trait Foo:
4+
cap C _ // error
5+

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

+4-2
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)