Repository: scalameta/scalameta
Pull request #2759 (2022-05-23 10:43:11)

Only print `?` for Scala 2 dialect if explicitly used
Previously, we would always print `?` for the placeholder even if the Scala version wouldn't allow it. Now, we only print `?` if it was explicitely used in the code.

Fixes https://github.com/scalameta/scalameta/issues/2751

The other solution would be introducing another dialect, but that would require a lot of people having to also change that, so it would require a lot more effort in the long run.


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 3f16814ca..ecc2530b6 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
@@ -15,11 +15,13 @@ import Show.{
 }
 import scala.meta.internal.trees.{root => _, branch => _, _}
 import scala.meta.internal.tokenizers.Chars._
+import scala.meta.internal.trees.Origin.Parsed
 import org.scalameta.adt._
 import org.scalameta.invariants._
 import org.scalameta.unreachable
 import scala.compat.Platform.EOL
 import scala.meta.inputs.Position
+import scala.meta.tokens.Token
 
 object TreeSyntax {
   private final object SyntaxInstances {
@@ -688,7 +690,20 @@ object TreeSyntax {
           s(p(AnyInfixTyp, t.tpe), " ", kw("match"), " {", r(t.cases.map(i(_)), ""), n("}"))
         )
       case t: Type.Placeholder =>
-        if (dialect.allowQuestionMarkPlaceholder) m(SimpleTyp, s(kw("?"), t.bounds))
+        /* In order not to break existing tools `.syntax` should still return
+         * `_` instead `?` unless specifically used.
+         */
+        def isScala3Dialect = dialect.allowSignificantIndentation
+        def questionMarkUsed = t.origin match {
+          case _: Origin.Parsed =>
+            !t.tokens.exists {
+              case _: Token.Underscore => true
+              case _ => false
+            }
+          case _ => false
+        }
+        if (dialect.allowQuestionMarkPlaceholder && (isScala3Dialect || questionMarkUsed))
+          m(SimpleTyp, s(kw("?"), t.bounds))
         else m(SimpleTyp, s(kw("_"), t.bounds))
       case t: Type.Bounds =>
         s(


Test patch:
diff --git a/tests/jvm/src/test/scala/scala/meta/tests/prettyprinters/RegressionSyntaxSuite.scala b/tests/jvm/src/test/scala/scala/meta/tests/prettyprinters/RegressionSyntaxSuite.scala
new file mode 100644
index 000000000..5776ece33
--- /dev/null
+++ b/tests/jvm/src/test/scala/scala/meta/tests/prettyprinters/RegressionSyntaxSuite.scala
@@ -0,0 +1,58 @@
+package scala.meta.tests.prettyprinters
+
+import scala.meta.tests.parsers.ParseSuite
+
+import scala.meta._
+import munit.TestOptions
+
+class RegressionSyntaxSuite extends ParseSuite {
+
+  def check(
+      testOpts: TestOptions,
+      code: String,
+      expected: String,
+      newDialect: Option[Dialect] = None
+  )(implicit dialect: Dialect) = test(testOpts) {
+    val obtained = code.parse[Stat].get
+    val reprintedCode =
+      scala.meta.internal.prettyprinters.TreeSyntax
+        .reprint(obtained)(newDialect.getOrElse(dialects.Scala213))
+        .toString
+    assertEquals(reprintedCode, expected)
+  }
+
+  check("question-mark", "type T = List[?]", "type T = List[?]")
+  check("underscore", "type T = List[_]", "type T = List[_]")
+  check(
+    "new-dialect-scala3",
+    "type T = List[_]",
+    "type T = List[?]",
+    newDialect = Some(dialects.Scala3)
+  )(
+    dialects.Scala211
+  )
+
+  check(
+    "new-dialect-scala213",
+    "type T = List[_]",
+    "type T = List[_]",
+    newDialect = Some(dialects.Scala213)
+  )(
+    dialects.Scala211
+  )
+  check(
+    "new-dialect-scala213-from-scala3",
+    "type T = List[?]",
+    "type T = List[?]",
+    newDialect = Some(dialects.Scala213)
+  )(
+    dialects.Scala3
+  )
+
+  test("no-origin") {
+    assertEquals(
+      Type.Placeholder(Type.Bounds(None, None)).toString(),
+      "_"
+    )
+  }
+}