Repository: scalameta/scalameta
Pull request #3184 (2023-06-16 20:44:36)

ScalametaParser: use TemplateOwner in parsing
Add attribute methods to TemplateOwner.


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 55c1cbb5a..a2f4fa79e 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
@@ -2141,7 +2141,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           canApply = false
           Success(autoPos {
             next()
-            template(enumCaseAllowed = false, secondaryConstructorAllowed = false) match {
+            template(OwnedByTrait) match {
               case Template(Nil, init :: Nil, Self(_: Name.Anonymous, None), Nil)
                   if !prevToken.is[RightBrace] =>
                 Term.New(init)
@@ -3880,7 +3880,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   /* -------- CONSTRUCTORS ------------------------------------------- */
 
   def primaryCtor(owner: TemplateOwner): Ctor.Primary = autoPos {
-    if (owner.isClass || (owner.isTrait && dialect.allowTraitParameters) || owner.isEnum) {
+    if (owner.isPrimaryCtorAllowed) {
       val mods = listBy[Mod] { buf =>
         annotsBuf(buf, skipNewLines = false, allowArgss = false, insidePrimaryCtorAnnot = true)
         ctorModifiers(buf)
@@ -3888,10 +3888,8 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       val name = anonNameEmpty()
       val paramss = termParamClauses(ownerIsType = true, owner == OwnedByCaseClass)
       Ctor.Primary(mods, name, paramss)
-    } else if (owner.isTrait) {
-      Ctor.Primary(Nil, anonNameEmpty(), Seq.empty)
     } else {
-      unreachable(debug(owner))
+      Ctor.Primary(Nil, anonNameEmpty(), Seq.empty)
     }
   }
 
@@ -4054,16 +4052,36 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   /* -------- TEMPLATES ------------------------------------------- */
 
   sealed trait TemplateOwner {
-    def isTerm = this eq OwnedByObject
-    def isClass = (this eq OwnedByCaseClass) || (this eq OwnedByClass)
-    def isTrait = this eq OwnedByTrait
-    def isEnum = this eq OwnedByEnum
+    def isEnumCaseAllowed: Boolean
+    def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean
+    def isSecondaryCtorAllowed: Boolean
+  }
+  object OwnedByTrait extends TemplateOwner {
+    final override def isEnumCaseAllowed: Boolean = false
+    final override def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean =
+      dialect.allowTraitParameters
+    final override def isSecondaryCtorAllowed: Boolean = false
+  }
+  object OwnedByCaseClass extends TemplateOwner {
+    final override def isEnumCaseAllowed: Boolean = false
+    final override def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
+    final override def isSecondaryCtorAllowed: Boolean = true
+  }
+  object OwnedByClass extends TemplateOwner {
+    final override def isEnumCaseAllowed: Boolean = false
+    final override def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
+    final override def isSecondaryCtorAllowed: Boolean = true
+  }
+  object OwnedByEnum extends TemplateOwner {
+    final override def isEnumCaseAllowed: Boolean = true
+    final override def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
+    final override def isSecondaryCtorAllowed: Boolean = true
+  }
+  object OwnedByObject extends TemplateOwner {
+    final override def isEnumCaseAllowed: Boolean = false
+    final override def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = false
+    final override def isSecondaryCtorAllowed: Boolean = false
   }
-  object OwnedByTrait extends TemplateOwner
-  object OwnedByCaseClass extends TemplateOwner
-  object OwnedByClass extends TemplateOwner
-  object OwnedByEnum extends TemplateOwner
-  object OwnedByObject extends TemplateOwner
 
   def init() = {
     token match {
@@ -4086,6 +4104,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   def derivesClasses(): List[Type] = {
     if (isAfterOptNewLine[soft.KwDerives]) {
       next()
+      newLineOpt()
       val deriving = ListBuffer[Type]()
       do token match {
         case t: Ellipsis => deriving += ellipsis[Type](t, 1)
@@ -4098,48 +4117,39 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   }
 
   private def templateAfterExtends(
-      edefs: List[Stat],
-      parents: List[Init],
-      enumCaseAllowed: Boolean,
-      secondaryConstructorAllowed: Boolean,
-      parenMeansSyntaxError: Boolean = false
+      owner: TemplateOwner,
+      parents: List[Init] = Nil,
+      edefs: List[Stat] = Nil
   ): Template = {
     val derived = derivesClasses()
-    val (self, body) = templateBodyOpt(
-      parenMeansSyntaxError = parenMeansSyntaxError,
-      enumCaseAllowed,
-      secondaryConstructorAllowed
-    )
+    val (self, body) = templateBodyOpt(owner)
     Template(edefs, parents, self, body, derived)
   }
 
   def template(
-      afterExtend: Boolean = false,
-      enumCaseAllowed: Boolean,
-      secondaryConstructorAllowed: Boolean
+      owner: TemplateOwner,
+      afterExtend: Boolean = false
   ): Template = autoPos {
     if (isAfterOptNewLine[LeftBrace]) {
       // @S: pre template body cannot stub like post body can!
-      val (self, body) = templateBody(enumCaseAllowed)
+      val (self, body) = templateBody(owner)
       if (token.is[KwWith] && self.isEmpty) {
         val edefs = body.map(ensureEarlyDef)
         next()
         val parents = templateParents(afterExtend)
-        templateAfterExtends(edefs, parents, enumCaseAllowed, secondaryConstructorAllowed)
+        templateAfterExtends(owner, parents, edefs)
       } else {
         Template(Nil, Nil, self, body)
       }
     } else {
       val parents = if (token.is[Colon]) Nil else templateParents(afterExtend)
-      templateAfterExtends(Nil, parents, enumCaseAllowed, secondaryConstructorAllowed)
+      templateAfterExtends(owner, parents)
     }
   }
 
   def quasiquoteTemplate(): Template = entrypointTemplate()
 
-  def entrypointTemplate(): Template = autoPos(
-    template(enumCaseAllowed = false, secondaryConstructorAllowed = true)
-  )
+  def entrypointTemplate(): Template = autoPos(template(OwnedByClass))
 
   def ensureEarlyDef(tree: Stat): Stat = tree match {
     case q: Quasi => q
@@ -4157,55 +4167,39 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         case _ => true
       })) {
       unquote[Template](token.asInstanceOf[Unquote])
-    } else if (acceptOpt[KwExtends] || (owner.isTrait && acceptOpt[Subtype])) {
-      template(
-        afterExtend = true,
-        enumCaseAllowed = owner.isEnum,
-        secondaryConstructorAllowed = owner.isClass || owner.isEnum
-      )
+    } else if (acceptOpt[KwExtends] || (owner eq OwnedByTrait) && acceptOpt[Subtype]) {
+      template(owner, afterExtend = true)
     } else {
-      templateAfterExtends(
-        Nil,
-        Nil,
-        enumCaseAllowed = owner.isEnum,
-        secondaryConstructorAllowed = owner.isClass || owner.isEnum,
-        parenMeansSyntaxError = !owner.isClass
-      )
+      templateAfterExtends(owner)
     }
   }
 
-  def templateBody(
-      enumCaseAllowed: Boolean = false,
-      secondaryConstructorAllowed: Boolean = false
-  ): (Self, List[Stat]) =
-    inBraces(templateStatSeq(enumCaseAllowed, secondaryConstructorAllowed))
+  @inline private def templateBody(owner: TemplateOwner): (Self, List[Stat]) =
+    inBraces(templateStatSeq(owner))
 
-  def templateBodyOpt(
-      parenMeansSyntaxError: Boolean,
-      enumCaseAllowed: Boolean = false,
-      secondaryConstructorAllowed: Boolean = false
-  ): (Self, List[Stat]) = {
+  def templateBodyOpt(owner: TemplateOwner): (Self, List[Stat]) = {
     if (isAfterOptNewLine[LeftBrace]) {
-      templateBody(enumCaseAllowed, secondaryConstructorAllowed)
+      templateBody(owner)
     } else if (isColonEol()) {
       accept[Colon]
 
-      if (!enumCaseAllowed)
+      if (!owner.isEnumCaseAllowed)
         in.observeIndented()
 
       if (acceptOpt[Indentation.Indent])
-        indentedAfterOpen(templateStatSeq(enumCaseAllowed, secondaryConstructorAllowed))
-      else if (!enumCaseAllowed && isEndMarkerIntro(tokenPos))
+        indentedAfterOpen(templateStatSeq(owner))
+      else if (!owner.isEnumCaseAllowed && isEndMarkerIntro(tokenPos))
         (selfEmpty(), Nil)
       else
         syntaxError("expected template body", token)
-    } else {
-      if (token.is[LeftParen]) {
-        if (parenMeansSyntaxError) {
-          val what = if (dialect.allowTraitParameters) "objects" else "traits or objects"
-          syntaxError(s"$what may not have parameters", at = token)
-        } else syntaxError("unexpected opening parenthesis", at = token)
+    } else if (token.is[LeftParen]) {
+      if (owner.isPrimaryCtorAllowed)
+        syntaxError("unexpected opening parenthesis", at = token)
+      else {
+        val what = owner.getClass.getSimpleName.stripPrefix("OwnedBy")
+        syntaxError(s"$what may not have parameters", at = token)
       }
+    } else {
       (selfEmpty(), Nil)
     }
   }
@@ -4335,7 +4329,10 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       nonLocalDefOrDcl()
   }
 
-  def templateStatSeq(
+  @inline private def templateStatSeq(owner: TemplateOwner): (Self, List[Stat]) =
+    templateStatSeq(owner.isEnumCaseAllowed, owner.isSecondaryCtorAllowed)
+
+  private def templateStatSeq(
       enumCaseAllowed: Boolean = false,
       secondaryConstructorAllowed: Boolean = false
   ): (Self, List[Stat]) = {


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/DerivesSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/DerivesSuite.scala
index f2121fca4..9366059a1 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/DerivesSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/DerivesSuite.scala
@@ -163,7 +163,7 @@ class DerivesSuite extends BaseDottySuite {
          |""".stripMargin,
       Some(layout)
     )(tree)
-    runTestError[Stat](
+    runTestAssert[Stat](
       """|class A[T](a: Int, b: Int)
          |    derives
          |      Alpha[T],
@@ -172,10 +172,8 @@ class DerivesSuite extends BaseDottySuite {
          |  def a = ???
          |}
          |""".stripMargin,
-      """|<input>:2: error: identifier expected but \n found
-         |    derives
-         |           ^""".stripMargin
-    )
+      Some(layout)
+    )(tree)
   }
 
   test("newline-derives-coloneol") {
@@ -204,28 +202,24 @@ class DerivesSuite extends BaseDottySuite {
          |""".stripMargin,
       Some(layout)
     )(tree)
-    runTestError[Stat](
+    runTestAssert[Stat](
       """|class A[T](a: Int, b: Int)
          |    derives
          |      Alpha[T],
          |      Epsilon[T]:
          |  def a = ???
          |""".stripMargin,
-      """|<input>:2: error: identifier expected but \n found
-         |    derives
-         |           ^""".stripMargin
-    )
-    runTestError[Stat](
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
       """|class A[T](a: Int, b: Int)
          |    derives
          |   Alpha[T],
          |   Epsilon[T]:
          |  def a = ???
          |""".stripMargin,
-      """|<input>:2: error: identifier expected but \n found
-         |    derives
-         |           ^""".stripMargin
-    )
+      Some(layout)
+    )(tree)
   }
 
   test("newline-extends-derives-coloneol") {
@@ -251,7 +245,7 @@ class DerivesSuite extends BaseDottySuite {
          |""".stripMargin,
       Some(layout)
     )(tree)
-    runTestError[Stat](
+    runTestAssert[Stat](
       """|class A[T](a: Int, b: Int)
          |    extends
          |      Alpha[T]
@@ -259,11 +253,9 @@ class DerivesSuite extends BaseDottySuite {
          |      Epsilon[T]:
          |  def a = ???
          |""".stripMargin,
-      """|<input>:4: error: identifier expected but \n found
-         |    derives
-         |           ^""".stripMargin
-    )
-    runTestError[Stat](
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
       """|class A[T](a: Int, b: Int)
          |    extends
          |   Alpha[T]
@@ -271,10 +263,141 @@ class DerivesSuite extends BaseDottySuite {
          |   Epsilon[T]:
          |  def a = ???
          |""".stripMargin,
-      """|<input>:4: error: identifier expected but \n found
+      Some(layout)
+    )(tree)
+  }
+
+  test("newline-extends-derives-coloneol-not-like-refine-type") {
+    val layout = "class A[T](a: Int, b: Int) extends Alpha[T] derives Epsilon[T] { require(true) }"
+    val tree = Defn.Class(
+      Nil,
+      pname("A"),
+      List(pparam("T")),
+      ctorp(List(tparam("a", "Int"), tparam("b", "Int"))),
+      Template(
+        Nil,
+        List(Init(Type.Apply(pname("Alpha"), List(pname("T"))), anon, Nil)),
+        slf,
+        List(Term.Apply(tname("require"), List(bool(true)))),
+        List(Type.Apply(pname("Epsilon"), List(pname("T"))))
+      )
+    )
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends Alpha[T]
+         |    derives Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |      Alpha[T]
          |    derives
-         |           ^""".stripMargin
+         |      Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |   Alpha[T]
+         |    derives
+         |   Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+  }
+
+  test("newline-extends-with-not-derives-coloneol-not-like-refine-type") {
+    val layout = "class A[T](a: Int, b: Int) extends Alpha[T] with Epsilon[T] { require(true) }"
+    val tree = Defn.Class(
+      Nil,
+      pname("A"),
+      List(pparam("T")),
+      ctorp(List(tparam("a", "Int"), tparam("b", "Int"))),
+      Template(
+        Nil,
+        List(
+          Init(Type.Apply(pname("Alpha"), List(pname("T"))), anon, Nil),
+          Init(Type.Apply(pname("Epsilon"), List(pname("T"))), anon, Nil)
+        ),
+        slf,
+        List(Term.Apply(tname("require"), List(bool(true)))),
+        Nil
+      )
     )
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends Alpha[T]
+         |    with Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |      Alpha[T]
+         |    with
+         |      Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |   Alpha[T]
+         |    with
+         |   Epsilon[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+  }
+
+  test("newline-extends-not-derives-coloneol-not-like-refine-type") {
+    val layout = "class A[T](a: Int, b: Int) extends Alpha[T] { require(true) }"
+    val tree = Defn.Class(
+      Nil,
+      pname("A"),
+      List(pparam("T")),
+      ctorp(List(tparam("a", "Int"), tparam("b", "Int"))),
+      Template(
+        Nil,
+        List(Init(Type.Apply(pname("Alpha"), List(pname("T"))), anon, Nil)),
+        slf,
+        List(Term.Apply(tname("require"), List(bool(true)))),
+        Nil
+      )
+    )
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends Alpha[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |      Alpha[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+    runTestAssert[Stat](
+      """|class A[T](a: Int, b: Int)
+         |    extends
+         |   Alpha[T]:
+         |  require(true)
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
   }
 
   test("not-derives") {
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
index b76fed3b3..883449365 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/EndMarkerSuite.scala
@@ -337,4 +337,41 @@ class EndMarkerSuite extends BaseDottySuite {
       )
     )
   }
+
+  test("trait-empty-body-no-end-marker") {
+    runTestError[Source](
+      """|trait Foo:
+         |  trait Baz:
+         |  trait Bar:
+         |  end Bar
+         |end Foo
+         |""".stripMargin,
+      """|<input>:3: error: expected template body
+         |  trait Bar:
+         |  ^""".stripMargin
+    )
+  }
+
+  test("trait-empty-body-outer-end-marker") {
+    runTestError[Source](
+      """|trait Foo:
+         |  trait Bar:
+         |end Foo
+         |""".stripMargin,
+      """|<input>:2: error: expected template body
+         |  trait Bar:
+         |            ^""".stripMargin
+    )
+  }
+
+  test("trait-empty-body-no-end-marker") {
+    runTestError[Source](
+      """|trait Foo:
+         |""".stripMargin,
+      """|<input>:2: error: expected template body
+         |
+         |^""".stripMargin
+    )
+  }
+
 }