Repository: scalameta/scalameta (scala)

Issue:
Go to definition breaks in for comprehensions containing .unapply calls
### Describe the bug

An attempt to go to the definition of `foo` or `bar` in the following for comprehension leads to the `Option.withFilter` method
```scala
object GoToDefinitionWithFilterBug {
  case class Foo()
  val foo: Option[Foo] = None
  val bar: Option[(String, String)] = None

  for {
    Foo() <- foo // try going to definition on `foo`
    (_, _) <- bar // or on `bar`
  } yield ()
}
```
The bug seems to appear in expressions with calls to `.unapply` methods.

Actually, found it [here](https://github.com/scalameta/metals/blob/f8077f466130debd7d1fc7cb9a52f86d15b83f88/metals/src/main/scala/scala/meta/internal/rename/RenameProvider.scala#L80) while wandering over the Metals codebase

### Expected behavior

Go to definition results in going to actual definitions instead of `Option.withFilter`

### Operating system

Linux

### Editor/Extension

Nvim (nvim-metals)

### Version of Metals

v0.11.9

### Extra context or search terms

_No response_

Pull request:
Fixes https://github.com/scalameta/metals/issues/4548

Patch:
diff --git a/semanticdb/integration/src/main/scala/example/ForComprehension.scala b/semanticdb/integration/src/main/scala/example/ForComprehension.scala
index 23cbc1ce9d..fcfe6636b5 100644
--- a/semanticdb/integration/src/main/scala/example/ForComprehension.scala
+++ b/semanticdb/integration/src/main/scala/example/ForComprehension.scala
@@ -42,4 +42,8 @@ class ForComprehension {
       f
     )
   }
+  val x: Option[(Int, Int)] = None
+  for {
+    (_, _) <- x
+  } yield ()
 }
diff --git a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
index 0870ef13e7..0e8f270daa 100644
--- a/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
+++ b/semanticdb/scalac/library/src/main/scala/scala/meta/internal/semanticdb/scalac/TextDocumentOps.scala
@@ -728,7 +728,15 @@ trait TextDocumentOps { self: SemanticdbOps =>
                   case _ =>
                 }
               case select: g.Select if isSyntheticName(select) =>
-                tryFindMtree(select.qualifier)
+                select.qualifier match {
+                  // This case handles multiple synthetics in a row, for example
+                  //
+                  // ```val foo: Option[(Int, Int)] = None
+                  //    for { (_, _) <- foo } yield ()```
+                  // where `for` expands to `foo.withFilter(...).map(...)`
+                  case g.Apply(s: g.Select, _) if isSyntheticName(s) => traverse(s)
+                  case _ => tryFindMtree(select.qualifier)
+                }
                 tryFindSynthetic(select)
               case gtree: g.AppliedTypeTree =>
                 tryFindMtree(gtree)


Test patch:
diff --git a/tests/jvm/src/test/resources/example/ForComprehension.scala b/tests/jvm/src/test/resources/example/ForComprehension.scala
index 02967be5a1..e90f789779 100644
--- a/tests/jvm/src/test/resources/example/ForComprehension.scala
+++ b/tests/jvm/src/test/resources/example/ForComprehension.scala
@@ -42,4 +42,8 @@ class ForComprehension/*<=example.ForComprehension#*/ {
       f/*=>local15*/
     )
   }
+  val x/*<=example.ForComprehension#x.*/: Option/*=>scala.Option#*/[(Int/*=>scala.Int#*/, Int/*=>scala.Int#*/)] = None/*=>scala.None.*/
+  for {
+    (_, _) <- x/*=>example.ForComprehension#x.*/
+  } yield ()
 }
diff --git a/tests/jvm/src/test/resources/metac.expect_2.12 b/tests/jvm/src/test/resources/metac.expect_2.12
index a3c6dcbbc7..78ebd6f32f 100644
--- a/tests/jvm/src/test/resources/metac.expect_2.12
+++ b/tests/jvm/src/test/resources/metac.expect_2.12
@@ -1461,14 +1461,18 @@ Schema => SemanticDB v4
 Uri => semanticdb/integration/src/main/scala/example/ForComprehension.scala
 Text => non-empty
 Language => Scala
