Skip to content

Fix #3408 Implement SIP-27: Trailing Commas #3463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -1973,7 +1973,7 @@ object Parsers {
else Nil
}
}

}
/** ImportSelector ::= id [`=>' id | `=>' `_']
*/
def importSelector(): Tree = {
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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? */
Expand Down
58 changes: 58 additions & 0 deletions tests/neg/trailingCommas/trailingCommas.scala
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the two errors emitted here? One is the invalid trailing comma. What is the other one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first one is illegal start of simple expression. The second is as follow

5 |trait ArgumentExprs1 { validMethod(23, "bar", )(Ev0, Ev1) } // error // error
  |                                            ^
  |too many arguments for method validMethod: (foo: Int, bar: String)(implicit ev0: foo.package.Ev0, ev1: foo.package.Ev1): Int

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)
}
155 changes: 155 additions & 0 deletions tests/pos/trailingCommas/trailingCommas.scala
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions tests/run/trailingCommas/trailingCommas.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
42
17
8 changes: 8 additions & 0 deletions tests/run/trailingCommas/trailingCommas.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}