Repository: scalameta/scalameta (scala)

Issue:
Leading infix not supported for types
It looks like the `AvoidInfix` rewrite is incorrectly applied to type aliases.

## Configuration  ##

Contents of`.scalafmt.conf`:
```
version = "3.7.3"
runner.dialect = scala213
rewrite.rules = [AvoidInfix]
```

## Command-line parameters ##

When I run scalafmt via CLI like this: `scalafmt src`

## Steps

Given code like this:
```scala
object Main extends App {

  trait Trait1
  trait Trait2
  trait Trait3
  trait Trait4
  trait Trait5
  trait Trait6

  type AllTraits = Trait1 & Trait2 & Trait3
  & Trait4 & Trait5

  println("Hello, World!")
}
```

## Problem

Scalafmt formats code like this:
```scala
object Main extends App {

  trait Trait1
  trait Trait2
  trait Trait3
  trait Trait4
  trait Trait5
  trait Trait6

  type AllTraits = Trait1 & Trait2 & Trait3
  &.Trait4(&) Trait5

  println("Hello, World!")
}
```

## Expectation

I would like the formatted output to look like this:
```scala
// No changes should happen
```

Pull request description:
Fixes #3119.

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 513df3dd4d..81ae29ac3b 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
@@ -882,6 +882,11 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
       val base = ctx.stack
       @inline def reduce(rhs: ctx.Typ, op: Option[ctx.Op]): ctx.Typ =
         ctx.reduceStack(base, rhs, rhs, op)
+      def getNextRhs(op: ctx.Op, rhs: ctx.Typ): ctx.Typ = {
+        newLineOptWhenFollowedBy[TypeIntro]
+        ctx.push(ctx.UnfinishedInfix(reduce(rhs, Some(op)), op))
+        compoundType(inMatchType = inMatchType)
+      }
       @tailrec
       def loop(rhs: ctx.Typ): ctx.Typ = token match {
         case Ident("*") if (peekToken match {
@@ -890,10 +895,12 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
             }) => // we assume that this is a type specification for a vararg parameter
           reduce(rhs, None)
         case _: Ident | _: Unquote =>
-          val op = typeName()
-          newLineOptWhenFollowedBy[TypeIntro]
-          ctx.push(ctx.UnfinishedInfix(reduce(rhs, Some(op)), op))
-          loop(compoundType(inMatchType = inMatchType))
+          loop(getNextRhs(typeName(), rhs))
+        case _: LF if dialect.allowInfixOperatorAfterNL =>
+          tryGetNextInfixOpIfLeading(t.startTokenPos)(Type.Name.apply) match {
+            case Some(op) => loop(getNextRhs(op, rhs))
+            case _ => reduce(rhs, None)
+          }
         case _ =>
           reduce(rhs, None)
       }
@@ -1946,6 +1953,35 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         at = at
       )
 
+  private def tryGetNextInfixOpIfLeading[A <: Name](startPos: Int)(f: String => A): Option[A] =
+    peekToken match {
+      case opToken: Ident if opToken.isIdentSymbolicInfixOperator =>
+        tryAhead {
+          val opIndex = tokenPos
+          next()
+          newLineOpt()
+          val argToken = token
+          val opPos = opToken.pos
+          val argPos = argToken.pos
+          val opLine = opPos.startLine
+          val argLine = argPos.startLine
+          val opCol = opPos.startColumn
+          val okInfix = canBeLeadingInfixArg(argToken, tokenPos) && {
+            if (opLine == argLine) opPos.endColumn < argPos.startColumn // space on same line
+            else opLine == argLine - 1 && opCol <= argPos.startColumn // indent on next line
+          }
+          if (okInfix) Some(atPos(opIndex)(f(opToken.value)))
+          else {
+            val indentedWithoutArg = opLine != argLine &&
+              opCol > scannerTokens.getTokenAtLineStart(startPos).pos.startColumn
+            if (indentedWithoutArg)
+              syntaxError(s"Invalid indented leading infix operator found", at = opToken)
+            None
+          }
+        }
+      case _ => None
+    }
+
   def postfixExpr(allowRepeated: Boolean, location: Location = NoStat): Term = {
     val startPos = tokenPos
 
@@ -2009,33 +2045,6 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
         }
       }
 
-      def tryGetNextRhsIfLeading: Option[Either[Term, Term]] = peekToken match {
-        case opToken: Ident if opToken.isIdentSymbolicInfixOperator =>
-          tryAhead {
-            val op = atCurPosNext(Term.Name(opToken.value))
-            newLineOpt()
-            val argToken = token
-            val opPos = opToken.pos
-            val argPos = argToken.pos
-            val opLine = opPos.startLine
-            val argLine = argPos.startLine
-            val opCol = opPos.startColumn
-            val okInfix = canBeLeadingInfixArg(argToken, tokenPos) && {
-              if (opLine == argLine) opPos.endColumn < argPos.startColumn // space on same line
-              else opLine == argLine - 1 && opCol <= argPos.startColumn // indent on next line
-            }
-            if (okInfix) Some(Right(getNextRhs(op, autoPos(Type.ArgClause(Nil)))))
-            else {
-              val indentedWithoutArg = opLine != argLine &&
-                opCol > scannerTokens.getTokenAtLineStart(startPos).pos.startColumn
-              if (indentedWithoutArg)
-                syntaxError(s"Invalid indented leading infix operator found", at = opToken)
-              None
-            }
-          }
-        case _ => None
-      }
-
       val resOpt = token match {
         case t: Unquote =>
           val op = unquote[Term.Name](t)
@@ -2048,7 +2057,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
           val lhs = getPrevLhs(op)
           Some(Right(matchClause(lhs, lhs.startTokenPos)))
         case _: LF if dialect.allowInfixOperatorAfterNL =>
-          tryGetNextRhsIfLeading
+          tryGetNextInfixOpIfLeading(startPos)(Term.Name.apply).map { op =>
+            Right(getNextRhs(op, autoPos(Type.ArgClause(Nil))))
+          }
         case _ => None
       }
       resOpt match {


Test patch:
diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/AndOrTypesSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/AndOrTypesSuite.scala
index 5667a9176a..e520b047ef 100644
--- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/AndOrTypesSuite.scala
+++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/AndOrTypesSuite.scala
@@ -95,4 +95,47 @@ class AndOrTypesSuite extends BaseDottySuite {
       )
     )
   }
+
+  test("#3119") {
+    val layout = "object A { type AllTraits = Trait1 & Trait2 & Trait3 & Trait4 & Trait5 }"
+    val tree = Defn.Object(
+      Nil,
+      tname("A"),
+      Template(
+        Nil,
+        Nil,
+        slf,
+        List(
+          Defn.Type(
+            Nil,
+            pname("AllTraits"),
+            Nil,
+            Type.ApplyInfix(
+              Type.ApplyInfix(
+                Type.ApplyInfix(
+                  Type.ApplyInfix(pname("Trait1"), pname("&"), pname("Trait2")),
+                  pname("&"),
+                  pname("Trait3")
+                ),
+                pname("&"),
+                pname("Trait4")
+              ),
+              pname("&"),
+              pname("Trait5")
+            ),
+            noBounds
+          )
+        ),
+        Nil
+      )
+    )
+    runTestAssert[Stat](
+      """|object A:
+         |  type AllTraits = Trait1 & Trait2 & Trait3
+         |    & Trait4 & Trait5
+         |""".stripMargin,
+      Some(layout)
+    )(tree)
+  }
+
 }