Skip to content

Commit 24ec0b8

Browse files
committed
Check for trailing commas in parser instead of scanner
1 parent 547ed43 commit 24ec0b8

File tree

6 files changed

+163
-38
lines changed

6 files changed

+163
-38
lines changed

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

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -559,11 +559,18 @@ object Parsers {
559559
* If the parser consumes a `part` that is not followed by a comma or this expected
560560
* token, issue a syntax error and try to recover at the next safe point.
561561
*/
562-
def commaSeparated[T](part: () => T, expectedEnd: Token = EMPTY): List[T] = {
563-
val ts = new ListBuffer[T] += part()
564-
while (in.token == COMMA) {
562+
def commaSeparated[T](part: () => T, expectedEnd: Token, readFirst: Boolean = true): List[T] = {
563+
val ts = new ListBuffer[T]
564+
if (readFirst) ts += part()
565+
var done = false
566+
while (in.token == COMMA && !done) {
565567
in.nextToken()
566-
ts += part()
568+
if (in.isAfterLineEnd && (in.token == OUTDENT || (expectedEnd != EMPTY && in.token == expectedEnd))) {
569+
// skip the trailing comma
570+
done = true
571+
} else {
572+
ts += part()
573+
}
567574
}
568575
if (expectedEnd != EMPTY && in.token != expectedEnd) {
569576
// As a side effect, will skip to the nearest safe point, which might be a comma
@@ -1390,14 +1397,7 @@ object Parsers {
13901397
else
13911398
Function(params, t)
13921399
}
1393-
def funTypeArgsRest(first: Tree, following: () => Tree) = {
1394-
val buf = new ListBuffer[Tree] += first
1395-
while (in.token == COMMA) {
1396-
in.nextToken()
1397-
buf += following()
1398-
}
1399-
buf.toList
1400-
}
1400+
14011401
var isValParamList = false
14021402

14031403
val t =
@@ -1413,11 +1413,10 @@ object Parsers {
14131413
val ts = funArgType() match {
14141414
case Ident(name) if name != tpnme.WILDCARD && in.isColon() =>
14151415
isValParamList = true
1416-
funTypeArgsRest(
1417-
typedFunParam(paramStart, name.toTermName, imods),
1418-
() => typedFunParam(in.offset, ident(), imods))
1416+
typedFunParam(paramStart, name.toTermName, imods) :: commaSeparated(
1417+
() => typedFunParam(in.offset, ident(), imods), RPAREN, readFirst = false)
14191418
case t =>
1420-
funTypeArgsRest(t, funArgType)
1419+
t :: commaSeparated(funArgType, RPAREN, readFirst = false)
14211420
}
14221421
accept(RPAREN)
14231422
if isValParamList || in.isArrow then
@@ -3121,7 +3120,7 @@ object Parsers {
31213120
*/
31223121
def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
31233122
val offset = accept(leading)
3124-
commaSeparated(importExpr(mkTree)) match {
3123+
commaSeparated(importExpr(mkTree), EMPTY) match {
31253124
case t :: rest =>
31263125
// The first import should start at the start offset of the keyword.
31273126
val firstPos =
@@ -3198,9 +3197,9 @@ object Parsers {
31983197
}
31993198
else ImportSelector(from)
32003199

3201-
def importSelectors(idOK: Boolean): List[ImportSelector] =
3200+
def importSelector(idOK: Boolean)(): ImportSelector =
32023201
val isWildcard = in.token == USCORE || in.token == GIVEN || isIdent(nme.raw.STAR)
3203-
val selector = atSpan(in.offset) {
3202+
atSpan(in.offset) {
32043203
in.token match
32053204
case USCORE => wildcardSelector()
32063205
case GIVEN => givenSelector()
@@ -3210,13 +3209,6 @@ object Parsers {
32103209
if !idOK then syntaxError(i"named imports cannot follow wildcard imports")
32113210
namedSelector(termIdent())
32123211
}
3213-
val rest =
3214-
if in.token == COMMA then
3215-
in.nextToken()
3216-
importSelectors(idOK = idOK && !isWildcard)
3217-
else
3218-
Nil
3219-
selector :: rest
32203212

32213213
def importSelection(qual: Tree): Tree =
32223214
if in.isIdent(nme.as) && qual.isInstanceOf[RefTree] then
@@ -3234,7 +3226,7 @@ object Parsers {
32343226
case GIVEN =>
32353227
mkTree(qual, givenSelector() :: Nil)
32363228
case LBRACE =>
3237-
mkTree(qual, inBraces(importSelectors(idOK = true)))
3229+
mkTree(qual, inBraces(commaSeparated(importSelector(idOK = true), RBRACE)))
32383230
case _ =>
32393231
if isIdent(nme.raw.STAR) then
32403232
mkTree(qual, wildcardSelector() :: Nil)
@@ -3291,7 +3283,7 @@ object Parsers {
32913283
var lhs = first match {
32923284
case id: Ident if in.token == COMMA =>
32933285
in.nextToken()
3294-
id :: commaSeparated(() => termIdent())
3286+
id :: commaSeparated(() => termIdent(), EMPTY)
32953287
case _ =>
32963288
first :: Nil
32973289
}
@@ -3560,7 +3552,7 @@ object Parsers {
35603552
val id = termIdent()
35613553
if (in.token == COMMA) {
35623554
in.nextToken()
3563-
val ids = commaSeparated(() => termIdent())
3555+
val ids = commaSeparated(() => termIdent(), EMPTY)
35643556
PatDef(mods1, id :: ids, TypeTree(), EmptyTree)
35653557
}
35663558
else {
@@ -3764,7 +3756,7 @@ object Parsers {
37643756
val derived =
37653757
if (isIdent(nme.derives)) {
37663758
in.nextToken()
3767-
commaSeparated(() => convertToTypeId(qualId()))
3759+
commaSeparated(() => convertToTypeId(qualId()), EMPTY)
37683760
}
37693761
else Nil
37703762
possibleTemplateStart()

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -654,13 +654,6 @@ object Scanners {
654654
insert(OUTDENT, offset)
655655
currentRegion = r.outer
656656
case _ =>
657-
lookAhead()
658-
if isAfterLineEnd
659-
&& (token == RPAREN || token == RBRACKET || token == RBRACE || token == OUTDENT)
660-
then
661-
() /* skip the trailing comma */
662-
else
663-
reset()
664657
case END =>
665658
if !isEndMarker then token = IDENTIFIER
666659
case COLON =>

tests/neg/t11900.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Error: tests/neg/t11900.scala:44:16 ---------------------------------------------------------------------------------
2+
44 | a => a + 1, // error: weird comma
3+
| ^
4+
| end of statement expected but ',' found
5+
-- Error: tests/neg/t11900.scala:48:16 ---------------------------------------------------------------------------------
6+
48 | println("a"), // error: weird comma
7+
| ^
8+
| end of statement expected but ',' found
9+
-- Error: tests/neg/t11900.scala:52:16 ---------------------------------------------------------------------------------
10+
52 | println("b"), // error: weird comma
11+
| ^
12+
| end of statement expected but ',' found
13+
-- [E032] Syntax Error: tests/neg/t11900.scala:64:8 --------------------------------------------------------------------
14+
64 | _*, // error
15+
| ^
16+
| pattern expected
17+
|
18+
| longer explanation available when compiling with `-explain`

tests/neg/t11900.scala

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
trait t11900 {
3+
// cf pos/trailing-commas
4+
//
5+
import scala.collection.{
6+
immutable,
7+
mutable,
8+
}
9+
10+
def h[A,
11+
]: List[A] = Nil
12+
13+
def u(
14+
x: Int,
15+
y: Int,
16+
)(using List[Int],
17+
Set[Int],
18+
)(using l: List[Int],
19+
s : Set[Int],
20+
): Int = 1
21+
22+
def g = List(
23+
1,
24+
2,
25+
3,
26+
)
27+
28+
def star =
29+
List(1, 2, 3, 4, 5) match {
30+
case List(
31+
1,
32+
2,
33+
3,
34+
) => false
35+
case List(
36+
1,
37+
2,
38+
_*,
39+
) => true
40+
}
41+
42+
def f =
43+
List(1, 2, 3).map {
44+
a => a + 1, // error: weird comma
45+
}
46+
47+
class A() {
48+
println("a"), // error: weird comma
49+
}
50+
51+
def b() = {
52+
println("b"), // error: weird comma
53+
}
54+
55+
def starcrossed =
56+
List(1, 2, 3, 4, 5) match {
57+
case List(
58+
1,
59+
2,
60+
3,
61+
) => false
62+
case List(
63+
1,
64+
_*, // error
65+
2,
66+
) => true
67+
}
68+
69+
def p(p: (Int,
70+
String,
71+
)
72+
): Unit
73+
74+
def q: (Int,
75+
String,
76+
)
77+
78+
val z = 42
79+
}

tests/neg/trailingCommas.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,28 @@ object `package` {
5555

5656
case class Foo(foo: Any)
5757
case class Bar(foo: Any)
58-
}
58+
}
59+
60+
// Unparenthesized lists
61+
trait Deriv1[T]
62+
object Deriv1 {
63+
def derived[T]: Deriv1[T] = new Deriv1[T] {}
64+
}
65+
66+
trait Deriv2[T]
67+
object Deriv2 {
68+
def derived[T]: Deriv2[T] = new Deriv2[T] {}
69+
}
70+
71+
class Derives1 derives Deriv1, Deriv2,
72+
object End // error: an identifier expected, but 'object' found
73+
74+
class Derives2 derives Deriv1,
75+
Deriv2,
76+
object End2 // error: an identifier expected, but 'object' found
77+
78+
val a,
79+
b,
80+
c,
81+
= (1, 2, 3) // error
82+
val x, y, z, = (1, 2, 3) // error // error

tests/pos/comma-separated.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Bar[T]
2+
object Bar {
3+
def derived[T]: Bar[T] = new Bar[T] {}
4+
}
5+
6+
trait Baz[T]
7+
object Baz {
8+
def derived[T]: Baz[T] = new Baz[T] {}
9+
}
10+
11+
class Foo derives Bar, Baz
12+
13+
class Foo2 derives Bar,
14+
Baz
15+
16+
val x, y, z = (1, 2, 3)
17+
val a,
18+
b,
19+
c = (1, 2, 3)

0 commit comments

Comments
 (0)