Skip to content

Commit ef928a3

Browse files
committed
implemented variant A about allowing , in the simple
1 parent eb3243e commit ef928a3

File tree

3 files changed

+95
-19
lines changed

3 files changed

+95
-19
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -576,10 +576,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
576576
def makeSelfDef(name: TermName, tpt: Tree)(using Context): ValDef =
577577
ValDef(name, tpt, EmptyTree).withFlags(PrivateLocal)
578578

579-
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match
580-
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
581-
case t :: Nil => Parens(t)
582-
case _ => Tuple(ts)
579+
def makeTupleOrParens(ts: List[Tree], trailingComma: Boolean = false)(using Context): Tree =
580+
if trailingComma then Tuple(ts)
581+
else ts match
582+
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
583+
case t :: Nil => Parens(t)
584+
case _ => Tuple(ts)
583585

584586
def makeTuple(ts: List[Tree])(using Context): Tree = ts match
585587
case (t: NamedArg) :: Nil => Tuple(t :: Nil)

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

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,15 @@ object Parsers {
660660
commaSeparatedRest(part(), part)
661661
}
662662

663+
/** Like commaSeparated but also detects trailing comma.
664+
* Returns (list, trailingComma) where trailingComma is true if the list
665+
* ended with a comma followed by a closing token.
666+
*/
667+
def commaSeparatedWithTrailingComma[T](part: () => T): (List[T], Boolean) =
668+
in.currentRegion.withCommasExpected {
669+
commaSeparatedRestWithTrailingComma(part(), part)
670+
}
671+
663672
/** {`,` <part>}
664673
*
665674
* currentRegion.commasExpected has to be set separately.
@@ -673,6 +682,22 @@ object Parsers {
673682
ts.toList
674683
else leading :: Nil
675684

685+
/** Like commaSeparatedRest but also detects trailing comma.
686+
* Returns (list, trailingComma) where trailingComma is true if the list
687+
* ended with a comma followed by a closing token (RPAREN, RBRACKET, RBRACE).
688+
* Used for tuple syntax like (A,) or (a,).
689+
*/
690+
def commaSeparatedRestWithTrailingComma[T](leading: T, part: () => T): (List[T], Boolean) =
691+
if in.token == COMMA then
692+
val ts = new ListBuffer[T] += leading
693+
while in.token == COMMA do
694+
in.nextToken()
695+
if in.token == RPAREN || in.token == RBRACKET || in.token == RBRACE then
696+
return (ts.toList, true)
697+
ts += part()
698+
(ts.toList, false)
699+
else (leading :: Nil, false)
700+
676701
def maybeNamed(op: () => Tree): () => Tree = () =>
677702
if isIdent && in.lookahead.token == EQUALS && sourceVersion.enablesNamedTuples then
678703
atSpan(in.offset):
@@ -1798,28 +1823,28 @@ object Parsers {
17981823
erasedArgs.addOne(isErased)
17991824
if isErased then in.skipToken()
18001825
addErased()
1801-
val args =
1826+
val (args, trailingComma) =
18021827
in.currentRegion.withCommasExpected:
18031828
funArgType() match
18041829
case Ident(name) if name != tpnme.WILDCARD && in.isColon =>
18051830
def funParam(start: Offset, mods: Modifiers) =
18061831
atSpan(start):
18071832
addErased()
18081833
typedFunParam(in.offset, ident(), imods)
1809-
commaSeparatedRest(
1834+
commaSeparatedRestWithTrailingComma(
18101835
typedFunParam(paramStart, name.toTermName, imods),
18111836
() => funParam(in.offset, imods))
18121837
case t =>
18131838
def funArg() =
18141839
erasedArgs.addOne(false)
18151840
funArgType()
1816-
commaSeparatedRest(t, funArg)
1841+
commaSeparatedRestWithTrailingComma(t, funArg)
18171842
accept(RPAREN)
18181843
if in.isArrow || isPureArrow || erasedArgs.contains(true) then
18191844
functionRest(args)
18201845
else
18211846
val tuple = atSpan(start):
1822-
makeTupleOrParens(args.mapConserve(convertToElem))
1847+
makeTupleOrParens(args.mapConserve(convertToElem), trailingComma)
18231848
typeRest:
18241849
infixTypeRest(inContextBound):
18251850
refinedTypeRest:
@@ -2120,7 +2145,8 @@ object Parsers {
21202145
def simpleType1() = simpleTypeRest {
21212146
if in.token == LPAREN then
21222147
atSpan(in.offset) {
2123-
makeTupleOrParens(inParensWithCommas(argTypes(namedOK = false, wildOK = true, tupleOK = true)))
2148+
val (ts, trailingComma) = inParensWithCommas(argTypesWithTrailingComma(namedOK = false, wildOK = true, tupleOK = true))
2149+
makeTupleOrParens(ts, trailingComma)
21242150
}
21252151
else if in.token == LBRACE then
21262152
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
@@ -2209,6 +2235,12 @@ object Parsers {
22092235
* NameAndType ::= id ‘:’ Type
22102236
*/
22112237
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
2238+
argTypesWithTrailingComma(namedOK, wildOK, tupleOK)._1
2239+
2240+
/** Like argTypes but also returns whether a trailing comma was present.
2241+
* Used for tuple syntax like (A,) to force tuple interpretation.
2242+
*/
2243+
def argTypesWithTrailingComma(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): (List[Tree], Boolean) =
22122244
def wildCardCheck(gen: Tree): Tree =
22132245
val t = gen
22142246
if wildOK then t else rejectWildcardType(t)
@@ -2234,12 +2266,12 @@ object Parsers {
22342266
NamedArg(name, argType())
22352267

22362268
if namedOK && (isIdent && in.lookahead.token == EQUALS) then
2237-
commaSeparated(() => namedTypeArg())
2269+
commaSeparatedWithTrailingComma(() => namedTypeArg())
22382270
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then
2239-
commaSeparated(() => nameAndType())
2271+
commaSeparatedWithTrailingComma(() => nameAndType())
22402272
else
2241-
commaSeparated(() => typeArg())
2242-
end argTypes
2273+
commaSeparatedWithTrailingComma(() => typeArg())
2274+
end argTypesWithTrailingComma
22432275

22442276
def paramTypeOf(core: () => Tree): Tree =
22452277
if in.token == ARROW || isPureArrow(nme.PUREARROW) then
@@ -2852,7 +2884,10 @@ object Parsers {
28522884
placeholderParams = param :: placeholderParams
28532885
atSpan(start) { Ident(pname) }
28542886
case LPAREN =>
2855-
atSpan(in.offset) { makeTupleOrParens(inParensWithCommas(exprsInParensOrBindings())) }
2887+
atSpan(in.offset) {
2888+
val (ts, trailingComma) = inParensWithCommas(exprsInParensOrBindingsWithTrailingComma())
2889+
makeTupleOrParens(ts, trailingComma)
2890+
}
28562891
case LBRACE | INDENT =>
28572892
canApply = false
28582893
blockExpr()
@@ -2948,12 +2983,18 @@ object Parsers {
29482983
end newExpr
29492984

29502985
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
2951-
* | NamedExprInParens {‘,’ NamedExprInParens}
2986+
* | NamedExprInParens {',' NamedExprInParens}
29522987
* Bindings ::= Binding {`,' Binding}
29532988
* NamedExprInParens ::= id '=' ExprInParens
29542989
*/
29552990
def exprsInParensOrBindings(): List[Tree] =
2956-
if in.token == RPAREN then Nil
2991+
exprsInParensOrBindingsWithTrailingComma()._1
2992+
2993+
/** Like exprsInParensOrBindings but also returns whether a trailing comma was present.
2994+
* Used for tuple syntax like (a,) to force tuple interpretation.
2995+
*/
2996+
def exprsInParensOrBindingsWithTrailingComma(): (List[Tree], Boolean) =
2997+
if in.token == RPAREN then (Nil, false)
29572998
else in.currentRegion.withCommasExpected {
29582999
var isFormalParams = false
29593000
def exprOrBinding() =
@@ -2963,7 +3004,7 @@ object Parsers {
29633004
val t = maybeNamed(exprInParens)()
29643005
if t.isInstanceOf[ValDef] then isFormalParams = true
29653006
t
2966-
commaSeparatedRest(exprOrBinding(), exprOrBinding)
3007+
commaSeparatedRestWithTrailingComma(exprOrBinding(), exprOrBinding)
29673008
}
29683009

29693010
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
@@ -3375,7 +3416,10 @@ object Parsers {
33753416
case USCORE =>
33763417
wildcardIdent()
33773418
case LPAREN =>
3378-
atSpan(in.offset) { makeTupleOrParens(inParensWithCommas(patternsOpt())) }
3419+
atSpan(in.offset) {
3420+
val (ts, trailingComma) = inParensWithCommas(patternsOptWithTrailingComma())
3421+
makeTupleOrParens(ts, trailingComma)
3422+
}
33793423
case QUOTE =>
33803424
simpleExpr(Location.InPattern)
33813425
case XMLSTART =>
@@ -3410,16 +3454,26 @@ object Parsers {
34103454
p
34113455

34123456
/** Patterns ::= Pattern [`,' Pattern]
3413-
* | NamedPattern {‘,’ NamedPattern}
3457+
* | NamedPattern {',' NamedPattern}
34143458
* NamedPattern ::= id '=' Pattern
34153459
*/
34163460
def patterns(location: Location = Location.InPattern): List[Tree] =
34173461
commaSeparated(maybeNamed(() => pattern(location)))
34183462
// check that patterns are all named or all unnamed is done at desugaring
34193463

3464+
/** Like patterns but also returns whether a trailing comma was present. */
3465+
def patternsWithTrailingComma(location: Location = Location.InPattern): (List[Tree], Boolean) =
3466+
commaSeparatedWithTrailingComma(maybeNamed(() => pattern(location)))
3467+
34203468
def patternsOpt(location: Location = Location.InPattern): List[Tree] =
34213469
if (in.token == RPAREN) Nil else patterns(location)
34223470

3471+
/** Like patternsOpt but also returns whether a trailing comma was present.
3472+
* Used for tuple syntax like (a,) to force tuple interpretation.
3473+
*/
3474+
def patternsOptWithTrailingComma(location: Location = Location.InPattern): (List[Tree], Boolean) =
3475+
if in.token == RPAREN then (Nil, false) else patternsWithTrailingComma(location)
3476+
34233477
/** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
34243478
* | ‘(’ [Patterns ‘,’] PatVar ‘*’ [‘,’ Patterns] ‘)’
34253479
*
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Test trailing comma syntax for tuples (B variant)
2+
// This allows (A,) to be a single-element type tuple and (a,) to be a single-element value tuple
3+
4+
object TupleTrailingComma:
5+
// Type tuples with trailing comma
6+
type T1 = (Int,) // single-element type tuple
7+
type T2 = (Int, String,) // trailing comma with multiple elements
8+
type T3 = (Int, String) // regular tuple (should still work)
9+
10+
// Value tuples with trailing comma
11+
val v1: (Int,) = (1,) // single-element value tuple
12+
val v2 = (1, 2,) // trailing comma with multiple elements
13+
val v3 = (1, 2) // regular tuple (should still work)
14+
15+
// Pattern matching with trailing comma
16+
def test(x: Any): Unit = x match
17+
case (a,) => println(s"single: $a")
18+
case (a, b,) => println(s"pair: $a, $b")
19+
case (a, b) => println(s"pair no trailing: $a, $b")
20+
case _ => println("other")

0 commit comments

Comments
 (0)