Repository: scalameta/scalameta (scala)

Issue:
ScalametaParser: missing `(` in postfix
4.6.0 OK
```scala
@ """(10) + 1 toInt""".parse[Stat].get.collect { case Term.Select(qual, _) => qual }.head.syntax 
res1: String = "(10) + 1"
```

4.7.0 KO
```scala
@ """(10) + 1 toInt""".parse[Stat].get.collect { case Term.Select(qual, _) => qual }.head.syntax 
res1: String = "10) + 1"
```

4.8.0 KO
```scala
@ """(10) + 1 toInt""".parse[Stat].get.collect { case Term.Select(qual, _) => qual }.head.syntax 
res1: String = "10) + 1"
```

Originally reported in https://github.com/scalacenter/scalafix/issues/1794

Pull request description:
Fixes #3219.

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 0677c554b7..90f22839c3 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
@@ -2037,6 +2037,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
     val ctx = termInfixContext
     val base = ctx.stack
 
+    def getLhsStartPos(lhs: ctx.Typ): Int =
+      if (lhs eq rhs0) startPos else lhs.startTokenPos
+
     // Skip to later in the `postfixExpr` method to start mental debugging.
     // rhsStartK/rhsEndK may be bigger than then extent of rhsK,
     // so we really have to track them separately.
@@ -2049,13 +2052,16 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       }
 
       def getNextRhs(op: Term.Name, targs: Type.ArgClause): Term = {
-        ctx.push(ctx.UnfinishedInfix(getPrevLhs(op), op, targs))
+        val lhs = getPrevLhs(op)
+        val wrap = (lhs eq rhs0) && lhs.startTokenPos != startPos
+        val lhsExt = if (wrap) atPosWithBody(startPos, Term.Tuple(lhs :: Nil), rhsEndK) else lhs
+        ctx.push(ctx.UnfinishedInfix(lhsExt, op, targs))
         argumentExprsOrPrefixExpr(PostfixStat)
       }
 
       def getPostfix(op: Term.Name): Term = {
         val finQual = getPrevLhs(op)
-        atPos(finQual, op)(Term.Select(finQual, op))
+        atPos(getLhsStartPos(finQual), op)(Term.Select(finQual, op))
       }
 
       def getPostfixOrNextRhs(op: Term.Name): Either[Term, Term] = {
@@ -2091,7 +2097,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         case _: KwMatch if dialect.allowMatchAsOperator =>
           val op = atCurPosNext(Term.Name("match"))
           val lhs = getPrevLhs(op)
-          Some(Right(matchClause(lhs, lhs.startTokenPos)))
+          Some(Right(matchClause(lhs, getLhsStartPos(lhs))))
         case _: LF if dialect.allowInfixOperatorAfterNL =>
           tryGetNextInfixOpIfLeading(startPos)(Term.Name.apply).map { op =>
             Right(getNextRhs(op, autoPos(Type.ArgClause(Nil))))


Test patch:
diff --git a/tests/shared/src/test/scala-2.13/scala/meta/tests/parsers/dotty/Scala3PositionSuite.scala b/tests/shared/src/test/scala-2.13/scala/meta/tests/parsers/dotty/Scala3PositionSuite.scala
index 4a0385e5a9..56eb4f0836 100644
--- a/tests/shared/src/test/scala-2.13/scala/meta/tests/parsers/dotty/Scala3PositionSuite.scala
+++ b/tests/shared/src/test/scala-2.13/scala/meta/tests/parsers/dotty/Scala3PositionSuite.scala
@@ -1079,4 +1079,23 @@ class Scala3PositionSuite extends BasePositionSuite(dialects.Scala3) {
        |""".stripMargin
   )
 
+  // #3219
+  checkPositions[Stat](
+    "(10) + 1 toInt",
+    """|Term.ApplyInfix (10) + 1
+       |Type.ArgClause (10) + @@1 toInt
+       |""".stripMargin
+  )
+  checkPositions[Stat](
+    """|if (10) + 1 == 11 then
+       |  fx
+       |""".stripMargin,
+    """|Term.ApplyInfix (10) + 1 == 11
+       |Term.ApplyInfix (10) + 1
+       |Type.ArgClause if (10) + @@1 == 11 then
+       |Type.ArgClause if (10) + 1 == @@11 then
+       |Lit.Unit @@
+       |""".stripMargin
+  )
+
 }
diff --git a/tests/shared/src/test/scala-2.13/scala/meta/tests/tokenizers/TokensPositionSuite.scala b/tests/shared/src/test/scala-2.13/scala/meta/tests/tokenizers/TokensPositionSuite.scala
index 6bcfc64377..67c3d4e411 100644
--- a/tests/shared/src/test/scala-2.13/scala/meta/tests/tokenizers/TokensPositionSuite.scala
+++ b/tests/shared/src/test/scala-2.13/scala/meta/tests/tokenizers/TokensPositionSuite.scala
@@ -816,4 +816,12 @@ class TokensPositionSuite extends BasePositionSuite(dialects.Scala213) {
        |Case case foo => foo
        |""".stripMargin
   )
+
+  // #3219
+  checkPositions[Term](
+    "(10) + 1 toInt",
+    """|Term.ApplyInfix (10) + 1
+       |Type.ArgClause (10) + @@1 toInt
+       |""".stripMargin
+  )
 }