Skip to content

Fix #8715: enforce syntax for _* #8732

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

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 1 addition & 1 deletion community-build/community-projects/stdLib213
49 changes: 36 additions & 13 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,14 @@ object Parsers {
finally staged = saved
}

private var inArguments = false
private def withinArguments[T](in: Boolean)(body: => T): T = {
val saved = inArguments
inArguments = in
try body
finally inArguments = saved
}

/* ---------- TREE CONSTRUCTION ------------------------------------------- */

/** Convert tree to formal parameter list
Expand Down Expand Up @@ -2009,7 +2017,14 @@ object Parsers {
val uscoreStart = in.skipToken()
if (isIdent(nme.raw.STAR)) {
in.nextToken()
if (in.token != RPAREN) syntaxError(SeqWildcardPatternPos(), uscoreStart)
if in.token != RPAREN || !inArguments && sourceVersion.isAtLeast(`3.1`) then
syntaxError(SeqWildcardPatternPos(), uscoreStart)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe be more aggressive and demand this for 3.0? In any case, we should do migration warning and it would be nice to also offer a rewrite.

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, I believe the warning that this is no longer supported is more important than the error that it's in the wrong place. The theory is that any code that has xs @ _* will have already been compiled by Scala 2 (in most cases, at least)

else if !inArguments then
ctx.errorOrMigrationWarning(
"`_*` may only be used for last argument. Possibly using it inside non-method call parenthesis?",
in.sourcePos(uscoreStart)
)

Typed(t, atSpan(uscoreStart) { Ident(tpnme.WILDCARD_STAR) })
}
else {
Expand Down Expand Up @@ -2185,7 +2200,9 @@ object Parsers {
placeholderParams = param :: placeholderParams
atSpan(start) { Ident(pname) }
case LPAREN =>
atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOpt())) }
withinArguments(in = false) {
atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOpt())) }
}
case LBRACE | INDENT =>
canApply = false
blockExpr()
Expand Down Expand Up @@ -2274,14 +2291,16 @@ object Parsers {
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
* | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')'
*/
def parArgumentExprs(): (List[Tree], Boolean) = inParens {
if in.token == RPAREN then
(Nil, false)
else if isIdent(nme.using) then
in.nextToken()
(commaSeparated(argumentExpr), true)
else
(commaSeparated(argumentExpr), false)
def parArgumentExprs(): (List[Tree], Boolean) = withinArguments(in = true) {
inParens {
if in.token == RPAREN then
(Nil, false)
else if isIdent(nme.using) then
in.nextToken()
(commaSeparated(argumentExpr), true)
else
(commaSeparated(argumentExpr), false)
}
}

/** ArgumentExprs ::= ParArgumentExprs
Expand Down Expand Up @@ -2632,12 +2651,16 @@ object Parsers {
// `x: _*' is parsed in `ascription'
if (isIdent(nme.raw.STAR)) {
in.nextToken()
if (in.token != RPAREN) syntaxError(SeqWildcardPatternPos(), wildIdent.span)
if (in.token != RPAREN || !inArguments) syntaxError(SeqWildcardPatternPos(), wildIdent.span)
atSpan(wildIdent.span) { Ident(tpnme.WILDCARD_STAR) }
}
else wildIdent
case LPAREN =>
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
atSpan(in.offset) {
withinArguments(in = false) {
makeTupleOrParens(inParens(patternsOpt()))
}
}
case QUOTE =>
simpleExpr()
case XMLSTART =>
Expand Down Expand Up @@ -2671,7 +2694,7 @@ object Parsers {
* | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’
*/
def argumentPatterns(): List[Tree] =
inParens(patternsOpt())
withinArguments(in = true) { inParens(patternsOpt()) }

/* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */

Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/vulpix/ParallelTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
lazy val actualErrors = reporters.foldLeft(0)(_ + _.errorCount)
def hasMissingAnnotations = getMissingExpectedErrors(errorMap, reporters.iterator.flatMap(_.errors))
def showErrors = "-> following the errors:\n" +
reporters.flatMap(_.allErrors.map(e => e.pos.toString + ": " + e.message)).mkString(start = "at ", sep = "\n at ", end = "")
reporters.flatMap(_.allErrors.map(e => e.pos.line.toString + ": " + e.message)).mkString(start = "at ", sep = "\n at ", end = "")

if (compilerCrashed) Some(s"Compiler crashed when compiling: ${testSource.title}")
else if (actualErrors == 0) Some(s"\nNo errors found when compiling neg test $testSource")
Expand Down
10 changes: 5 additions & 5 deletions tests/neg/i7972.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
object O {
def m1(a: Int*) = (a: _*) // error: Cannot return repeated parameter type Int*
def m1(a: Int*) = (a: _*) // error // error: Cannot return repeated parameter type Int*
def m2(a: Int*) = { // error: Cannot return repeated parameter type Int*
val b = (a: _*) // error: Cannot return repeated parameter type Int*
val b = (a: _*) // error // error: Cannot return repeated parameter type Int*
b
}
def m3(a: Int*): Any = {
val b = (a: _*) // error: Cannot return repeated parameter type Int*
val b = (a: _*) // error // error: Cannot return repeated parameter type Int*
b
}
def m4(a: 2*) = (a: _*) // error: Cannot return repeated parameter type Int*
def m4(a: 2*) = (a: _*) // error // error: Cannot return repeated parameter type Int*

}

class O(a: Int*) {
val m = (a: _*) // error: Cannot return repeated parameter type Int*
val m = (a: _*) // error // error: Cannot return repeated parameter type Int*
}

2 changes: 2 additions & 0 deletions tests/neg/i8715.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@main
def Test = List(42) match { case List(xs @ (ys: _*)) => xs } // error
29 changes: 29 additions & 0 deletions tests/neg/t5702-neg-bad-and-wild.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

object Test {
case class K(i: Int)

def main(args: Array[String]) = {
val k = new K(9)
val is = List(1,2,3)

is match {
case List(1, _*,) => // error // error // error: bad use of _* (a sequence pattern must be the last pattern)
// illegal start of simple pattern
case List(1, _*3,) => // error // error: illegal start of simple pattern
//case List(1, _*3:) => // poor recovery by parens
case List(1, x*) => // error: use _* to match a sequence
case List(x*, 1) => // error: trailing * is not a valid pattern
case (1, x*) => // error: trailing * is not a valid pattern
case (1, x@_*) => // error: bad use of _* (sequence pattern not allowed)
}

// good syntax, bad semantics, detected by typer
//gowild.scala:14: error: star patterns must correspond with varargs parameters
val K(x @ _*) = k
val K(ns @ _*, x) = k // error: bad use of _* (a sequence pattern must be the last pattern)
val (b, _ * ) = (5,6) // error: bad use of _* (sequence pattern not allowed)
// no longer complains
//bad-and-wild.scala:15: error: ')' expected but '}' found.
}
}

4 changes: 4 additions & 0 deletions tests/pos-scala2/i8715b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// from stdlib
class Test {
def printf(text: String, args: Any*): Unit = { System.out.print(text format (args : _*)) }
}
29 changes: 0 additions & 29 deletions tests/untried/neg/t5702-neg-bad-and-wild.scala

This file was deleted.