Repository: scalameta/scalameta
Pull request #3191 (2023-06-21 18:10:14)

ScalametaParser: allow `erased` keyword in params
For #2774.


Patch:
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
index d41f202d3..577d0c2af 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/LazyTokenIterator.scala
@@ -57,18 +57,6 @@ private[parsers] class LazyTokenIterator private (
     }
   }
 
-  /**
-   * Deals with different rules for indentation after self type arrow.
-   */
-  def undoIndent(): Unit = {
-    curr.regions match {
-      case (_: SepRegionIndented) :: others if curr.token.is[Indentation.Indent] =>
-        // don't change prev.next in case this fork fails
-        curr = nextToken(curr.token, curr.pos, curr.nextPos, others)
-      case _ =>
-    }
-  }
-
   def observeIndented(): Boolean = {
     observeIndented0 { prev =>
       /* When adding RegionIndent (we wrap the current code block in indentation)
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 a2f4fa79e..d43f52ec5 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
@@ -3113,9 +3113,20 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       if (!mods.has[Mod.Override])
         rejectMod[Mod.Abstract](mods, Messages.InvalidAbstract)
     } else {
-      if (token.is[soft.KwInline] && tryAhead[Ident]) {
-        mods += autoEndPos(prevTokenPos)(Mod.Inline())
-      }
+      @tailrec
+      def otherMods(): Unit =
+        if (peekToken.isNot[Colon]) {
+          val mod = token.text match {
+            case soft.KwInline() => Mod.Inline()
+            case soft.KwErased() => Mod.Erased()
+            case _ => null
+          }
+          if (mod ne null) {
+            mods += atCurPosNext(mod)
+            otherMods()
+          }
+        }
+      otherMods()
     }
     val hasExplicitMods = mods.view.drop(numAnnots).exists {
       case _: Mod.Quasi => false
@@ -4298,7 +4309,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       stats: ListBuffer[T],
       statpf: PartialFunction[Token, T],
       errorMsg: String = "illegal start of definition"
-  ): Unit = {
+  ): Boolean = {
     val isIndented = acceptOpt[Indentation.Indent]
     val statpfAdd = statpf.runWith(stats += _)
 
@@ -4307,7 +4318,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       else if (token.is[StatSep]) acceptStatSep()
       else syntaxError(errorMsg + s" ${token.name}", at = token)
     }
+
     if (isIndented) accept[Indentation.Outdent]
+    isIndented
   }
 
   val topStat: PartialFunction[Token, Stat] = {
@@ -4336,12 +4349,16 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       enumCaseAllowed: Boolean = false,
       secondaryConstructorAllowed: Boolean = false
   ): (Self, List[Stat]) = {
-    val selfTree: Self = tryParse(selfOpt() match {
-      case x: Some[Self] if acceptOpt[RightArrow] => in.undoIndent(); x
+    val selfTreeOpt = tryParse(selfOpt() match {
+      case x: Some[Self] if acceptOpt[RightArrow] => x
       case _ => None
-    }).getOrElse(selfEmpty())
+    })
+    val selfTree = selfTreeOpt.getOrElse(selfEmpty())
     val stats = listBy[Stat] { buf =>
-      statSeqBuf(buf, templateStat(enumCaseAllowed, secondaryConstructorAllowed))
+      def getStats() =
+        statSeqBuf(buf, templateStat(enumCaseAllowed, secondaryConstructorAllowed))
+      val wasIndented = getStats() // some stats could be indented relative to self-type
+      if (wasIndented && selfTreeOpt.isDefined) getStats() // and the rest might not be
     }
     (selfTree, stats)
   }
diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/TokenIterator.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/TokenIterator.scala
index 9d896825f..5b233cc46 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/TokenIterator.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/TokenIterator.scala
@@ -16,7 +16,6 @@ trait TokenIterator {
   def currentIndentation: Int
 
   def observeIndented(): Boolean
-  def undoIndent(): Unit
 
   def peekToken: Token
   def peekIndex: Int


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/ErasedDefsSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/ErasedDefsSuite.scala
index 2d237caa8..ab451db7a 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/ErasedDefsSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/ErasedDefsSuite.scala
@@ -11,11 +11,15 @@ class ErasedDefsSuite extends BaseDottySuite {
 
   test("def with erased params") {
     val code = "def methodWithErasedEv(erased ev: Ev, x: Int): Int = x + 2"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: : expected but identifier found
-         |def methodWithErasedEv(erased ev: Ev, x: Int): Int = x + 2
-         |                              ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Def(
+        Nil,
+        tname("methodWithErasedEv"),
+        Nil,
+        List(List(tparam(List(Mod.Erased()), "ev", "Ev"), tparam("x", "Int"))),
+        Some(pname("Int")),
+        Term.ApplyInfix(tname("x"), tname("+"), Nil, List(Lit.Int(2)))
+      )
     )
   }
 
@@ -41,21 +45,35 @@ class ErasedDefsSuite extends BaseDottySuite {
 
   test("def with only one erased param") {
     val code = "def methodWithErasedEv(x: Int, erased ev: Ev): Int = ???"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: : expected but identifier found
-         |def methodWithErasedEv(x: Int, erased ev: Ev): Int = ???
-         |                                      ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Def(
+        Nil,
+        tname("methodWithErasedEv"),
+        Nil,
+        List(List(tparam("x", "Int"), tparam(List(Mod.Erased()), "ev", "Ev"))),
+        Some(pname("Int")),
+        tname("???")
+      )
     )
   }
 
   test("def with both using and erased") {
     val code = "def turnedOn(using erased ev: IsOff[S]): Machine[On] = new Machine[On]"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: identifier expected but : found
-         |def turnedOn(using erased ev: IsOff[S]): Machine[On] = new Machine[On]
-         |                            ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Def(
+        Nil,
+        tname("turnedOn"),
+        Nil,
+        List(
+          tparam(
+            List(Mod.Erased(), Mod.Using()),
+            "ev",
+            Type.Apply(pname("IsOff"), List(pname("S")))
+          ) :: Nil
+        ),
+        Some(Type.Apply(pname("Machine"), List(pname("On")))),
+        Term.New(Init(Type.Apply(pname("Machine"), List(pname("On"))), anon, Nil))
+      )
     )
   }