-Symbols => 13 entries
-Occurrences => 50 entries
-Synthetics => 12 entries
+Symbols => 15 entries
+Occurrences => 56 entries
+Synthetics => 13 entries
 
 Symbols:
-example/ForComprehension# => class ForComprehension extends AnyRef { +1 decls }
+example/ForComprehension# => class ForComprehension extends AnyRef { +2 decls }
   AnyRef => scala/AnyRef#
 example/ForComprehension#`<init>`(). => primary ctor <init>()
+example/ForComprehension#x. => val method x: Option[Tuple2[Int, Int]]
+  Option => scala/Option#
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 local0 => param a: Int
   Int => scala/Int#
 local1 => val local b: Int
@@ -1495,6 +1499,9 @@ local14 => val local e: Tuple4[Int, Int, Int, Int]
 local15 => param f: Tuple4[Int, Int, Int, Int]
   Tuple4 => scala/Tuple4#
   Int => scala/Int#
+local16 => param check$ifrefutable$2: Tuple2[Int, Int]
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 
 Occurrences:
 [0:8..0:15): example <= example/
@@ -1547,6 +1554,12 @@ Occurrences:
 [39:6..39:7): d => local12
 [40:6..40:7): e => local14
 [41:6..41:7): f => local15
+[44:6..44:7): x <= example/ForComprehension#x.
+[44:9..44:15): Option => scala/Option#
+[44:17..44:20): Int => scala/Int#
+[44:22..44:25): Int => scala/Int#
+[44:30..44:34): None => scala/None.
+[46:14..46:15): x => example/ForComprehension#x.
 
 Synthetics:
 [3:2..8:19): for {
@@ -1715,6 +1728,15 @@ Synthetics:
   apply => scala/collection/immutable/List.apply().
   Tuple4 => scala/Tuple4#
   Int => scala/Int#
+[45:2..47:12): for {
+    (_, _) <- x
+  } yield () => orig(x).withFilter({(check$ifrefutable$2) => orig()}).map[Unit]({(local17) => orig((_, _) <- x
+  } yield ())})
+  withFilter => scala/Option#withFilter().
+  check$ifrefutable$2 => local16
+  map => scala/Option#WithFilter#map().
+  Unit => scala/Unit#
+  local17 => local17
 
 semanticdb/integration/src/main/scala/example/ImplicitConversion.scala
 ----------------------------------------------------------------------
diff --git a/tests/jvm/src/test/resources/metac.expect_2.13 b/tests/jvm/src/test/resources/metac.expect_2.13
index 0c0ebefd30..2842bdeff4 100644
--- a/tests/jvm/src/test/resources/metac.expect_2.13
+++ b/tests/jvm/src/test/resources/metac.expect_2.13
@@ -1485,14 +1485,18 @@ Schema => SemanticDB v4
 Uri => semanticdb/integration/src/main/scala/example/ForComprehension.scala
 Text => non-empty
 Language => Scala
-Symbols => 13 entries
-Occurrences => 50 entries
-Synthetics => 12 entries
+Symbols => 15 entries
+Occurrences => 56 entries
+Synthetics => 13 entries
 
 Symbols:
-example/ForComprehension# => class ForComprehension extends AnyRef { +1 decls }
+example/ForComprehension# => class ForComprehension extends AnyRef { +2 decls }
   AnyRef => scala/AnyRef#
 example/ForComprehension#`<init>`(). => primary ctor <init>()
+example/ForComprehension#x. => val method x: Option[Tuple2[Int, Int]]
+  Option => scala/Option#
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 local0 => param a: Int
   Int => scala/Int#
 local1 => val local b: Int
@@ -1519,6 +1523,9 @@ local14 => val local e: Tuple4[Int, Int, Int, Int]
 local15 => param f: Tuple4[Int, Int, Int, Int]
   Tuple4 => scala/Tuple4#
   Int => scala/Int#
+local16 => param check$ifrefutable$2: Tuple2[Int, Int]
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 
 Occurrences:
 [0:8..0:15): example <= example/
@@ -1571,6 +1578,12 @@ Occurrences:
 [39:6..39:7): d => local12
 [40:6..40:7): e => local14
 [41:6..41:7): f => local15
