diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 523891400e9c..70c0b410c752 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1961,10 +1961,10 @@ object Parsers { /** ImportSelectors ::= `{' {ImportSelector `,'} (ImportSelector | `_') `}' */ - def importSelectors(): List[Tree] = - if (in.token == RBRACE) Nil + def importSelectors(): List[Tree] = { + val sel = importSelector() + if (in.token == RBRACE) sel :: Nil else { - val sel = importSelector() sel :: { if (!isWildcardArg(sel) && in.token == COMMA) { in.nextToken() @@ -1973,7 +1973,7 @@ object Parsers { else Nil } } - + } /** ImportSelector ::= id [`=>' id | `=>' `_'] */ def importSelector(): Tree = { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index d49eecee7637..e89b5f565a3a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -390,7 +390,16 @@ object Scanners { val nextLastOffset = lastCharOffset lookahead() if (token != ELSE) reset(nextLastOffset) + } else if (token == COMMA){ + val nextLastOffset = lastCharOffset + lookahead() + if (isAfterLineEnd() && (token == RPAREN || token == RBRACKET || token == RBRACE)) { + /* skip the trailing comma */ + } else if (token == EOF) { // e.g. when the REPL is parsing "val List(x, y, _*," + /* skip the trailing comma */ + } else reset(nextLastOffset) } + } /** Is current token first one after a newline? */ diff --git a/tests/neg/trailingCommas/trailingCommas.scala b/tests/neg/trailingCommas/trailingCommas.scala new file mode 100644 index 000000000000..356f735ce656 --- /dev/null +++ b/tests/neg/trailingCommas/trailingCommas.scala @@ -0,0 +1,58 @@ +package foo + +// Multi-line only cases: make sure trailing commas are only supported when multi-line + +trait ArgumentExprs1 { validMethod(23, "bar", )(Ev0, Ev1) } // error // error +trait ArgumentExprs2 { validMethod(23, "bar")(Ev0, Ev1, ) } // error // error +trait ArgumentExprs3 { new ValidClass(23, "bar", )(Ev0, Ev1) } // error // error +trait ArgumentExprs4 { new ValidClass(23, "bar")(Ev0, Ev1, ) } // error // error + +trait Params1 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error // error + +trait Params2 { def f(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1, ) = 1 } // error // error // error + +trait ClassParams1 { final class C(foo: Int, bar: String, )(implicit ev0: Ev0, ev1: Ev1) } // error +trait ClassParams2 { final class C(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1, ) } // error + +trait SimpleExpr { (23, "bar", ) } // error +trait TypeArgs { def f: ValidGeneric[Int, String, ] } // error + +trait TypeParamClause { type C[A, B, ] } // error +trait FunTypeParamClause { def f[A, B, ] } // error + +trait SimpleType { def f: (Int, String, ) } // error +trait FunctionArgTypes { def f: (Int, String, ) => Boolean } // error + +trait SimplePattern { val (foo, bar, ) = null: Any } // error + +trait ImportSelectors { import foo.{ Ev0, Ev1, } } // error + +trait Import { import foo.Ev0, foo.Ev1, } // error + +trait ValDcl { val foo, bar, = 23 } // error + +trait VarDef { var foo, bar, = _ } // error + +trait PatDef { val Foo(foo), Bar(bar), = bippy } // error + + +// The Tuple 1 cases + +// the Tuple1 value case: make sure that the possible "(23, )" syntax for Tuple1 doesn't compile to "23" +trait SimpleExpr2 { (23, ) } // error + +// the Tuple1 type case: make sure that the possible "(Int, )" syntax for Tuple1[Int] doesn't compile to "Int" +trait SimpleType2 { def f: (Int, ) } // error + +// Test utilities +object `package` { + sealed trait Ev0; implicit object Ev0 extends Ev0 + sealed trait Ev1; implicit object Ev1 extends Ev1 + + class ValidClass(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1) + class ValidGeneric[A, B] + def validMethod(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1): Int = 1 + + case class Foo(foo: Any) + case class Bar(foo: Any) +} \ No newline at end of file diff --git a/tests/pos/trailingCommas/trailingCommas.scala b/tests/pos/trailingCommas/trailingCommas.scala new file mode 100644 index 000000000000..6170fda0fe9b --- /dev/null +++ b/tests/pos/trailingCommas/trailingCommas.scala @@ -0,0 +1,155 @@ +package foo + +trait ArgumentExprs1 { + def f(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1) = 1 + f( + 23, + "bar", + )( + Ev0, + Ev1, + ) + + // test arg exprs in the presence of varargs + def g(x: Int, y: Int*) = 1 + g(1,2, + ) + g(1,List(2, 3): _*, + ) +} + +trait ArgumentExprs2 { + class C(foo: Int, bar: String)(implicit ev0: Ev0, ev1: Ev1) + new C( + 23, + "bar", + )( + Ev0, + Ev1, + ) +} + +trait Params { + def f( + foo: Int, + bar: String, + )(implicit + ev0: Ev0, + ev1: Ev1, + ): Unit +} + +trait ClassParams { + class C( + foo: Int, + bar: String, + )(implicit + ev0: Ev0, + ev1: Ev1, + ) + + // test class params in the precense of varargs + case class D(i: Int*, + ) +} + +trait SimpleExpr1 { + def f: (Int, String) = ( + 23, + "bar", + ) + + // the Tuple1 value case, the trailing comma is ignored so the type is Int and the value 23 + def g: Int = ( + 23, + ) +} + +trait TypeArgs { + class C[A, B] + def f: C[ + Int, + String, + ] +} + +trait TypeParamClause { + class C[ + A, + B, + ] +} + +trait FunTypeParamClause { + def f[ + A, + B, + ]: Unit +} + +trait SimpleType { + def f: ( + Int, + String, + ) + + // the Tuple1 type case, the trailing comma is ignored so the type is Int and the value 23 + def g: ( + Int, + ) = 23 +} + +trait FunctionArgTypes { + def f: ( + Int, + String, + ) => Boolean +} + +trait SimplePattern { + val ( + foo, + bar, + ) = null: Any + + // test '@' syntax in patterns + Some(1) match { + case Some(x @ 1, + ) => x + } + + // test ': _*' syntax in patterns + List(1, 2, 3) match { + case List(1, 2, x : _*, + ) => 1 + } + + // test varargs in patterns + val List(x, y, z: _*, + ) = 42 :: 17 :: Nil +} + +trait ImportSelectors { + import foo.{ + Ev0, + Ev1, + } +} + +trait Bindings { + def g(f: (Int, String) => Boolean): Unit + + g(( + foo, + bar, + ) => true) +} + +// Import, ids, ValDcl, VarDcl, VarDef, PatDef use commas, but not inside paren, bracket or brace, +// so they don't support an optional trailing comma + +// test utilities +object `package` { + sealed trait Ev0; implicit object Ev0 extends Ev0 + sealed trait Ev1; implicit object Ev1 extends Ev1 +} \ No newline at end of file diff --git a/tests/run/trailingCommas/trailingCommas.check b/tests/run/trailingCommas/trailingCommas.check new file mode 100644 index 000000000000..fec73e47a3d7 --- /dev/null +++ b/tests/run/trailingCommas/trailingCommas.check @@ -0,0 +1,2 @@ +42 +17 \ No newline at end of file diff --git a/tests/run/trailingCommas/trailingCommas.scala b/tests/run/trailingCommas/trailingCommas.scala new file mode 100644 index 000000000000..319737b0e299 --- /dev/null +++ b/tests/run/trailingCommas/trailingCommas.scala @@ -0,0 +1,8 @@ +object Test { + val List(x, y, z: _ *, + ) = 42 :: 17 :: Nil + def main(args: Array[String]): Unit = { + Console.println(x) + Console.println(y) + } +}