Repository: scalameta/scalameta (scala)

Issue:
Support erased definitions
This template is mostly a guideline, not a strict requirement, except
the sections marked `(required)`.

Please make sure that the issue is observable using the latest version of `scalafmt` and
within the `scalafmt` **CLI**: `https://scalameta.org/scalafmt/docs/installation.html#cli`

Using the CLI is _mandatory_, in one of these configurations:
- latest version of CLI and latest version in `.scalafmt.conf`
- previous version of CLI and latest version in `.scalafmt.conf`
- latest version of CLI and previous version in `.scalafmt.conf`

## Configuration (required) ##

Please paste the contents of your `.scalafmt.conf` file here:
```
version = 3.5.8
binPack.parentConstructors = true
maxColumn = 120
includeCurlyBraceInSelectChains = true
align.preset = most
trailingCommas = preserve
runner.dialect = scala3
project.excludeFilters = ["kaivessel/package.scala"]

rewrite.scala3.insertEndMarkerMinLines = 5
...
```

NB: before submitting, please confirm that the problem is observed in the
*latest published* version of the formatter! We don't publish hotfixes for older
versions, and the problem you have observed in an older version may have already
been fixed.

## Command-line parameters (required) ##

When I run scalafmt via CLI like this: `<command-line parameter>`

If you were not using CLI initially:
- please check using `scalafmt` CLI before submitting here (required)
- if the problem does not present via CLI, DO NOT SUBMIT
  here: this is NOT a formatter issue
  - it could be an issue with your integration (sbt-scalafmt, metals, intellij etc.)
  - instead please submit the issue in the appropriate tool's repository
  - for instance, if the tool is `sbt` with the `sbt-scalafmt` plugin: please
    submit in `sbt-scalafmt` and include scalafmt parameters from `build.sbt`
    and the `sbt` command used
  - conversely, do not submit formatting issues into other tools; you must first
    verify using `scalafmt` CLI

## Steps

Given code like this:
```scala
package kuraga.datum

import scala.language.experimental.erasedDefinitions

import Repr.Holder

object Repr:
  trait Holder:
    type A

trait Repr[-P[-_, +_], +X]:
  def apply(erased holder: Holder)(p: P[X, holder.A]): holder.A
```

## Problem

Scalafmt formats raises an error like this:
```scala
Repr.scala:12: error: [dialect scala3] : expected but identifier found
  def apply(erased holder: Holder)(p: P[X, holder.A]): holder.A
```


## Workaround

I've found that by...

## Notes

See also...

Pull request:
Fixes #2774.

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 b14b8bdc86..e02fe8ccdb 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
@@ -654,7 +654,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       var hasTypes = false
 
       @tailrec
-      def paramOrType(): Type = token match {
+      def paramOrType(modsBuf: mutable.Builder[Mod, List[Mod]]): Type = token match {
         case t: Ellipsis =>
           ellipsis[Type](t)
         case t: Unquote =>
@@ -662,7 +662,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         case KwImplicit() if !hasImplicits =>
           next()
           hasImplicits = true
-          paramOrType()
+          paramOrType(modsBuf)
         case t: Ident if tryAhead[Colon] =>
           if (hasTypes)
             syntaxError(
@@ -672,7 +672,12 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           hasParams = true
           val startPos = prevTokenPos
           next() // skip colon
-          autoEndPos(startPos)(Type.TypedParam(identName(t, startPos, Type.Name(_)), typ()))
+          val mods = modsBuf.result()
+          val modPos = if (mods.isEmpty) startPos else mods.head.startTokenPos
+          val name = identName(t, startPos, Type.Name.apply)
+          autoEndPos(modPos)(Type.TypedParam(name, typ(), mods))
+        case soft.KwErased() if allowFunctionType =>
+          paramOrType(modsBuf += atCurPosNext(Mod.Erased()))
         case _ =>
           if (hasParams)
             syntaxError(
@@ -680,11 +685,13 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
               at = token
             )
           hasTypes = true
-          paramType()
+          val mods = modsBuf.result()
+          val tpe = paramType()
+          if (mods.isEmpty) tpe else autoEndPos(mods.head)(Type.FunctionArg(mods, tpe))
       }
 
       val openParenPos = tokenPos
-      val ts = inParensOr(commaSeparated(paramOrType()))(Nil)
+      val ts = inParensOr(commaSeparated(paramOrType(List.newBuilder[Mod])))(Nil)
       // NOTE: can't have this, because otherwise we run into #312
       // newLineOptWhenFollowedBy[LeftParen]
 
@@ -701,10 +708,12 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         case _ => None
       })
       (if (allowFunctionType) maybeFunc else None).getOrElse {
-        ts.foreach {
-          case t: Type.ByName => syntaxError("by name type not allowed here", at = t)
-          case t: Type.Repeated => syntaxError("repeated type not allowed here", at = t)
-          case _ =>
+        ts.find {
+          case _: Type.ByName | _: Type.Repeated => true
+          case t: Type.FunctionParamOrArg => t.mods.nonEmpty
+          case _ => false
+        }.foreach { t =>
+          syntaxError(s"'${t.productPrefix}' type not allowed here", at = t)
         }
         val simple = simpleTypeRest(makeTupleType(openParenPos, ts), openParenPos)
         val compound = compoundTypeRest(annotTypeRest(simple, openParenPos), openParenPos)


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 4ac6a050de..879a788ef4 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
@@ -25,11 +25,21 @@ class ErasedDefsSuite extends BaseDottySuite {
 
   test("val with erased lambda") {
     val code = "val lambdaWithErasedEv: (erased Ev, Int) => Int = (erased ev, x) => x + 2"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: identifier expected but , found
-         |val lambdaWithErasedEv: (erased Ev, Int) => Int = (erased ev, x) => x + 2
-         |                                  ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Val(
+        Nil,
+        List(Pat.Var(tname("lambdaWithErasedEv"))),
+        Some(
+          Type.Function(
+            List(Type.FunctionArg(List(Mod.Erased()), pname("Ev")), pname("Int")),
+            pname("Int")
+          )
+        ),
+        Term.Function(
+          List(tparam(List(Mod.Erased()), "ev"), tparam(Nil, "x")),
+          Term.ApplyInfix(tname("x"), tname("+"), Nil, List(int(2)))
+        )
+      )
     )
   }
 
@@ -186,11 +196,18 @@ class ErasedDefsSuite extends BaseDottySuite {
 
   test("dependent-type") {
     val code = "val extractor: (erased e: Entry) => e.Key = extractKey"
-    runTestError[Stat](
-      code,
-      """|<input>:1: error: identifier expected but : found
-         |val extractor: (erased e: Entry) => e.Key = extractKey
-         |                        ^""".stripMargin
+    runTestAssert[Stat](code)(
+      Defn.Val(
+        Nil,
+        List(Pat.Var(Term.Name("extractor"))),
+        Some(
+          Type.Function(
+            List(Type.TypedParam(Type.Name("e"), Type.Name("Entry"), List(Mod.Erased()))),
+            Type.Select(Term.Name("e"), Type.Name("Key"))
+          )
+        ),
+        Term.Name("extractKey")
+      )
     )
   }