Repository: scalameta/scalameta
Pull request #3640 (2024-03-12 07:59:00)

Keywords: match for allowPostfixStarVarargSplices
Helps with #3631.


Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/Keywords.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/Keywords.scala
index 0502703cc..186f37217 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/Keywords.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/Keywords.scala
@@ -33,4 +33,9 @@ object Keywords {
   abstract class IsWithName(isEnabled: Boolean, val name: String)
       extends IsWithPred(isEnabled, name == _)
 
+  abstract class NotWithName(val name: String) extends IsWithPred(true, name != _)
+
+  object PatAlt extends IsWithName(true, "|")
+  object NotPatAlt extends NotWithName("|")
+
 }
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 8ebc59573..a5759e60f 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
@@ -527,14 +527,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     case Ident(x) => pred(x)
     case _ => false
   }
-  def isIdentExcept(except: String) = isIdentAnd(token, _ != except)
   def isIdentOf(tok: Token, name: String) = isIdentAnd(tok, _ == name)
   @inline def isStar: Boolean = isStar(token)
   def isStar(tok: Token): Boolean = isIdentOf(tok, "*")
-  def isVarargStarParam(allowRepeated: Boolean) =
-    isStar && isVarargParamOnStar(allowRepeated)
-  def isVarargParamOnStar(allowRepeated: Boolean) =
-    allowRepeated && dialect.allowPostfixStarVarargSplices && peekToken.is[RightParen]
 
   private trait MacroIdent {
     protected def ident(token: Token): Option[String]
@@ -1201,8 +1196,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           val t = if (token.is[LeftParen]) tupleInfixType() else compoundType()
           token match {
             case KwForsome() => next(); copyPos(t)(Type.Existential(t, existentialStats()))
-            case Ident("|") => t
-            case _: Unquote | _: Ident => infixTypeRest(t)
+            case _: Unquote | Keywords.NotPatAlt() => infixTypeRest(t)
             case _ => t
           }
         } else {
@@ -1731,7 +1725,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
               iter(addPos(Term.Ascribe(t, typeOrInfixType(location))))
             }
         }
-      case Ident("*") if isVarargParamOnStar(allowRepeated) =>
+      case soft.StarSplice() if allowRepeated && peekToken.is[RightParen] =>
         repeatedTerm(t, next)
       case _: KwMatch =>
         next()
@@ -2173,7 +2167,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         case t: Unquote =>
           val op = unquote[Term.Name](t)
           Some(getPostfixOrNextRhs(op))
-        case t: Ident if !isVarargStarParam(allowRepeated) =>
+        case t: Ident if !(allowRepeated && soft.StarSplice(t) && peekToken.is[RightParen]) =>
           val op = atCurPosNext(Term.Name(t.value))
           Some(getPostfixOrNextRhs(op))
         case _: KwMatch if dialect.allowMatchAsOperator =>
