Repository: scalameta/scalameta (scala)

Issue:
'using' keyword is incorrectly repeated in pretty-printed syntax of class definition
```scala
@ import scala.meta.dialects.Scala3
import scala.meta.dialects.Scala3

@ val classDefn = q"class Foo(a: Int)(using x: Int, y: String) {}"
classDefn: Defn.Class = Defn.Class(
  mods = List(),
  name = Type.Name(value = "Foo"),
  tparams = List(),
  ctor = Ctor.Primary(
    mods = List(),
    name = _,
    paramss = List(
      List(Term.Param(mods = List(), name = Term.Name(value = "a"), decltpe = Some(value = Type.Name(value = "Int")), default = None)),
      List(Term.Param(mods = List(using), name = Term.Name(value = "x"), decltpe = Some(value = Type.Name(value = "Int")), default = None), Term.Param(mods = List(using), name = Term.Name(value = "y"), decltpe = Some(value = Type.Name(value = "String")), default = None))
    )
  ),
  templ = Template(early = List(), inits = List(), self = Self(name = _, decltpe = None), stats = List(), derives = List())
)

@ classDefn.syntax
res11: String = "class Foo(a: Int)(using x: Int, using y: String)"
```

Note how the `using` keyword is repeated for each parameter in the list, which is not valid Scala 3 syntax.

This problem only seems to occur for class definitions, not method definitions:

```scala
@ val defDefn = q"def foo(a: Int)(using x: Int, y: String) = a"
defDefn: Defn.Def = Defn.Def(
  mods = List(),
  name = Term.Name(value = "foo"),
  tparams = List(),
  paramss = List(
    List(Term.Param(mods = List(), name = Term.Name(value = "a"), decltpe = Some(value = Type.Name(value = "Int")), default = None)),
    List(Term.Param(mods = List(using), name = Term.Name(value = "x"), decltpe = Some(value = Type.Name(value = "Int")), default = None), Term.Param(mods = List(using), name = Term.Name(value = "y"), decltpe = Some(value = Type.Name(value = "String")), default = None))
  ),
  decltpe = None,
  body = Term.Name(value = "a")
)

@ defDefn.syntax
res13: String = "def foo(a: Int)(using x: Int, y: String) = a"
```

Pull request:
Fixes #2699

Patch:
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 53048594bd..cdb2642229 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
@@ -984,10 +984,16 @@ object TreeSyntax {
                 params.headOption.exists(_.mods.headOption.exists(_.is[Mod.Implicit]))
               val firstParamContainsImplicit =
                 params.headOption.exists(_.mods.exists(_.is[Mod.Implicit]))
+              val firstParamModIsUsing =
+                params.headOption.exists(_.mods.headOption.exists(_.is[Mod.Using]))
+              val firstParamContainsUsing =
+                params.headOption.exists(_.mods.exists(_.is[Mod.Using]))
 
               if (firstParamModIsImplicit)
                 s("(implicit ", r(params, ", "), ")")
-              else if (firstParamContainsImplicit)
+              else if (firstParamModIsUsing)
+                s("(using ", r(params, ", "), ")")
+              else if (firstParamContainsImplicit || firstParamContainsUsing)
                 s(
                   "(",
                   printParam(params.head),


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
index bdae0bb41f..6cabe6b911 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala
@@ -1228,4 +1228,64 @@ class SyntacticSuite extends scala.meta.tests.parsers.ParseSuite {
     checkTree(q"new Foo((a: Int) => a + 1)", "new Foo((a: Int) => a + 1)")
   }
 
+  test("#2699 method declaration with multiple named 'using' params") {
+    assertEquals(
+      templStat("def foo(x: Int)(using y: String, z: Boolean): String")(dialects.Scala3).syntax,
+      "def foo(x: Int)(using y: String, z: Boolean): String"
+    )
+  }
+
+  test("#2699 method definition with multiple named 'using' params") {
+    assertEquals(
+      templStat("def foo(x: Int)(using y: String, z: Boolean) = x")(dialects.Scala3).syntax,
+      "def foo(x: Int)(using y: String, z: Boolean) = x"
+    )
+  }
+
+  test("#2699 primary constructor with multiple named 'using' params") {
+    assertEquals(
+      templStat("class C(x: Int)(using y: String, z: Boolean)")(dialects.Scala3).syntax,
+      "class C(x: Int)(using y: String, z: Boolean)"
+    )
+  }
+
+  test("#2699 secondary constructor with multiple named 'using' params") {
+    assertEquals(
+      templStat(
+        "class C(x: Int) { def this(x: String)(using y: String, z: Boolean) = this(x.toInt) }"
+      )(dialects.Scala3).syntax,
+      "class C(x: Int) { def this(x: String)(using y: String, z: Boolean) = this(x.toInt) }"
+    )
+  }
+
+  test("#2699 method declaration with multiple anonymous 'using' params") {
+    assertEquals(
+      templStat("def foo(x: Int)(using String, Boolean): String")(dialects.Scala3).syntax,
+      "def foo(x: Int)(using String, Boolean): String"
+    )
+  }
+
+  test("#2699 method definition with multiple anonymous 'using' params") {
+    assertEquals(
+      templStat("def foo(x: Int)(using String, Boolean) = x")(dialects.Scala3).syntax,
+      "def foo(x: Int)(using String, Boolean) = x"
+    )
+  }
+
+  test("#2699 primary constructor with multiple anonymous 'using' params") {
+    assertEquals(
+      templStat("class C(x: Int)(using String, Boolean)")(dialects.Scala3).syntax,
+      "class C(x: Int)(using String, Boolean)"
+    )
+  }
+
+  test("#2699 secondary constructor with multiple anonymous 'using' params") {
+    assertEquals(
+      templStat(
+        "class C(x: Int) { def this(x: String)(using String, Boolean) = this(x.toInt) }"
+      )(dialects.Scala3).syntax,
+      "class C(x: Int) { def this(x: String)(using String, Boolean) = this(x.toInt) }"
+    )
+  }
+
 }