Repository: scalameta/scalameta (scala)

Issue:
Case with structural type throws
## Required: Configuration ##

Please paste the **smallest** possible set of `.scalafmt.conf`
configuration parameters that reproduces the problem (also throws with scala212 and scala3 dialects):
```
version = 3.9.3
runner.dialect = scala213
```

## Required: Command-line parameters ##

When I run scalafmt via CLI like this: `scalafmt --test structural.scala`

## Steps

Given code like this:
```scala
case class Foo[A]()
trait Bar
"" match {
  case _: Foo[{ type A = Bar }] => ""
}
```

## Problem

Scalafmt formats throws the following:
```scala
org.scalafmt.cli.FailedToFormat: /Users/darius.jokilehto/dev/pap-promotions-promotions-data-model/structural-pm/structural.scala
Caused by: java.util.NoSuchElementException: Missing token index RightBrace [17..18) @2:0: `}`
	at org.scalafmt.internal.FormatTokens$.org$scalafmt$internal$FormatTokens$$throwNoToken(FormatTokens.scala:460)
	at org.scalafmt.internal.FormatTokens.$anonfun$org$scalafmt$internal$FormatTokens$$get$1(FormatTokens.scala:44)
	at scala.collection.immutable.BitmapIndexedMapNode.getOrElse(HashMap.scala:722)
	at scala.collection.immutable.HashMap.getOrElse(HashMap.scala:720)
	at org.scalafmt.internal.FormatTokens.org$scalafmt$internal$FormatTokens$$get(FormatTokens.scala:44)
	at org.scalafmt.internal.FormatTokens.before(FormatTokens.scala:52)
	at org.scalafmt.internal.FormatTokens.apply(FormatTokens.scala:57)
	at org.scalafmt.internal.FormatTokens.getLastImpl(FormatTokens.scala:264)
	at org.scalafmt.internal.FormatTokens.getLast(FormatTokens.scala:266)
	at org.scalafmt.internal.FormatTokens.getLast(FormatTokens.scala:268)
	at org.scalafmt.internal.SplitsAfterEquals$.endFt$1(Splits.scala:537)
	at org.scalafmt.internal.SplitsAfterEquals$.$anonfun$getDefValAssign$6(Splits.scala:540)
	at org.scalafmt.internal.SplitsAfterEquals$.getSplitsValEquals(Splits.scala:608)
	at org.scalafmt.internal.SplitsAfterEquals$.$anonfun$getDefValAssign$4(Splits.scala:540)
	at org.scalafmt.internal.SplitsAfterEquals$.getSplitsDefValEquals(Splits.scala:574)
	at org.scalafmt.internal.SplitsAfterEquals$.$anonfun$getDefValAssign$2(Splits.scala:538)
	at scala.Option.fold(Option.scala:263)
	at org.scalafmt.internal.FormatOps.maybeGetInfixSplitsBeforeLhs(FormatOps.scala:425)
	at org.scalafmt.internal.SplitsAfterEquals$.getDefValAssign(Splits.scala:536)
	at org.scalafmt.internal.SplitsAfterEquals$.get(Splits.scala:527)
	at org.scalafmt.internal.Splits$Multi.$anonfun$get$1(Splits.scala:42)
	at org.scalafmt.internal.Splits$Multi.$anonfun$get$1$adapted(Splits.scala:41)
	at scala.collection.immutable.List.foreach(List.scala:334)
	at org.scalafmt.internal.Splits$Multi.get(Splits.scala:41)
	at org.scalafmt.internal.Splits.getOpt(Splits.scala:28)
	at org.scalafmt.internal.Splits.getOpt$(Splits.scala:23)
	at org.scalafmt.internal.Splits$Multi.getOpt(Splits.scala:35)
	at org.scalafmt.internal.Router.$anonfun$getSplits$1(Router.scala:25)
	at scala.Option.flatMap(Option.scala:283)
	at org.scalafmt.internal.Router.getSplits(Router.scala:25)
	at org.scalafmt.internal.BestFirstSearch.$anonfun$routes$1(BestFirstSearch.scala:31)
	at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:619)
	at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:617)
	at org.scalafmt.internal.FormatTokens.foreach(FormatTokens.scala:15)
	at org.scalafmt.internal.BestFirstSearch.<init>(BestFirstSearch.scala:31)
	at org.scalafmt.internal.BestFirstSearch$.apply(BestFirstSearch.scala:334)
	at org.scalafmt.Scalafmt$.$anonfun$doFormatOne$7(Scalafmt.scala:128)
	at scala.util.Try$.apply(Try.scala:217)
	at org.scalafmt.Scalafmt$.$anonfun$doFormatOne$5(Scalafmt.scala:128)
	at scala.meta.parsers.Parsed.fold(Errors.scala:11)
	at scala.meta.parsers.Parsed.fold$(Errors.scala:10)
	at scala.meta.parsers.Parsed$Success.fold(Errors.scala:25)
	at org.scalafmt.Scalafmt$.doFormatOne(Scalafmt.scala:124)
	at org.scalafmt.Scalafmt$.doFormat(Scalafmt.scala:98)
	at org.scalafmt.Scalafmt$.$anonfun$formatCode$3(Scalafmt.scala:79)
	at scala.util.Success.fold(Try.scala:289)
	at org.scalafmt.Scalafmt$.formatCode(Scalafmt.scala:79)
	at org.scalafmt.cli.ScalafmtCoreRunner$.handleFile(ScalafmtCoreRunner.scala:63)
	at org.scalafmt.cli.ScalafmtCoreRunner$.$anonfun$runWithFilterMatcher$4(ScalafmtCoreRunner.scala:42)
	at org.scalafmt.cli.ScalafmtRunner.$anonfun$runInputs$3(ScalafmtRunner.scala:108)
	at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:467)
	at scala.concurrent.ExecutionContext$parasitic$.execute(ExecutionContext.scala:222)
	at scala.concurrent.impl.Promise$Transformation.submitWithValue(Promise.scala:429)
	at scala.concurrent.impl.Promise$DefaultPromise.submitWithValue(Promise.scala:338)
	at scala.concurrent.impl.Promise$DefaultPromise.tryComplete0(Promise.scala:285)
	at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:278)
	at scala.concurrent.Promise.trySuccess(Promise.scala:99)
	at scala.concurrent.Promise.trySuccess$(Promise.scala:99)
	at scala.concurrent.impl.Promise$DefaultPromise.trySuccess(Promise.scala:104)
	at org.scalafmt.sysops.GranularPlatformAsyncOps$ReadContext.read(GranularPlatformAsyncOps.scala:82)
	at org.scalafmt.sysops.GranularPlatformAsyncOps$ReadHandler$.completed(GranularPlatformAsyncOps.scala:55)
	at org.scalafmt.sysops.GranularPlatformAsyncOps$ReadHandler$.completed(GranularPlatformAsyncOps.scala:53)
	at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127)
	at java.base/sun.nio.ch.SimpleAsynchronousFileChannelImpl$2.run(SimpleAsynchronousFileChannelImpl.java:335)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)
error: UnexpectedError=8
```

