@@ -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 *
0 commit comments