Repository: scalameta/scalameta (scala)

Issue:
PrettyPrinter bug when a Template self has a type but is anonymous
Using:
  - Scalameta version 4.7.7
  - Scala 2.13.10

Steps to reproduce:
```scala
import scala.meta._

val block =
  q"""{
    trait Bar { val b: Int }
    class Foo { _: Bar => def a: Int = b * b }
    println(new Foo with Bar { val b = 10 }.a)
  }"""

block.syntax
```
---

actual:
```scala
{
  trait Bar { val b: Int }
  class Foo { : Bar => def a: Int = b * b }
  println(new Foo with Bar { val b = 10 }.a)
}
```
(Notice the missing `_` in the self of the class `Foo`)

---

expected (same as original):
```scala
{
  trait Bar { val b: Int }
  class Foo { _: Bar => def a: Int = b * b }
  println(new Foo with Bar { val b = 10 }.a)
}
```

Pull request:
Fixes #3142.

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 3204cdea13..a660c9a44f 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
@@ -4108,7 +4108,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     if (isAfterOptNewLine[LeftBrace]) {
       // @S: pre template body cannot stub like post body can!
       val (self, body) = templateBody(enumCaseAllowed)
-      if (token.is[KwWith] && self.name.is[Name.Anonymous] && self.decltpe.isEmpty) {
+      if (token.is[KwWith] && self.isEmpty) {
         val edefs = body.map(ensureEarlyDef)
         next()
         val parents = templateParents(afterExtend)
diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala b/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala
index aed3790709..116e4bfaeb 100644
--- a/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala
+++ b/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala
@@ -440,6 +440,7 @@ object Pat {
 
 @branch trait Member extends Tree {
   def name: Name
+  final def isNameAnonymous: Boolean = name.is[Name.Anonymous]
 }
 object Member {
   @branch trait Term extends Member {
@@ -837,7 +838,9 @@ object Ctor {
   @replacedField("4.6.0") final def argss: List[List[Term]] = argClauses.map(_.values).toList
 }
 
-@ast class Self(name: Name, decltpe: Option[Type]) extends Member with Tree.WithDeclTpeOpt
+@ast class Self(name: Name, decltpe: Option[Type]) extends Member with Tree.WithDeclTpeOpt {
+  final def isEmpty: Boolean = isNameAnonymous && decltpe.isEmpty
+}
 
 @ast class Template(
     early: List[Stat],
diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
index 06af43702b..dc99663385 100644
--- a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
+++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala
@@ -518,10 +518,8 @@ object TreeSyntax {
       case t: Term.New => m(SimpleExpr, s(kw("new"), " ", t.init))
       case t: Term.EndMarker => s(kw("end"), " ", t.name.value)
       case t: Term.NewAnonymous =>
-        val needsExplicitBraces = {
-          val selfIsEmpty = t.templ.self.name.is[Name.Anonymous] && t.templ.self.decltpe.isEmpty
-          t.templ.early.isEmpty && t.templ.inits.length < 2 && selfIsEmpty && t.templ.stats.isEmpty
-        }
+        val needsExplicitBraces = t.templ.early.isEmpty && t.templ.inits.lengthCompare(2) < 0 &&
+          t.templ.self.isEmpty && t.templ.stats.isEmpty
         m(SimpleExpr, s(kw("new"), " ", t.templ), w(" {", "", "}", needsExplicitBraces))
       case _: Term.Placeholder => m(SimpleExpr1, kw("_"))
       case t: Term.Eta => m(PostfixExpr, s(p(SimpleExpr1, t.expr), " ", kw("_")))
@@ -912,11 +910,13 @@ object TreeSyntax {
         )
 
       // Self
-      case t: Self => s(t.name, t.decltpe)
+      case t: Self =>
+        if (t.isNameAnonymous && t.decltpe.nonEmpty) s("_", t.decltpe)
+        else s(t.name, t.decltpe)
 
       // Template
       case t: Template =>
-        val isSelfEmpty = t.self.name.is[Name.Anonymous] && t.self.decltpe.isEmpty
+        val isSelfEmpty = t.self.isEmpty
         val useExtends = (t.inits.nonEmpty || t.early.nonEmpty) &&
           t.parent.exists(p => p.isNot[Defn.Given] && p.isNot[Term.NewAnonymous])
         val extendsKeyword = if (useExtends) "extends" else ""
@@ -1061,7 +1061,7 @@ object TreeSyntax {
     private def isUsingOrImplicit(m: Mod): Boolean = m.is[Mod.ParamsType]
     private def printParam(t: Term.Param, keepImplicit: Boolean = false): Show.Result = {
       val mods = if (keepImplicit) t.mods else t.mods.filterNot(isUsingOrImplicit)
-      val nameType = if (t.name.is[Name.Anonymous]) {
+      val nameType = if (t.isNameAnonymous) {
         o(t.decltpe)
       } else {
         s(t.name, t.decltpe)


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/ParseSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/ParseSuite.scala
index e82bfc27d9..dc8c94edc9 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/ParseSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/ParseSuite.scala
@@ -66,12 +66,13 @@ class ParseSuite extends TreeSuiteBase with CommonTrees {
       code: String,
       f: ScalametaParser => T,
       syntax: String = null
-  )(tree: Tree)(implicit loc: munit.Location): Unit =
+  )(tree: Tree)(implicit loc: munit.Location, dialect: Dialect): Unit =
     checkTree(parseRule(code, f), syntax)(tree)
 
-  protected def checkStat(code: String, syntax: String = null)(tree: Tree)(
-      implicit loc: munit.Location
-  ): Unit =
+  protected def checkStat(
+      code: String,
+      syntax: String = null
+  )(tree: Tree)(implicit loc: munit.Location, dialect: Dialect): Unit =
     checkParsedTree(code, _.entrypointStat(), syntax)(tree)
 
   protected def runTestError[T <: Tree](
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
index 79c60a6450..6d1893bace 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/TemplateSuite.scala
@@ -382,4 +382,16 @@ class TemplateSuite extends ParseSuite {
     assertNoDiff(err.shortMessage, "class type required but A.type found")
   }
 
+  test("#3142: template with anonymous self type") {
+    val code = "class foo { _: Int => }"
+    val tree = Defn.Class(
+      Nil,
+      Type.Name("foo"),
+      Nil,
+      Ctor.Primary(Nil, Name(""), Nil),
+      Template(Nil, Nil, Self(Name(""), Some(Type.Name("Int"))), Nil, Nil)
+    )
+    checkStat(code, code)(tree)
+  }
+
 }
diff --git a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/TreeSyntaxSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/TreeSyntaxSuite.scala
index aaecb82fb4..26d5094c1d 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/TreeSyntaxSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/TreeSyntaxSuite.scala
@@ -58,6 +58,7 @@ class TreeSyntaxSuite extends scala.meta.tests.parsers.ParseSuite {
 
   testBlockAfterDef(k => testBlockAddNL(s"$k foo: Int"))
   testBlockNoNL("class foo { self => }")
+  testBlockNoNL("class foo { _: Int => }")
   testBlockNoNL("type foo")
   testBlockAfterDef(k => testBlockAddNL(s"$k foo: Int = 1"))
   testBlockAfterDef(k => testBlockNoNL(s"$k foo: Int = {1}", s"$k foo: Int = {\n  1\n}"))