Repository: scalameta/scalameta
Pull request #3193 (2023-06-21 18:15:02)

ScalametaParser: allow `erased` as defn modifier
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..22671a248 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
@@ -2844,6 +2844,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           case soft.KwOpen() if !isLocal => atCurPosNext(Mod.Open())
           case soft.KwOpaque() => atCurPosNext(Mod.Opaque())
           case soft.KwTransparent() => atCurPosNext(Mod.Transparent())
+          case soft.KwErased() => atCurPosNext(Mod.Erased())
           case n =>
             val local = if (isLocal) "local " else ""
             syntaxError(s"${local}modifier expected but $n found", at = token)
@@ -2887,6 +2888,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           case soft.KwTransparent() => atCurPosNext(Mod.Transparent())
           case soft.KwInline() => atCurPosNext(Mod.Inline())
           case soft.KwInfix() => atCurPosNext(Mod.Infix())
+          case soft.KwErased() => atCurPosNext(Mod.Erased())
           case _ => fail(t)
         }
       case t => fail(t)
@@ -3113,9 +3115,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 +4311,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 +4320,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 +4351,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/ScannerTokens.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
index 85f4d830e..80fa7b97d 100644
--- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
+++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScannerTokens.scala
@@ -140,7 +140,7 @@ final class ScannerTokens(val tokens: Tokens)(implicit dialect: Dialect) {
       case soft.KwTransparent() => nextIsDclIntroOrModifierOr(_.is[KwTrait])
       case soft.KwOpaque() => nextIsDclIntroOrModifierOr(_ => false)
       case soft.KwInline() => nextIsDclIntroOrModifierOr(matchesAfterInlineMatchMod)
-      case soft.KwOpen() | soft.KwInfix() => isDefIntro(getNextIndex(index))
+      case soft.KwOpen() | soft.KwInfix() | soft.KwErased() => isDefIntro(getNextIndex(index))
       case _ => false
     }
   }
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..8fe515a0a 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)))
+      )
     )
   }
 
@@ -31,51 +35,67 @@ class ErasedDefsSuite extends BaseDottySuite {
 
   test("erased val") {
     val code = "erased val erasedEvidence: Ev = null"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: ; expected but val found
-         |erased val erasedEvidence: Ev = null
-         |       ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Val(
+        List(Mod.Erased()),
+        List(Pat.Var(tname("erasedEvidence"))),
+        Some(pname("Ev")),
+        Lit.Null()
+      )
     )
   }
 
   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))
+      )
     )
   }
 
   test("erased class") {
     val code = "erased class CanRead"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: ; expected but class found
-         |erased class CanRead
-         |       ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Class(List(Mod.Erased()), pname("CanRead"), Nil, ctor, tpl(Nil))
     )
   }
 
   test("erased given") {
     val code = "erased given IsEmpty[Empty] = new IsEmpty[Empty]"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: ; expected but given found
-         |erased given IsEmpty[Empty] = new IsEmpty[Empty]
-         |       ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.GivenAlias(
+        List(Mod.Erased()),
+        anon,
+        None,
+        Type.Apply(pname("IsEmpty"), List(pname("Empty"))),
+        Term.New(Init(Type.Apply(pname("IsEmpty"), List(pname("Empty"))), anon, Nil))
+      )
     )
   }