Repository: scalameta/scalameta (scala)

Issue:
"repeated argument not allowed here" false negative parse error
```
/home/geirsson/frontend/common/app/views/support/package.scala:87: error: repeated argument not allowed here
def withJsoup(html: Html)(cleaners: HtmlCleaner*): Html = withJsoup(html.body) { cleaners: _* }
                                                                                             ^
```

Discovered while running scalafix on https://github.com/guardian/frontend

Pull request:
Fixes #1063.

Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
index 39da3ea4d6..ea9d168e5b 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala
@@ -2216,7 +2216,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
 
   def argumentExprsWithUsing(location: Location = NoStat): (List[Term], Boolean) = token match {
     case LeftBrace() =>
-      (List(blockExpr()), false)
+      (List(blockExpr(allowRepeated = true)), false)
     case LeftParen() =>
       inParensOnOpen(token match {
         case RightParen() =>
@@ -2239,16 +2239,16 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     trees
   }
 
-  def blockExpr(isBlockOptional: Boolean = false): Term = {
+  def blockExpr(isBlockOptional: Boolean = false, allowRepeated: Boolean = false): Term = {
     if (ahead(token.is[CaseIntro] || (token.is[Ellipsis] && next(token.is[KwCase]))))
       autoPos(Term.PartialFunction {
         if (acceptOpt[LeftBrace]) inBracesAfterOpen(caseClauses()) else indented(caseClauses())
       })
-    else block(isBlockOptional)
+    else block(isBlockOptional, allowRepeated)
   }
 
-  def block(isBlockOptional: Boolean = false): Term = autoPos {
-    def blockWithStats = Term.Block(blockStatSeq())
+  def block(isBlockOptional: Boolean = false, allowRepeated: Boolean = false): Term = autoPos {
+    def blockWithStats = Term.Block(blockStatSeq(allowRepeated = allowRepeated))
     if (!isBlockOptional && acceptOpt[LeftBrace]) {
       inBracesAfterOpen(blockWithStats)
     } else {
@@ -4202,7 +4202,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     }
   }
 
-  def blockStatSeq(): List[Stat] = listBy[Stat] { stats =>
+  def blockStatSeq(allowRepeated: Boolean = false): List[Stat] = listBy[Stat] { stats =>
     while (!token.is[CaseDefEnd] && !in.observeOutdented()) token match {
       case _: KwExport =>
         stats += exportStmt()
@@ -4220,7 +4220,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         stats += localDef(None)
         if (!token.is[CaseDefEnd]) acceptStatSepOpt()
       case ExprIntro() =>
-        stats += stat(expr(location = BlockStat, allowRepeated = false))
+        stats += stat(expr(location = BlockStat, allowRepeated = allowRepeated))
         if (!token.is[CaseDefEnd]) acceptStatSep()
       case StatSep() =>
         next()
@@ -4231,6 +4231,11 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       case _ =>
         syntaxError("illegal start of statement", at = token)
     }
+    if (allowRepeated && stats.length > 1)
+      stats.foreach {
+        case t: Term.Repeated => syntaxError("repeated argument not allowed here", at = t)
+        case _ =>
+      }
   }
 
   def packageOrPackageObjectDef(statpf: PartialFunction[Token, Stat]): Stat = autoPos {
diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/ParentChecks.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/ParentChecks.scala
index 655948b415..bb629ba120 100644
--- a/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/ParentChecks.scala
+++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/trees/ParentChecks.scala
@@ -8,11 +8,17 @@ import scala.annotation.tailrec
 import scala.meta.classifiers._
 
 object ParentChecks {
+  @tailrec
   private def termArgument(parent: Tree, destination: String): Boolean = parent match {
     case _: Term.Apply | _: Term.ApplyInfix | _: Term.Interpolate => destination == "args"
     case _: Term.ApplyUnary => destination == "arg"
     case _: Term.Assign => destination == "rhs"
     case _: Init => destination == "argss"
+    case _: Term.Block =>
+      parent.parent match {
+        case Some(p) => termArgument(p, destination)
+        case None => true
+      }
     case _ => false
   }
 


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
index 6cabe6b911..3d67eb5f02 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
@@ -1288,4 +1288,33 @@ class SyntacticSuite extends scala.meta.tests.parsers.ParseSuite {
     )
   }
 
+  test("#1063 original") {
+    checkTree(
+      q"def withJsoup(html: Html)(cleaners: HtmlCleaner*): Html = withJsoup(html.body) { cleaners: _* }",
+      """def withJsoup(html: Html)(cleaners: HtmlCleaner*): Html = withJsoup(html.body) {
+        |  cleaners: _*
+        |}""".stripMargin
+    )
+  }
+
+  test("#1063 good") {
+    checkTree(
+      term("foo(bar) { baz: _* }"),
+      Term.Apply(
+        Term.Apply(Term.Name("foo"), List(Term.Name("bar"))),
+        List(Term.Block(List(Term.Repeated(Term.Name("baz")))))
+      )
+    )
+  }
+
+  test("#1063 bad") {
+    val thrown = intercept[ParseException] {
+      term("foo(bar) { val baz = qux; baz: _* }")
+    }
+    assertEquals(
+      thrown.getMessage.substring(0, 52),
+      "<input>:1: error: repeated argument not allowed here"
+    )
+  }
+
 }