## Expectation

Not throw and formatted appropriately.

## Workaround

Downgrade to 3.8.3.

## Notes

Erroneously raised in scalameta earlier: https://github.com/scalameta/scalameta/issues/4207.
This is a regression since 3.8.4 - version 3.8.3 doesn't throw.
Failing test: https://github.com/dariusj/scalafmt/commit/da6fe26f66594aca2c9f6909b0027f2cc57d9d81.

Pull request:
Since tree fields are changing, and `unapply` always supports only the initial field composition, let's not use `unapply` when constructing a new tree from these fields, as they might lose their parsed positions. Fixes #4208.

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 96832bf3e3..da94a26cf7 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
@@ -1144,10 +1144,9 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
       def loop(outerTpe: Type, convertTypevars: Boolean): Type = setPos(outerTpe) {
         outerTpe match {
           case q: Quasi => q
-          case tpe @ Type.Name(value) if convertTypevars && value(0).isLower => Type.Var(tpe)
-          case tpe: Type.Name => tpe
+          case t: Type.Name => if (convertTypevars && t.value(0).isLower) Type.Var(t) else t
           case tpe: Type.Select => tpe
-          case Type.Project(qual, name) => Type.Project(copyType(qual), name)
+          case t: Type.Project => Type.Project(copyType(t.qual), t.name)
           case tpe: Type.Singleton => tpe
           case t: Type.Apply =>
             val args1 = t.argClause match {
@@ -1155,7 +1154,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
               case x: Type.ArgClause => copyPos(x)(Type.ArgClause(x.values.map(convertType)))
             }
             Type.Apply(copyType(t.tpe), args1)
-          case Type.ApplyInfix(lhs, op, rhs) => Type.ApplyInfix(copyType(lhs), op, copyType(rhs))
+          case t: Type.ApplyInfix => Type.ApplyInfix(copyType(t.lhs), t.op, copyType(t.rhs))
           case t: Type.ByName => convertByName(t)(Type.ByName(_))
           case t: Type.PureByName => convertByName(t)(Type.PureByName(_))
           case t: Type.Function => convertFunc(t)(Type.Function(_, _))
@@ -1163,11 +1162,11 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) {
           case t: Type.ContextFunction => convertFunc(t)(Type.ContextFunction(_, _))
           case t: Type.PureContextFunction => convertFunc(t)(Type.PureContextFunction(_, _))
           case t: Type.PolyFunction => Type.PolyFunction(t.tparamClause, copyType(t.tpe))
-          case Type.Tuple(elements) => Type.Tuple(elements.map(convertType))
-          case Type.With(lhs, rhs) => Type.With(copyType(lhs), copyType(rhs))
-          case Type.Refine(tpe, stats) => Type.Refine(tpe.map(copyType), stats)
-          case Type.Existential(underlying, stats) => Type.Existential(copyType(underlying), stats)
-          case Type.Annotate(underlying, annots) => Type.Annotate(copyType(underlying), annots)
+          case t: Type.Tuple => Type.Tuple(t.args.map(convertType))
+          case t: Type.With => Type.With(copyType(t.lhs), copyType(t.rhs))
+          case t: Type.Refine => Type.Refine(t.tpe.map(copyType), t.body)
+          case t: Type.Existential => Type.Existential(copyType(t.tpe), t.body)
+          case t: Type.Annotate => Type.Annotate(copyType(t.tpe), t.annots)
           case t: Type.Wildcard => Type.Wildcard(t.bounds)
           case t: Type.AnonymousLambda => Type.AnonymousLambda(copyType(t.tpe))
           case t: Type.AnonymousParam => Type.AnonymousParam(t.variant)


Test patch:
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 e66650c815..f5aba5c0c3 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
@@ -1107,4 +1107,29 @@ class TokensPositionSuite extends BasePositionSuite(dialects.Scala213) {
        |""".stripMargin
   )
 
+  // #4208
+  checkPositions[Term](
+    """|"" match {
+       |  case _: Foo[{ type A = Bar }] =>
+       |}
+       |""".stripMargin,
+    """|<expr>Lit.String ""</expr> [0:"":2)
+       |<casesBlock>Term.CasesBlock {
+       |  case _: Foo[{ type A = Bar }] =>
+       |}</casesBlock> [9:{...:47)
+       |<cases0>Case case _: Foo[{ type A = Bar }] =></cases0> [13:case _: Foo[{ type A = Bar }] =>:45)
+       |<pat>Pat.Typed _: Foo[{ type A = Bar }]</pat> [18:_: Foo[{ type A = Bar }]:42)
+       |<rhs>Type.Apply Foo[{ type A = Bar }]</rhs> [21:Foo[{ type A = Bar }]:42)
+       |<argClause>Type.ArgClause [{ type A = Bar }]</argClause> [24:[{ type A = Bar }]:42)
+       |<values0>Type.Refine { type A = Bar }</values0> [25:{ type A = Bar }:41)
+       |<body>Stat.Block { type A = Bar }</body> [25:{ type A = Bar }:41)
+       |<stats0>Defn.Type type A = Bar</stats0> [27:type A = Bar:39)
+       |<tparamClause>Type.ParamClause   case _: Foo[{ type A @@= Bar }] =></tparamClause> [34::34)
+       |<bounds>Type.Bounds   case _: Foo[{ type A @@= Bar }] =></bounds> [34::34)
+       |<body>Term.Block @@}</body> [46::46)
+       |""".stripMargin,
+    showPosition = true,
+    showFieldName = true
+  )
+
 }