+[44:6..44:7): x <= example/ForComprehension#x.
+[44:9..44:15): Option => scala/Option#
+[44:17..44:20): Int => scala/Int#
+[44:22..44:25): Int => scala/Int#
+[44:30..44:34): None => scala/None.
+[46:14..46:15): x => example/ForComprehension#x.
 
 Synthetics:
 [3:2..8:19): for {
@@ -1730,6 +1743,15 @@ Synthetics:
   apply => scala/collection/IterableFactory#apply().
   Tuple4 => scala/Tuple4#
   Int => scala/Int#
+[45:2..47:12): for {
+    (_, _) <- x
+  } yield () => orig(x).withFilter({(check$ifrefutable$2) => orig()}).map[Unit]({(local17) => orig((_, _) <- x
+  } yield ())})
+  withFilter => scala/Option#withFilter().
+  check$ifrefutable$2 => local16
+  map => scala/Option#WithFilter#map().
+  Unit => scala/Unit#
+  local17 => local17
 
 semanticdb/integration/src/main/scala/example/ImplicitConversion.scala
 ----------------------------------------------------------------------
diff --git a/tests/jvm/src/test/resources/metacp.expect_2.12 b/tests/jvm/src/test/resources/metacp.expect_2.12
index 4b7b54a4eb..95adfbbde1 100644
--- a/tests/jvm/src/test/resources/metacp.expect_2.12
+++ b/tests/jvm/src/test/resources/metacp.expect_2.12
@@ -1764,12 +1764,16 @@ Schema => SemanticDB v4
 Uri => example/ForComprehension.class
 Text => empty
 Language => Scala
-Symbols => 2 entries
+Symbols => 3 entries
 
 Symbols:
-example/ForComprehension# => class ForComprehension extends AnyRef { +1 decls }
+example/ForComprehension# => class ForComprehension extends AnyRef { +2 decls }
   AnyRef => scala/AnyRef#
 example/ForComprehension#`<init>`(). => primary ctor <init>()
+example/ForComprehension#x. => val method x: Option[Tuple2[Int, Int]]
+  Option => scala/Option#
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 
 example/ImplicitConversion.class
 --------------------------------
diff --git a/tests/jvm/src/test/resources/metacp.expect_2.13 b/tests/jvm/src/test/resources/metacp.expect_2.13
index 4c9148d7f6..b98b9b1047 100644
--- a/tests/jvm/src/test/resources/metacp.expect_2.13
+++ b/tests/jvm/src/test/resources/metacp.expect_2.13
@@ -1781,12 +1781,16 @@ Schema => SemanticDB v4
 Uri => example/ForComprehension.class
 Text => empty
 Language => Scala
-Symbols => 2 entries
+Symbols => 3 entries
 
 Symbols:
-example/ForComprehension# => class ForComprehension extends AnyRef { +1 decls }
+example/ForComprehension# => class ForComprehension extends AnyRef { +2 decls }
   AnyRef => scala/AnyRef#
 example/ForComprehension#`<init>`(). => primary ctor <init>()
+example/ForComprehension#x. => val method x: Option[Tuple2[Int, Int]]
+  Option => scala/Option#
+  Tuple2 => scala/Tuple2#
+  Int => scala/Int#
 
 example/ImplicitConversion.class
 --------------------------------
diff --git a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/TargetedSuite.scala b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/TargetedSuite.scala
index ca02f49d05..90eb007e60 100644
--- a/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/TargetedSuite.scala
+++ b/tests/jvm/src/test/scala/scala/meta/tests/semanticdb/TargetedSuite.scala
@@ -225,4 +225,21 @@ class TargetedSuite extends SemanticdbSuite {
       assertEquals(toInt, "scala/Int#toLong().")
     }
   )
+
+  targeted(
+    """
+    package n
+      | object ForCompWithFilter {
+      |  val foo: Option[(Int, Int)] = None
+      |  for {
+      |    (_, _) <- <<foo>>
+      |    (_, _) <- <<foo>>
+      |  } yield ()
+      |}
+    """.stripMargin,
+    (_, foo1, foo2) => {
+      assertEquals(foo1, "n/ForCompWithFilter.foo.")
+      assertEquals(foo2, "n/ForCompWithFilter.foo.")
+    }
+  )
 }