@@ -641,6 +641,19 @@ object Parsers {
641641 def inParensWithCommas [T ](body : => T ): T = enclosedWithCommas(LPAREN , body)
642642 def inBracketsWithCommas [T ](body : => T ): T = enclosedWithCommas(LBRACKET , body)
643643
644+ /** Like inParensWithCommas but also preserves trailing comma for tuple detection. */
645+ def inParensWithTrailingComma [T ](body : => T ): T =
646+ accept(LPAREN )
647+ in.currentRegion.withPreserveTrailingComma:
648+ val closing = RPAREN
649+ val isEmpty = in.token == closing
650+ val ts = body
651+ if in.token != closing then
652+ val prefix = if ! isEmpty && canStartExprTokens3.contains(in.token) then " ',' or " else " "
653+ syntaxErrorOrIncomplete(ExpectedTokenButFound (closing, in.token, prefix))
654+ if in.token == closing then in.nextToken()
655+ ts
656+
644657 def inBracesOrIndented [T ](body : => T , rewriteWithColon : Boolean = false ): T =
645658 if in.token == INDENT then
646659 val rewriteToBraces = in.rewriteNoIndent
@@ -1817,28 +1830,40 @@ object Parsers {
18171830 if in.token == RPAREN then
18181831 in.nextToken()
18191832 functionRest(Nil )
1833+ else if in.token == COMMA then
1834+ // Empty tuple with comma: (,)
1835+ in.nextToken()
1836+ accept(RPAREN )
1837+ val tuple = atSpan(start)(makeTupleOrParens(Nil , trailingComma = true ))
1838+ typeRest :
1839+ infixTypeRest(inContextBound):
1840+ refinedTypeRest :
1841+ withTypeRest :
1842+ annotTypeRest :
1843+ simpleTypeRest(tuple)
18201844 else
18211845 val paramStart = in.offset
18221846 def addErased () =
18231847 erasedArgs.addOne(isErased)
18241848 if isErased then in.skipToken()
18251849 addErased()
18261850 val (args, trailingComma) =
1827- in.currentRegion.withCommasExpected:
1828- funArgType() match
1829- case Ident (name) if name != tpnme.WILDCARD && in.isColon =>
1830- def funParam (start : Offset , mods : Modifiers ) =
1831- atSpan(start):
1832- addErased()
1833- typedFunParam(in.offset, ident(), imods)
1834- commaSeparatedRestWithTrailingComma(
1835- typedFunParam(paramStart, name.toTermName, imods),
1836- () => funParam(in.offset, imods))
1837- case t =>
1838- def funArg () =
1839- erasedArgs.addOne(false )
1840- funArgType()
1841- commaSeparatedRestWithTrailingComma(t, funArg)
1851+ in.currentRegion.withPreserveTrailingComma:
1852+ in.currentRegion.withCommasExpected:
1853+ funArgType() match
1854+ case Ident (name) if name != tpnme.WILDCARD && in.isColon =>
1855+ def funParam (start : Offset , mods : Modifiers ) =
1856+ atSpan(start):
1857+ addErased()
1858+ typedFunParam(in.offset, ident(), imods)
1859+ commaSeparatedRestWithTrailingComma(
1860+ typedFunParam(paramStart, name.toTermName, imods),
1861+ () => funParam(in.offset, imods))
1862+ case t =>
1863+ def funArg () =
1864+ erasedArgs.addOne(false )
1865+ funArgType()
1866+ commaSeparatedRestWithTrailingComma(t, funArg)
18421867 accept(RPAREN )
18431868 if in.isArrow || isPureArrow || erasedArgs.contains(true ) then
18441869 functionRest(args)
@@ -2885,7 +2910,7 @@ object Parsers {
28852910 atSpan(start) { Ident (pname) }
28862911 case LPAREN =>
28872912 atSpan(in.offset) {
2888- val (ts, trailingComma) = inParensWithCommas (exprsInParensOrBindingsWithTrailingComma())
2913+ val (ts, trailingComma) = inParensWithTrailingComma (exprsInParensOrBindingsWithTrailingComma())
28892914 makeTupleOrParens(ts, trailingComma)
28902915 }
28912916 case LBRACE | INDENT =>
@@ -2995,6 +3020,9 @@ object Parsers {
29953020 */
29963021 def exprsInParensOrBindingsWithTrailingComma (): (List [Tree ], Boolean ) =
29973022 if in.token == RPAREN then (Nil , false )
3023+ else if in.token == COMMA then
3024+ in.nextToken() // skip the comma
3025+ (Nil , true ) // empty tuple with comma: (,)
29983026 else in.currentRegion.withCommasExpected {
29993027 var isFormalParams = false
30003028 def exprOrBinding () =
@@ -3417,7 +3445,7 @@ object Parsers {
34173445 wildcardIdent()
34183446 case LPAREN =>
34193447 atSpan(in.offset) {
3420- val (ts, trailingComma) = inParensWithCommas (patternsOptWithTrailingComma())
3448+ val (ts, trailingComma) = inParensWithTrailingComma (patternsOptWithTrailingComma())
34213449 makeTupleOrParens(ts, trailingComma)
34223450 }
34233451 case QUOTE =>
@@ -3472,7 +3500,11 @@ object Parsers {
34723500 * Used for tuple syntax like (a,) to force tuple interpretation.
34733501 */
34743502 def patternsOptWithTrailingComma (location : Location = Location .InPattern ): (List [Tree ], Boolean ) =
3475- if in.token == RPAREN then (Nil , false ) else patternsWithTrailingComma(location)
3503+ if in.token == RPAREN then (Nil , false )
3504+ else if in.token == COMMA then
3505+ in.nextToken() // skip the comma
3506+ (Nil , true ) // empty tuple with comma: (,)
3507+ else patternsWithTrailingComma(location)
34763508
34773509 /** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
34783510 * | ‘(’ [Patterns ‘,’] PatVar ‘*’ [‘,’ Patterns] ‘)’
0 commit comments