Repository: scalameta/scalameta
Pull request #3034 (2023-01-08 08:29:06)

ScalametaParser: parse annot args before params
Allow annotations before class constructor, meaning annotation could have its arg clauses but has to stop before ctor param clauses. For #2405.

kitbellew: @tgodzik how does the compiler handle this case? it can probably tell literal goes to annotation, implicit goes to constructor, default values as well...

except, specifically, the ambiguity between ascribed annotation argument and constructor parameter `(foo: Foo)`? and what's the proper way to use this as a parameter, and not annotation argument?
tgodzik: > except, specifically, the ambiguity between ascribed annotation argument and constructor parameter (foo: Foo)? and what's the proper way to use this as a parameter, and not annotation argument?

It doesn't accept type annotated parameters there, this will always be treated as a param. It seems it parses it annotation and those might not accept typed params?

It's somewhere around here:
https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/parsing/Parsers.scala#L3702
tgodzik: Actually, it seems everything is explained here https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/parsing/Parsers.scala#L2586

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 873154468..8dcba5c9c 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
@@ -2947,6 +2947,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   def annotsBuf[T >: Mod.Annot](
       annots: ListBuffer[T],
       skipNewLines: Boolean,
+      insidePrimaryCtorAnnot: Boolean = false,
       allowArgss: Boolean = true
   ): Unit = {
     while (token match {
@@ -2954,7 +2955,10 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           next()
           annots += (token match {
             case t: Unquote => unquote[Mod.Annot](t)
-            case _ => autoEndPos(prevTokenPos)(Mod.Annot(initRest(exprSimpleType(), allowArgss)))
+            case _ =>
+              autoEndPos(prevTokenPos) {
+                Mod.Annot(initRest(exprSimpleType(), allowArgss, insidePrimaryCtorAnnot))
+              }
           })
           true
         case t: Ellipsis if ahead(token.is[At]) =>
@@ -3828,7 +3832,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   def primaryCtor(owner: TemplateOwner): Ctor.Primary = autoPos {
     if (owner.isClass || (owner.isTrait && dialect.allowTraitParameters) || owner.isEnum) {
       val mods = listBy[Mod] { buf =>
-        annotsBuf(buf, skipNewLines = false, allowArgss = false)
+        annotsBuf(buf, skipNewLines = false, allowArgss = false, insidePrimaryCtorAnnot = true)
         ctorModifiers().foreach(buf += _)
       }
       val name = autoPos(Name.Anonymous())
@@ -3916,6 +3920,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
   def initRest(
       typeParser: => Type,
       allowArgss: Boolean,
+      insidePrimaryCtorAnnot: Boolean = false,
       allowBraces: Boolean = false,
       allowTypeSingleton: Boolean = true
   ): Init = autoPosOpt {
@@ -3936,12 +3941,26 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         val argss = listBy[Term.ArgClause] { argss =>
           @inline def body() = {
             argss += getArgClause()
+          }
+          def isLegalAnnotArg(): Boolean = ahead(token match {
+            // explained here:
+            // https://github.com/lampepfl/dotty/blob/675ae0c6440d5527150d650ad45d20fda5e03e69/compiler/src/dotty/tools/dotc/parsing/Parsers.scala#L2581
+            case _: RightParen => argss.isEmpty
+            case _: Ident => next(); !token.is[Colon]
+            case _: At => false
+            case _ => !isModifier(tokenPos)
+          })
+          def maybeBody() = {
             newlineOpt()
+            val ok = token match {
+              case _: LeftParen => !insidePrimaryCtorAnnot || isLegalAnnotArg()
+              case _: LeftBrace => allowBraces
+              case _ => false
+            }
+            if (ok) body()
+            ok
           }
-          newlineOpt()
-          if (allowArgss)
-            while (isPendingArglist) body()
-          else if (isPendingArglist) body()
+          if (maybeBody() && allowArgss) while (maybeBody()) {}
         }
         Init(tpe, name, argss)
     }


Test patch:
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 36cd9c0d5..2dca35a39 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
@@ -1492,4 +1492,80 @@ class MinorDottySuite extends BaseDottySuite {
     )
   }
 
+  test("class Baz1 @deprecated(implicit c: C)") {
+    runTestAssert[Stat](
+      "class Baz1 @deprecated(implicit c: C)",
+      Some("class Baz1 @deprecated (implicit c: C)")
+    )(
+      Defn.Class(
+        Nil,
+        pname("Baz1"),
+        Nil,
+        Ctor.Primary(
+          List(Mod.Annot(init("deprecated"))),
+          anon,
+          List(List(Term.Param(List(Mod.Implicit()), tname("c"), Some(pname("C")), None)))
+        ),
+        tpl(Nil)
+      )
+    )
+  }
+
+  test("class Baz1 @deprecated(c: C)") {
+    runTestAssert[Stat](
+      "class Baz1 @deprecated(c: C)",
+      Some("class Baz1 @deprecated (c: C)")
+    )(
+      Defn.Class(
+        Nil,
+        pname("Baz1"),
+        Nil,
+        Ctor.Primary(
+          List(Mod.Annot(init("deprecated"))),
+          anon,
+          List(List(tparam("c", "C")))
+        ),
+        tpl(Nil)
+      )
+    )
+  }
+
+  test("class Baz1 @deprecated(c: C = some)") {
+    runTestAssert[Stat](
+      "class Baz1 @deprecated(c: C = some)",
+      Some("class Baz1 @deprecated (c: C = some)")
+    )(
+      Defn.Class(
+        Nil,
+        pname("Baz1"),
+        Nil,
+        Ctor.Primary(
+          List(Mod.Annot(init("deprecated"))),
+          anon,
+          List(List(Term.Param(Nil, tname("c"), Some(pname("C")), Some(tname("some")))))
+        ),
+        tpl(Nil)
+      )
+    )
+  }
+
+  test("class Baz1 @deprecated(foo)(c: C)") {
+    runTestAssert[Stat](
+      "class Baz1 @deprecated(foo)(c: C)",
+      Some("class Baz1 @deprecated(foo) (c: C)")
+    )(
+      Defn.Class(
+        Nil,
+        pname("Baz1"),
+        Nil,
+        Ctor.Primary(
+          List(Mod.Annot(Init(pname("deprecated"), anon, List(List(tname("foo")))))),
+          anon,
+          List(List(Term.Param(Nil, Term.Name("c"), Some(Type.Name("C")), None)))
+        ),
+        tpl(Nil)
+      )
+    )
+  }
+
 }