@@ -2748,7 +2742,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     private def patternAlternatives(pats: List[Pat]): Pat = {
       val pat = pattern1()
       checkNoTripleDot(pat)
-      if (isIdentOf(token, "|")) {
+      if (Keywords.PatAlt(token)) {
         next()
         patternAlternatives(pat :: pats)
       } else if (pats.isEmpty) {
@@ -2847,9 +2841,10 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       val base = ctx.stack
       @tailrec
       def loop(rhs: ctx.Typ): ctx.Typ = {
-        val op =
-          if (isIdentExcept("|") || token.is[Unquote]) Some(termName())
-          else None
+        val op = token match {
+          case _: Unquote | Keywords.NotPatAlt() => Some(termName())
+          case _ => None
+        }
         val lhs1 = ctx.reduceStack(base, rhs, rhs, op)
         op match {
           case Some(op) =>
@@ -2918,7 +2913,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
             sid match {
               case name: Term.Name.Quasi => name.become[Pat]
               case name: Term.Name =>
-                if (dialect.allowPostfixStarVarargSplices && isStar && tryAheadNot[Ident])
+                if (soft.StarSplice(token) && tryAheadNot[Ident])
                   Pat.Repeated(name)
                 else if ((!sidToken.isBackquoted || isForComprehension) && {
                     val first = name.value.head
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/SoftKeywords.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/SoftKeywords.scala
index b45d385b6..29dfe011b 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/SoftKeywords.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/SoftKeywords.scala
@@ -18,6 +18,7 @@ class SoftKeywords(dialect: Dialect) {
   object KwExtension extends IsWithName(dialect.allowExtensionMethods, "extension")
   object KwErased extends IsWithName(dialect.allowErasedDefs, "erased")
 
+  object StarSplice extends IsWithName(dialect.allowPostfixStarVarargSplices, "*")
   object StarAsTypePlaceholder
       extends AsWithFunc(
         dialect.allowStarAsTypePlaceholder,
diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
index f2646173c..c10e4618b 100644
--- a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
+++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
@@ -94,6 +94,8 @@ object TreeSyntax {
     private val escapableSoftKeywords: Map[String, Seq[Class[_]]] = {
       val seq = Seq.newBuilder[(String, Seq[ClassTag[_]])]
 
+      seq += "|" -> Seq(classTag[Pat.ExtractInfix])
+
       if (dialect.allowExtensionMethods)
         seq += "extension" -> Seq(classTag[Term.Apply], classTag[Term.ApplyUsing])
 


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
index ffa44dac0..799852b5e 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/PatSuite.scala
@@ -193,7 +193,7 @@ class PatSuite extends ParseSuite {
     assertPat("1 | 2 | 3")(
       Alternative(int(1), Alternative(int(2), int(3)))
     )
-    runTestAssert[Pat]("1 `|` 2", "1 | 2")(Alternative(int(1), int(2)))
+    runTestAssert[Pat]("1 `|` 2")(ExtractInfix(lit(1), tname("|"), List(lit(2))))
   }
 
   test("()") {
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MinorDottySuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MinorDottySuite.scala
index 33626f314..c4e4d31aa 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MinorDottySuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MinorDottySuite.scala
@@ -907,14 +907,13 @@ class MinorDottySuite extends BaseDottySuite {
   }
 
   test("vararg-wildcard-like-postfix-star") {
-    runTestAssert[Stat]("val lst = List(0, arr`*`)", "val lst = List(0, arr*)")(
-      Defn.Val(
-        Nil,
-        List(Pat.Var(tname("lst"))),
-        None,
-        Term.Apply(tname("List"), List(int(0), Term.Repeated(tname("arr"))))
-      )
+    val tree = Defn.Val(
+      Nil,
+      List(Pat.Var(tname("lst"))),
+      None,
+      Term.Apply(tname("List"), List(lit(0), Term.Select(tname("arr"), tname("*"))))
     )
+    runTestAssert[Stat]("val lst = List(0, arr`*`)", "val lst = List(0, arr.*)")(tree)
   }
 
   test("non-vararg-infix-star") {
@@ -962,27 +961,11 @@ class MinorDottySuite extends BaseDottySuite {
   }
 
   test("vararg-wildcard-like-postfix-start-pat") {
-    runTestAssert[Stat](
-      "a match {case List(xs`*`) => }",
-      assertLayout = Some(
-        """|a match {
-           |  case List(xs*) =>
-           |}
-           |""".stripMargin
-      )
-    )(
-      Term.Match(
-        tname("a"),
-        List(
-          Case(
-            Pat.Extract(tname("List"), List(Pat.Repeated(tname("xs")))),
-            None,
-            Term.Block(Nil)
-          )
-        ),
-        Nil
-      )
-    )
+    val error =
+      """|<input>:1: error: bad simple pattern: use _* to match a sequence
+         |a match {case List(xs`*`) => }
+         |                        ^""".stripMargin
+    runTestError[Stat]("a match {case List(xs`*`) => }", error)
   }
 
   test("empty-case-class") {
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
index 1ee762e3f..9dc22be47 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/PatSuite.scala
@@ -201,7 +201,7 @@ class PatSuite extends ParseSuite {
   test("1 | 2 | 3") {
     assertPat("1 | 2")(Alternative(int(1), int(2)))
     assertPat("1 | 2 | 3")(Alternative(int(1), Alternative(int(2), int(3))))
-    runTestAssert[Pat]("1 `|` 2", "1 | 2")(Alternative(int(1), int(2)))
+    runTestAssert[Pat]("1 `|` 2")(ExtractInfix(lit(1), tname("|"), List(lit(2))))
   }
 
   test("()") {