From 748d4391bb90fcbd852f01562aa74224caa790a4 Mon Sep 17 00:00:00 2001
From: rochala <rochala@tutanota.com>
Date: Sun, 30 Jun 2024 23:50:14 +0200
Subject: [PATCH 1/4] Remove artificial CURSOR added for the completions

---
 .../dotty/tools/dotc/ast/NavigateAST.scala    | 11 +++-
 .../tools/dotc/interactive/Completion.scala   | 13 ++--
 .../tools/languageserver/CompletionTest.scala |  9 +++
 .../dotty/tools/pc/AutoImportsProvider.scala  |  2 +-
 .../tools/pc/completions/CompletionPos.scala  |  9 ++-
 .../pc/completions/CompletionProvider.scala   | 64 +++++++++++++------
 .../tools/pc/completions/Completions.scala    | 20 ++----
 .../completions/InterpolatorCompletions.scala |  2 +-
 .../pc/completions/NamedArgCompletions.scala  | 31 ++++-----
 .../pc/completions/OverrideCompletions.scala  | 16 ++++-
 .../tests/completion/CompletionArgSuite.scala | 62 ++++++++++--------
 .../CompletionInterpolatorSuite.scala         |  2 +-
 .../pc/tests/completion/CompletionSuite.scala |  9 +--
 .../tests/definition/PcDefinitionSuite.scala  |  4 +-
 .../tools/pc/tests/hover/HoverTermSuite.scala |  6 +-
 15 files changed, 159 insertions(+), 101 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
index f83f12e1c027..2aeb2f7df067 100644
--- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
+++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
@@ -3,6 +3,7 @@ package ast
 
 import core.Contexts.*
 import core.Decorators.*
+import core.StdNames
 import util.Spans.*
 import Trees.{Closure, MemberDef, DefTree, WithLazyFields}
 import dotty.tools.dotc.core.Types.AnnotatedType
@@ -76,6 +77,8 @@ object NavigateAST {
       var bestFit: List[Positioned] = path
       while (it.hasNext) {
         val path1 = it.next() match {
+          // FIXME this has to be changed to deterministicaly find recoveed tree
+          case untpd.Select(qual, name) if name == StdNames.nme.??? => path
           case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path)
           case m: untpd.Modifiers => childPath(m.productIterator, path)
           case xs: List[?] => childPath(xs.iterator, path)
@@ -84,11 +87,17 @@ object NavigateAST {
         if ((path1 ne path) &&
             ((bestFit eq path) ||
              bestFit.head.span != path1.head.span &&
-             bestFit.head.span.contains(path1.head.span)))
+             envelops(bestFit.head.span, path1.head.span)))
           bestFit = path1
       }
       bestFit
     }
+
+    def envelops(a: Span, b: Span): Boolean =
+      !b.exists || a.exists && (
+        (a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end)
+      )
+
     /*
      * Annotations trees are located in the Type
      */
diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala
index 1395d9b80b53..7112caf1cfad 100644
--- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala
+++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala
@@ -121,16 +121,17 @@ object Completion:
       case _ =>
         ""
 
+  def naiveCompletionPrefix(text: String, offset: Int): String =
+    var i = offset - 1
+    while i >= 0 && text(i).isUnicodeIdentifierPart do i -= 1
+    i += 1 // move to first character
+    text.slice(i, offset)
+
   /**
    * Inspect `path` to determine the completion prefix. Only symbols whose name start with the
    * returned prefix should be considered.
    */
   def completionPrefix(path: List[untpd.Tree], pos: SourcePosition)(using Context): String =
-    def fallback: Int =
-      var i = pos.point - 1
-      while i >= 0 && Character.isUnicodeIdentifierPart(pos.source.content()(i)) do i -= 1
-      i + 1
-
     path match
       case GenericImportSelector(sel) =>
         if sel.isGiven then completionPrefix(sel.bound :: Nil, pos)
@@ -148,7 +149,7 @@ object Completion:
       case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
         tree.name.toString.take(pos.span.point - tree.span.point)
 
-      case _ => pos.source.content.slice(fallback, pos.point).mkString
+      case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)
 
 
   end completionPrefix
diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala
index 8034b4c8d40b..043788dbd0ac 100644
--- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala
+++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala
@@ -1706,6 +1706,15 @@ class CompletionTest {
        ("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
      ))
 
+  @Test def testtest: Unit =
+    code"""|object M {
+           |  def sel$m1
+           |}
+           |"""
+     .completion(m1, Set(
+       ("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
+     ))
+
   @Test def noEnumCompletionInNewContext: Unit =
     code"""|enum TestEnum:
            |  case TestCase
diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
index ded7845ffa4e..0252786c20f6 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
@@ -67,7 +67,7 @@ final class AutoImportsProvider(
     val results = symbols.result.filter(isExactMatch(_, name))
 
     if results.nonEmpty then
-      val correctedPos = CompletionPos.infer(pos, params, path).toSourcePosition
+      val correctedPos = CompletionPos.infer(pos, params, path, false).toSourcePosition
       val mkEdit =
         path match
           // if we are in import section just specify full name
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala
index ad571ff843c3..6d89cb663b9c 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala
@@ -22,7 +22,8 @@ case class CompletionPos(
   identEnd: Int,
   query: String,
   originalCursorPosition: SourcePosition,
-  sourceUri: URI
+  sourceUri: URI,
+  withCURSOR: Boolean
 ):
   def queryEnd: Int = originalCursorPosition.point
   def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd))
@@ -34,17 +35,19 @@ object CompletionPos:
   def infer(
       sourcePos: SourcePosition,
       offsetParams: OffsetParams,
-      adjustedPath: List[Tree]
+      adjustedPath: List[Tree],
+      wasCursorApplied: Boolean
   )(using Context): CompletionPos =
     val identEnd = adjustedPath match
       case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) =>
         refTree.span.end - Cursor.value.length
+      case (refTree: RefTree) :: _  => refTree.span.end
       case _ => sourcePos.end
 
     val query = Completion.completionPrefix(adjustedPath, sourcePos)
     val start = sourcePos.end - query.length()
 
-    CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn)
+    CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied)
 
   /**
    * Infer the indentation by counting the number of spaces in the given line.
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
index 4d45595dac8d..a04cd82d10b3 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
@@ -14,9 +14,12 @@ import dotty.tools.dotc.ast.tpd.*
 import dotty.tools.dotc.core.Constants.Constant
 import dotty.tools.dotc.core.Contexts.Context
 import dotty.tools.dotc.core.Phases
-import dotty.tools.dotc.core.StdNames
+import dotty.tools.dotc.core.StdNames.nme
+import dotty.tools.dotc.core.Flags
 import dotty.tools.dotc.interactive.Interactive
+import dotty.tools.dotc.interactive.Completion
 import dotty.tools.dotc.interactive.InteractiveDriver
+import dotty.tools.dotc.parsing.Tokens
 import dotty.tools.dotc.util.SourceFile
 import dotty.tools.pc.AutoImports.AutoImportEdits
 import dotty.tools.pc.AutoImports.AutoImportsGenerator
@@ -47,23 +50,31 @@ class CompletionProvider(
     val uri = params.uri().nn
     val text = params.text().nn
 
-    val code = applyCompletionCursor(params)
+    val (wasCursorApplied, code) = applyCompletionCursor(params)
     val sourceFile = SourceFile.virtual(uri, code)
     driver.run(uri, sourceFile)
 
-    val ctx = driver.currentCtx
+    given ctx: Context = driver.currentCtx
     val pos = driver.sourcePosition(params)
     val (items, isIncomplete) = driver.compilationUnits.get(uri) match
       case Some(unit) =>
-
         val newctx = ctx.fresh.setCompilationUnit(unit).withPhase(Phases.typerPhase(using ctx))
-        val tpdPath = Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
-        val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)(using newctx)
+        val tpdPath0 = Interactive.pathTo(unit.tpdTree, pos.span)(using newctx)
+        val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath0, pos)(using newctx)
+
+        val tpdPath = tpdPath0 match
+          // $1$ // FIXME add check for a $1$ name to make sure we only do the below in lifting case
+          case Select(qual, name) :: tail if qual.symbol.is(Flags.Synthetic) =>
+            qual.symbol.defTree match
+              case valdef: ValDef => Select(valdef.rhs, name) :: tail
+              case _ => tpdPath0
+          case _ => tpdPath0
+
 
         val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx)
         val indexedCtx = IndexedContext(locatedCtx)
 
-        val completionPos = CompletionPos.infer(pos, params, adjustedPath)(using locatedCtx)
+        val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx)
 
         val autoImportsGen = AutoImports.generator(
           completionPos.toSourcePosition,
@@ -114,6 +125,10 @@ class CompletionProvider(
     )
   end completions
 
+  val allKeywords =
+    val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
+    Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)
+
   /**
    * In case if completion comes from empty line like:
    * {{{
@@ -126,23 +141,30 @@ class CompletionProvider(
    * Otherwise, completion poisition doesn't point at any tree
    * because scala parser trim end position to the last statement pos.
    */
-  private def applyCompletionCursor(params: OffsetParams): String =
+  private def applyCompletionCursor(params: OffsetParams): (Boolean, String) =
     val text = params.text().nn
     val offset = params.offset().nn
+    val query = Completion.naiveCompletionPrefix(text, offset)
 
-    val isStartMultilineComment =
-      val i = params.offset()
-      i >= 3 && (text.charAt(i - 1) match
-        case '*' =>
-          text.charAt(i - 2) == '*' &&
-          text.charAt(i - 3) == '/'
-        case _ => false
-      )
-    if isStartMultilineComment then
-      // Insert potentially missing `*/` to avoid comment out all codes after the "/**".
-      text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
+    if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart && !allKeywords.contains(query) then
+      false -> text
     else
-      text.substring(0, offset).nn + Cursor.value + text.substring(offset)
+      val isStartMultilineComment =
+
+        val i = params.offset()
+        i >= 3 && (text.charAt(i - 1) match
+          case '*' =>
+            text.charAt(i - 2) == '*' &&
+            text.charAt(i - 3) == '/'
+          case _ => false
+        )
+      true -> (
+        if isStartMultilineComment then
+          // Insert potentially missing `*/` to avoid comment out all codes after the "/**".
+          text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
+        else
+          text.substring(0, offset).nn + Cursor.value + text.substring(offset)
+      )
   end applyCompletionCursor
 
   private def completionItems(
@@ -175,7 +197,7 @@ class CompletionProvider(
               Select(Apply(Select(Select(_, name), _), _), _),
               _
             ) :: _ =>
-          name == StdNames.nme.StringContext
+          name == nme.StringContext
         // "My name is $name"
         case Literal(Constant(_: String)) :: _ =>
           true
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
index 3bebaa76a309..f691939772c6 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
@@ -67,23 +67,19 @@ class Completions(
       case _ :: (_: UnApply) :: _ => false
       case _ => true
 
-  private lazy val shouldAddSuffix = shouldAddSnippet && 
+  private lazy val shouldAddSuffix = shouldAddSnippet &&
     (path match
       /* In case of `method@@()` we should not add snippets and the path
        * will contain apply as the parent of the current tree.
        */
-      case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun =>
-        false
+      case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => false
       /* In case of `T@@[]` we should not add snippets.
        */
-      case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe =>
-        false
-      case _ :: (withcursor @ Select(fun, name)) :: (appl: GenericApply) :: _
-          if appl.fun == withcursor && name.decoded == Cursor.value =>
-        false
+      case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false
+      case sel  :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _
+        if appl.fun == funSel && sel == fun => false
       case _ => true)
 
-
   private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath)
 
   def includeSymbol(sym: Symbol)(using Context): Boolean =
@@ -521,14 +517,8 @@ class Completions(
           if tree.selectors.exists(_.renamed.sourcePos.contains(pos)) =>
         (List.empty, true)
 
-      // From Scala 3.1.3-RC3 (as far as I know), path contains
-      // `Literal(Constant(null))` on head for an incomplete program, in this case, just ignore the head.
-      case Literal(Constant(null)) :: tl =>
-        advancedCompletions(tl, completionPos)
-
       case _ =>
         val args = NamedArgCompletions.contribute(
-          pos,
           path,
           adjustedPath,
           indexedContext,
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala
index 2e39c17b24b3..da46e5167834 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala
@@ -224,7 +224,7 @@ object InterpolatorCompletions:
       buildTargetIdentifier: String
   )(using ctx: Context, reportsContext: ReportContext): List[CompletionValue] =
     val litStartPos = lit.span.start
-    val litEndPos = lit.span.end - Cursor.value.length()
+    val litEndPos = lit.span.end - (if completionPos.withCURSOR then Cursor.value.length else 0)
     val position = completionPos.originalCursorPosition
     val span = position.span
     val nameStart =
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
index 647b151a635b..11b0cd660f42 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
@@ -35,9 +35,8 @@ import scala.annotation.tailrec
 object NamedArgCompletions:
 
   def contribute(
-      pos: SourcePosition,
       path: List[Tree],
-      untypedPath: => List[untpd.Tree],
+      untypedPath: List[untpd.Tree],
       indexedContext: IndexedContext,
       clientSupportsSnippets: Boolean,
   )(using ctx: Context): List[CompletionValue] =
@@ -64,12 +63,13 @@ object NamedArgCompletions:
           for
             app <- getApplyForContextFunctionParam(rest)
             if !app.fun.isInfix
-          yield contribute(
-            Some(ident),
-            app,
-            indexedContext,
-            clientSupportsSnippets,
-          )
+          yield
+            contribute(
+              Some(ident),
+              app,
+              indexedContext,
+              clientSupportsSnippets,
+            )
         contribution.getOrElse(Nil)
       case (app: Apply) :: _ =>
         /**
@@ -156,10 +156,11 @@ object NamedArgCompletions:
           case _ => None
       val matchingMethods =
         for
-          (name, indxContext) <- maybeNameAndIndexedContext(method)
-          potentialMatches <- indxContext.findSymbol(name)
-        yield potentialMatches.collect {
-          case m
+          (name, indexedContext) <- maybeNameAndIndexedContext(method)
+          potentialMatches <- indexedContext.findSymbol(name)
+        yield
+          potentialMatches.collect {
+            case m
               if m.is(Flags.Method) &&
                 m.vparamss.length >= argss.length &&
                 Try(m.isAccessibleFrom(apply.symbol.info)).toOption
@@ -179,8 +180,7 @@ object NamedArgCompletions:
     end fallbackFindMatchingMethods
 
     val matchingMethods: List[Symbols.Symbol] =
-      if method.symbol.paramSymss.nonEmpty
-      then
+      if method.symbol.paramSymss.nonEmpty then
         val allArgsAreSupplied =
           val vparamss = method.symbol.vparamss
           vparamss.length == argss.length && vparamss
@@ -295,6 +295,7 @@ object NamedArgCompletions:
       )
     }
 
+    // FIXME pass query here
     val prefix = ident
       .map(_.name.toString)
       .getOrElse("")
@@ -391,7 +392,7 @@ class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context):
     (expectedArgs.length == actualArgs.length ||
       (!allArgsProvided && expectedArgs.length >= actualArgs.length)) &&
       actualArgs.zipWithIndex.forall {
-        case (Ident(name), _) if name.endsWith(Cursor.value) => true
+        case (Ident(name), _) => true
         case (NamedArg(name, arg), _) =>
           expectedArgs.exists { expected =>
             expected.name == name && (!arg.hasType || arg.typeOpt.unfold
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
index 1e310ca0e8ec..28dc4ebe59c9 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
@@ -582,7 +582,7 @@ object OverrideCompletions:
             )
           )
         // class Main extends Val:
-        //    he@@
+        //   he@@
         case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
             if t.parents.nonEmpty =>
           Some(
@@ -595,6 +595,20 @@ object OverrideCompletions:
             )
           )
 
+        // class Main extends Val:
+        //   hello@ // this transforms into this.hello, thus is a Select
+        case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _
+            if t.parents.nonEmpty && th.qual.name == td.name =>
+          Some(
+            (
+              td,
+              None,
+              sel.sourcePos.start,
+              false,
+              Some(name.show),
+            )
+          )
+
         case _ => None
 
   end OverrideExtractor
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala
index 210a28f6a7a1..17f21b16d6e8 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionArgSuite.scala
@@ -614,8 +614,9 @@ class CompletionArgSuite extends BaseCompletionSuite:
     check(
       s"""|case class Context()
           |
-          |def foo(arg1: (Context) ?=> Int, arg2: Int): String = ???
-          |val m = foo(ar@@)
+          |object Main:
+          |  def foo(arg1: (Context) ?=> Int, arg2: Int): String = ???
+          |  val m = foo(ar@@)
           |""".stripMargin,
       """|arg1 = : (Context) ?=> Int
          |arg2 = : Int
@@ -627,8 +628,9 @@ class CompletionArgSuite extends BaseCompletionSuite:
     check(
       s"""|case class Context()
           |
-          |def foo(arg1: Context ?=> Int, arg2: Context ?=> Int): String = ???
-          |val m = foo(arg1 = ???, a@@)
+          |object Main:
+          |  def foo(arg1: Context ?=> Int, arg2: Context ?=> Int): String = ???
+          |  val m = foo(arg1 = ???, a@@)
           |""".stripMargin,
       """|arg2 = : (Context) ?=> Int
          |""".stripMargin,
@@ -639,8 +641,9 @@ class CompletionArgSuite extends BaseCompletionSuite:
     check(
       s"""|case class Context()
           |
-          |def foo(arg1: (Boolean, Context) ?=> Int ?=> String, arg2: (Boolean, Context) ?=> Int ?=> String): String = ???
-          |val m = foo(arg1 = ???, a@@)
+          |object Main:
+          |  def foo(arg1: (Boolean, Context) ?=> Int ?=> String, arg2: (Boolean, Context) ?=> Int ?=> String): String = ???
+          |  val m = foo(arg1 = ???, a@@)
           |""".stripMargin,
       """|arg2 = : (Boolean, Context) ?=> (Int) ?=> String
          |""".stripMargin,
@@ -786,10 +789,11 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-with-param` =
     check(
-      """|def m(idd : String, abb: Int): Int = ???
-         |def m(inn : Int, uuu: Option[Int]): Int = ???
-         |def m(inn : Int, aaa: Int): Int = ???
-         |def k: Int = m(1, a@@)
+      """|object Main:
+         |  def m(idd : String, abb: Int): Int = ???
+         |  def m(inn : Int, uuu: Option[Int]): Int = ???
+         |  def m(inn : Int, aaa: Int): Int = ???
+         |  def k: Int = m(1, a@@)
          |""".stripMargin,
       """|aaa = : Int
          |assert(assertion: Boolean): Unit
@@ -799,10 +803,11 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-with-named-param` =
     check(
-      """|def m(idd : String, abb: Int): Int = ???
-         |def m(inn : Int, uuu: Option[Int]): Int = ???
-         |def m(inn : Int, aaa: Int): Int = ???
-         |def k: Int = m(inn = 1, a@@)
+      """|object Main:
+         |  def m(idd : String, abb: Int): Int = ???
+         |  def m(inn : Int, uuu: Option[Int]): Int = ???
+         |  def m(inn : Int, aaa: Int): Int = ???
+         |  def k: Int = m(inn = 1, a@@)
          |""".stripMargin,
       """|aaa = : Int
          |assert(assertion: Boolean): Unit
@@ -812,7 +817,7 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-generic` =
     check(
-      """|object M:
+      """|object Main:
          |  val g = 3
          |  val l : List[Int] = List(1,2,3)
          |  def m[T](inn : List[T], yy: Int, aaa: Int, abb: Option[Int]): Int = ???
@@ -899,10 +904,11 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-function-param` =
     check(
-      """|def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ???
-         |def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ???
-         |def m[T](i: Int)(inn: T => String, acc: List[Int]): Int = ???
-         |def k = m(1)(inn = identity[Int], a@@)
+      """|object Main:
+         |  def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ???
+         |  def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ???
+         |  def m[T](i: Int)(inn: T => String, acc: List[Int]): Int = ???
+         |  def k = m(1)(inn = identity[Int], a@@)
          |""".stripMargin,
       """|aaa = : Int
          |abb = : Option[Int]
@@ -913,10 +919,11 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-function-param2` =
     check(
-      """|def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ???
-         |def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ???
-         |def m[T](i: String)(inn: T => Int, acc: List[Int]): Int = ???
-         |def k = m(1)(inn = identity[Int], a@@)
+      """|object Main:
+         |  def m[T](i: Int)(inn: T => Int, abb: Option[Int]): Int = ???
+         |  def m[T](i: Int)(inn: T => Int, aaa: Int): Int = ???
+         |  def m[T](i: String)(inn: T => Int, acc: List[Int]): Int = ???
+         |  def k = m(1)(inn = identity[Int], a@@)
          |""".stripMargin,
       """|aaa = : Int
          |abb = : Option[Int]
@@ -978,9 +985,10 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `overloaded-function-param3` =
     check(
-      """|def m[T](inn: Int => T, abb: Option[Int]): Int = ???
-         |def m[T](inn: String => T, aaa: Int): Int = ???
-         |def k = m(identity[Int], a@@)
+      """|object Main:
+         |  def m[T](inn: Int => T, abb: Option[Int]): Int = ???
+         |  def m[T](inn: String => T, aaa: Int): Int = ???
+         |  def k = m(identity[Int], a@@)
          |""".stripMargin,
       """|abb = : Option[Int]
          |""".stripMargin,
@@ -1109,7 +1117,7 @@ class CompletionArgSuite extends BaseCompletionSuite:
 
   @Test def `comparison` =
     check(
-      """package a
+      """
         |object w {
         |  abstract class T(x: Int) {
         |    def met(x: Int): Unit = {
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala
index 08cc1535fd56..50019928a2f3 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala
@@ -112,7 +112,7 @@ class CompletionInterpolatorSuite extends BaseCompletionSuite:
          |""".stripMargin.triplequoted,
       """|object Main {
          |  val myName = ""
-         |  s"$myName $$"
+         |  s"$myName$0 $$"
          |}
          |""".stripMargin.triplequoted,
       filterText = "myName"
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala
index 47e4cabb76f4..1cd26858b934 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala
@@ -530,8 +530,6 @@ class CompletionSuite extends BaseCompletionSuite:
       """.stripMargin,
       """|until(end: Int): Range
          |until(end: Int, step: Int): Range
-         |until(end: Long): Exclusive[Long]
-         |until(end: Long, step: Long): Exclusive[Long]
          |""".stripMargin,
       stableOrder = false
     )
@@ -1606,7 +1604,7 @@ class CompletionSuite extends BaseCompletionSuite:
 
   @Test def `multi-export` =
     check(
-      """export scala.collection.{AbstractMap, Set@@}
+      """export scala.collection.{AbstractMap, Se@@}
         |""".stripMargin,
       """Set scala.collection
         |SetOps scala.collection
@@ -1619,7 +1617,9 @@ class CompletionSuite extends BaseCompletionSuite:
         |StrictOptimizedSetOps scala.collection
         |StrictOptimizedSortedSetOps scala.collection
         |GenSet = scala.collection.Set[X]
-        |""".stripMargin
+        |""".stripMargin,
+      filter = _.contains("Set")
+
     )
 
   @Test def `multi-imports` =
@@ -1638,6 +1638,7 @@ class CompletionSuite extends BaseCompletionSuite:
         |StrictOptimizedSortedSetOps scala.collection
         |GenSet = scala.collection.Set[X]
         |""".stripMargin,
+      filter = _.contains("Set")
     )
 
 
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
index c7c9b9979404..20d56ab94938 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
@@ -28,7 +28,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
       MockLocation("scala/Predef.Ensuring#ensuring(+2).", "Predef.scala"),
       MockLocation("scala/Predef.Ensuring#ensuring(+3).", "Predef.scala"),
       MockLocation("scala/collection/immutable/List#`::`().", "List.scala"),
-      MockLocation("scala/collection/IterableFactory#apply().", "Factory.scala")
+      MockLocation("scala/package.List.", "List.scala")
     )
 
   override def definitions(offsetParams: OffsetParams): List[Location] =
@@ -123,7 +123,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
     check(
       """|
          |object Main {
-         |  /*scala/collection/IterableFactory#apply(). Factory.scala*/@@List(1)
+         |  /*scala/package.List. List.scala*/@@List(1)
          |}
          |""".stripMargin
     )
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
index 9ae37048caf7..0b992fe98f08 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
@@ -269,9 +269,9 @@ class HoverTermSuite extends BaseHoverSuite:
         |  } yield x
         |}
         |""".stripMargin,
-      """|Option[Int]
-         |override def headOption: Option[A]
-         |""".stripMargin.hover
+      """|```scala
+         |override def headOption: Option[Int]
+         |```""".stripMargin.hover
     )
 
   @Test def `object` =

From 7205f20f1dc3f8712a925a780a244f7448cf8226 Mon Sep 17 00:00:00 2001
From: rochala <rochala@tutanota.com>
Date: Thu, 4 Jul 2024 21:10:23 +0200
Subject: [PATCH 2/4] Add tests, don't cache when CURSOR is added

---
 .../dotty/tools/dotc/ast/NavigateAST.scala    |  37 ++--
 .../dotty/tools/dotc/parsing/Parsers.scala    |   2 +-
 .../tools/languageserver/CompletionTest.scala |   9 -
 .../tools/languageserver/HoverTest.scala      |   2 +-
 .../dotty/tools/pc/AutoImportsProvider.scala  |   1 -
 ...MetalsDriver.scala => CachingDriver.scala} |  10 +-
 .../dotty/tools/pc/PcInlayHintsProvider.scala |   1 -
 .../dotty/tools/pc/Scala3CompilerAccess.scala |   3 +-
 .../tools/pc/Scala3CompilerWrapper.scala      |   7 +-
 .../tools/pc/ScalaPresentationCompiler.scala  |  34 ++--
 .../tools/pc/ScriptFirstImportPosition.scala  |   1 -
 .../tools/pc/SignatureHelpProvider.scala      |   1 -
 .../tools/pc/SymbolInformationProvider.scala  |   3 +-
 .../pc/completions/CompletionProvider.scala   |  25 ++-
 .../tools/pc/completions/Completions.scala    |   7 +-
 .../pc/completions/NamedArgCompletions.scala  |   2 -
 .../pc/printer/ShortenedTypePrinter.scala     |   2 -
 .../tools/pc/base/BaseInlayHintsSuite.scala   |   4 +-
 .../tools/pc/base/ReusableClassRunner.scala   |   9 +-
 .../tools/pc/tests/CompilerCachingSuite.scala | 166 ++++++++++++++++++
 .../CompletionExtraConstructorSuite.scala     |   4 -
 .../tests/definition/PcDefinitionSuite.scala  |   4 +-
 .../tools/pc/tests/hover/HoverTermSuite.scala |   8 +
 .../tools/pc/utils/DefSymbolCollector.scala   |   2 +-
 .../tools/pc/utils/MockSymbolSearch.scala     |   1 -
 .../dotty/tools/pc/utils/PcAssertions.scala   |   2 -
 .../dotty/tools/pc/utils/TestInlayHints.scala |   3 +-
 27 files changed, 256 insertions(+), 94 deletions(-)
 rename presentation-compiler/src/main/dotty/tools/pc/{MetalsDriver.scala => CachingDriver.scala} (88%)
 create mode 100644 presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala

diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
index 2aeb2f7df067..429e0868667c 100644
--- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
+++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
@@ -75,24 +75,41 @@ object NavigateAST {
   def pathTo(span: Span, from: List[Positioned], skipZeroExtent: Boolean = false)(using Context): List[Positioned] = {
     def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = {
       var bestFit: List[Positioned] = path
-      while (it.hasNext) {
-        val path1 = it.next() match {
-          // FIXME this has to be changed to deterministicaly find recoveed tree
-          case untpd.Select(qual, name) if name == StdNames.nme.??? => path
+      while (it.hasNext) do
+        val path1 = it.next() match
+          case sel: untpd.Select if isTreeFromRecovery(sel) => path
           case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path)
           case m: untpd.Modifiers => childPath(m.productIterator, path)
           case xs: List[?] => childPath(xs.iterator, path)
           case _ => path
-        }
-        if ((path1 ne path) &&
-            ((bestFit eq path) ||
-             bestFit.head.span != path1.head.span &&
-             envelops(bestFit.head.span, path1.head.span)))
+
+        if (path1 ne path) && ((bestFit eq path) || isBetterFit(bestFit, path1)) then
           bestFit = path1
-      }
+
       bestFit
     }
 
+    /**
+      * When choosing better fit we compare spans. If candidate span has starting or ending point inside (exclusive)
+      * current best fit it is selected as new best fit. This means that same spans are failing the first predicate.
+      *
+      * In case when spans start and end at same offsets we prefer non synthethic one.
+      */
+    def isBetterFit(currentBest: List[Positioned], candidate: List[Positioned]): Boolean =
+      if currentBest.isEmpty && candidate.nonEmpty then true
+      else if currentBest.nonEmpty && candidate.nonEmpty then
+        val bestSpan= currentBest.head.span
+        val candidateSpan = candidate.head.span
+
+        bestSpan != candidateSpan &&
+          envelops(bestSpan, candidateSpan) ||
+          bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic
+      else false
+
+
+    def isTreeFromRecovery(p: untpd.Select): Boolean =
+      p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic
+
     def envelops(a: Span, b: Span): Boolean =
       !b.exists || a.exists && (
         (a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end)
diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
index 39bdc69111d3..8a173faa3cec 100644
--- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
+++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala
@@ -406,7 +406,7 @@ object Parsers {
       false
     }
 
-    def errorTermTree(start: Offset): Tree = atSpan(start, in.offset, in.offset) { unimplementedExpr }
+    def errorTermTree(start: Offset): Tree = atSpan(Span(start, in.offset)) { unimplementedExpr }
 
     private var inFunReturnType = false
     private def fromWithinReturnType[T](body: => T): T = {
diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala
index 043788dbd0ac..8034b4c8d40b 100644
--- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala
+++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala
@@ -1706,15 +1706,6 @@ class CompletionTest {
        ("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
      ))
 
-  @Test def testtest: Unit =
-    code"""|object M {
-           |  def sel$m1
-           |}
-           |"""
-     .completion(m1, Set(
-       ("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
-     ))
-
   @Test def noEnumCompletionInNewContext: Unit =
     code"""|enum TestEnum:
            |  case TestCase
diff --git a/language-server/test/dotty/tools/languageserver/HoverTest.scala b/language-server/test/dotty/tools/languageserver/HoverTest.scala
index a2196f4a71f3..91f72e222432 100644
--- a/language-server/test/dotty/tools/languageserver/HoverTest.scala
+++ b/language-server/test/dotty/tools/languageserver/HoverTest.scala
@@ -227,7 +227,7 @@ class HoverTest {
   @Test def enums: Unit = {
     code"""|package example
            |enum TestEnum3:
-           | case ${m1}A${m2} // no tooltip
+           |  case ${m1}A${m2} // no tooltip
            |
            |"""
       .hover(m1 to m2, hoverContent("example.TestEnum3"))
diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
index 0252786c20f6..3d4864c73508 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
@@ -13,7 +13,6 @@ import dotty.tools.dotc.core.Symbols.*
 import dotty.tools.dotc.interactive.Interactive
 import dotty.tools.dotc.interactive.InteractiveDriver
 import dotty.tools.dotc.util.SourceFile
-import dotty.tools.pc.AutoImports.*
 import dotty.tools.pc.completions.CompletionPos
 import dotty.tools.pc.utils.InteractiveEnrichments.*
 
diff --git a/presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala b/presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala
similarity index 88%
rename from presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala
rename to presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala
index 819c3f2fc9c9..f5715c2780a9 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala
@@ -10,8 +10,8 @@ import dotty.tools.dotc.util.SourceFile
 import scala.compiletime.uninitialized
 
 /**
- * MetalsDriver is a wrapper class that provides a compilation cache for InteractiveDriver.
- * MetalsDriver skips running compilation if
+ * CachingDriver is a wrapper class that provides a compilation cache for InteractiveDriver.
+ * CachingDriver skips running compilation if
  * - the target URI of `run` is the same as the previous target URI
  * - the content didn't change since the last compilation.
  *
@@ -27,9 +27,7 @@ import scala.compiletime.uninitialized
  * To avoid the complexity related to currentCtx,
  * we decided to cache only when the target URI only if the same as the previous run.
  */
-class MetalsDriver(
-    override val settings: List[String]
-) extends InteractiveDriver(settings):
+class CachingDriver(override val settings: List[String]) extends InteractiveDriver(settings):
 
   @volatile private var lastCompiledURI: URI = uninitialized
 
@@ -55,4 +53,4 @@ class MetalsDriver(
     lastCompiledURI = uri
     diags
 
-end MetalsDriver
+end CachingDriver
diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala
index b3f836801460..9c0e6bcfa9d8 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala
@@ -14,7 +14,6 @@ import scala.meta.internal.pc.LabelPart.*
 import scala.meta.pc.InlayHintsParams
 import scala.meta.pc.SymbolSearch
 
-import dotty.tools.dotc.ast.tpd
 import dotty.tools.dotc.ast.tpd.*
 import dotty.tools.dotc.core.Contexts.Context
 import dotty.tools.dotc.core.Flags
diff --git a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala
index ef5aaf4e5ed0..1443fbcf37cc 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala
@@ -8,13 +8,14 @@ import scala.meta.internal.pc.CompilerAccess
 import scala.meta.pc.PresentationCompilerConfig
 
 import dotty.tools.dotc.reporting.StoreReporter
+import dotty.tools.dotc.interactive.InteractiveDriver
 
 class Scala3CompilerAccess(
     config: PresentationCompilerConfig,
     sh: Option[ScheduledExecutorService],
     newCompiler: () => Scala3CompilerWrapper
 )(using ec: ExecutionContextExecutor, rc: ReportContext)
-    extends CompilerAccess[StoreReporter, MetalsDriver](
+    extends CompilerAccess[StoreReporter, InteractiveDriver](
       config,
       sh,
       newCompiler,
diff --git a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala
index de4fb282edc9..968c144625a3 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala
@@ -4,11 +4,12 @@ import scala.meta.internal.pc.CompilerWrapper
 import scala.meta.internal.pc.ReporterAccess
 
 import dotty.tools.dotc.reporting.StoreReporter
+import dotty.tools.dotc.interactive.InteractiveDriver
 
-class Scala3CompilerWrapper(driver: MetalsDriver)
-    extends CompilerWrapper[StoreReporter, MetalsDriver]:
+class Scala3CompilerWrapper(driver: InteractiveDriver)
+    extends CompilerWrapper[StoreReporter, InteractiveDriver]:
 
-  override def compiler(): MetalsDriver = driver
+  override def compiler(): InteractiveDriver = driver
 
   override def resetReporter(): Unit =
     val ctx = driver.currentCtx
diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala
index 85de8e7d8439..e6da8b79164f 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala
@@ -33,11 +33,13 @@ import dotty.tools.pc.completions.CompletionProvider
 import dotty.tools.pc.InferExpectedType
 import dotty.tools.pc.completions.OverrideCompletions
 import dotty.tools.pc.buildinfo.BuildInfo
+import dotty.tools.pc.SymbolInformationProvider
+import dotty.tools.dotc.interactive.InteractiveDriver
 
 import org.eclipse.lsp4j.DocumentHighlight
 import org.eclipse.lsp4j.TextEdit
 import org.eclipse.lsp4j as l
-import dotty.tools.pc.SymbolInformationProvider
+
 
 case class ScalaPresentationCompiler(
     buildTargetIdentifier: String = "",
@@ -76,14 +78,20 @@ case class ScalaPresentationCompiler(
   override def withReportsLoggerLevel(level: String): PresentationCompiler =
     copy(reportsLevel = ReportLevel.fromString(level))
 
-  val compilerAccess: CompilerAccess[StoreReporter, MetalsDriver] =
+  val compilerAccess: CompilerAccess[StoreReporter, InteractiveDriver] =
     Scala3CompilerAccess(
       config,
       sh,
-      () => new Scala3CompilerWrapper(newDriver)
-    )(using
-      ec
-    )
+      () => new Scala3CompilerWrapper(CachingDriver(driverSettings))
+    )(using ec)
+
+  val driverSettings =
+    val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0")
+    val defaultFlags = List("-color:never")
+    val filteredOptions = removeDoubleOptions(options.filterNot(forbiddenOptions))
+
+    filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath
+      .mkString(File.pathSeparator) :: Nil
 
   private def removeDoubleOptions(options: List[String]): List[String] =
     options match
@@ -92,19 +100,6 @@ case class ScalaPresentationCompiler(
       case head :: tail => head :: removeDoubleOptions(tail)
       case Nil => options
 
-  def newDriver: MetalsDriver =
-    val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0")
-    val defaultFlags = List("-color:never")
-    val filteredOptions = removeDoubleOptions(
-      options.filterNot(forbiddenOptions)
-    )
-    val settings =
-      filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath
-        .mkString(
-          File.pathSeparator
-        ) :: Nil
-    new MetalsDriver(settings)
-
   override def semanticTokens(
       params: VirtualFileParams
   ): CompletableFuture[ju.List[Node]] =
@@ -146,6 +141,7 @@ case class ScalaPresentationCompiler(
       new CompletionProvider(
         search,
         driver,
+        () => InteractiveDriver(driverSettings),
         params,
         config,
         buildTargetIdentifier,
diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala b/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala
index 2bb8023cee08..5a4c135fdc4c 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala
@@ -1,6 +1,5 @@
 package dotty.tools.pc
 
-import dotty.tools.dotc.ast.tpd.*
 import dotty.tools.dotc.core.Comments.Comment
 
 object ScriptFirstImportPosition:
diff --git a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala
index edfd9c95fa84..bd16d2ce2aa9 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala
@@ -1,6 +1,5 @@
 package dotty.tools.pc
 
-import dotty.tools.dotc.ast.tpd.*
 import dotty.tools.dotc.core.Contexts.*
 import dotty.tools.dotc.core.Flags
 import dotty.tools.dotc.core.Symbols.*
diff --git a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala
index da075e21f486..ccda618078b8 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala
@@ -7,8 +7,6 @@ import scala.meta.pc.PcSymbolKind
 import scala.meta.pc.PcSymbolProperty
 
 import dotty.tools.dotc.core.Contexts.Context
-import dotty.tools.dotc.core.Denotations.Denotation
-import dotty.tools.dotc.core.Denotations.MultiDenotation
 import dotty.tools.dotc.core.Flags
 import dotty.tools.dotc.core.Names.*
 import dotty.tools.dotc.core.StdNames.nme
@@ -19,6 +17,7 @@ import dotty.tools.pc.utils.InteractiveEnrichments.allSymbols
 import dotty.tools.pc.utils.InteractiveEnrichments.stripBackticks
 import scala.meta.internal.pc.PcSymbolInformation
 import scala.meta.internal.pc.SymbolInfo
+import dotty.tools.dotc.core.Denotations.{Denotation, MultiDenotation}
 
 class SymbolInformationProvider(using Context):
 
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
index a04cd82d10b3..78bf15614769 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
@@ -16,6 +16,7 @@ import dotty.tools.dotc.core.Contexts.Context
 import dotty.tools.dotc.core.Phases
 import dotty.tools.dotc.core.StdNames.nme
 import dotty.tools.dotc.core.Flags
+import dotty.tools.dotc.core.Names.DerivedName
 import dotty.tools.dotc.interactive.Interactive
 import dotty.tools.dotc.interactive.Completion
 import dotty.tools.dotc.interactive.InteractiveDriver
@@ -39,7 +40,8 @@ import scala.meta.pc.CompletionItemPriority
 
 class CompletionProvider(
     search: SymbolSearch,
-    driver: InteractiveDriver,
+    cachingDriver: InteractiveDriver,
+    freshDriver: () => InteractiveDriver,
     params: OffsetParams,
     config: PresentationCompilerConfig,
     buildTargetIdentifier: String,
@@ -52,6 +54,16 @@ class CompletionProvider(
 
     val (wasCursorApplied, code) = applyCompletionCursor(params)
     val sourceFile = SourceFile.virtual(uri, code)
+
+    /** Creating a new fresh driver is way slower than reusing existing one,
+     *  but runnig a compilation has side effects that modifies the state of the driver.
+     *  We don't want to affect cachingDriver state with compilation including "CURSOR" suffix.
+     *
+     *  We could in theory save this fresh driver for reuse, but it is a choice between extra memory usage and speed.
+     *  The scenario in which "CURSOR" is applied (empty query or query equal to any keyword) has a slim chance of happening.
+     */
+
+    val driver = if wasCursorApplied then freshDriver() else cachingDriver
     driver.run(uri, sourceFile)
 
     given ctx: Context = driver.currentCtx
@@ -63,11 +75,12 @@ class CompletionProvider(
         val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath0, pos)(using newctx)
 
         val tpdPath = tpdPath0 match
-          // $1$ // FIXME add check for a $1$ name to make sure we only do the below in lifting case
-          case Select(qual, name) :: tail if qual.symbol.is(Flags.Synthetic) =>
-            qual.symbol.defTree match
-              case valdef: ValDef => Select(valdef.rhs, name) :: tail
-              case _ => tpdPath0
+          case Select(qual, name) :: tail
+            // If for any reason we end up in param after lifting, we want to inline the synthetic val
+            if qual.symbol.is(Flags.Synthetic) && qual.symbol.name.isInstanceOf[DerivedName] =>
+              qual.symbol.defTree match
+                case valdef: ValDef => Select(valdef.rhs, name) :: tail
+                case _ => tpdPath0
           case _ => tpdPath0
 
 
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
index f691939772c6..05dbe1ef5a43 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala
@@ -5,7 +5,6 @@ import java.nio.file.Path
 import java.nio.file.Paths
 
 import scala.collection.mutable
-import scala.meta.internal.metals.Fuzzy
 import scala.meta.internal.metals.ReportContext
 import scala.meta.internal.mtags.CoursierComplete
 import scala.meta.internal.pc.{IdentifierComparator, MemberOrdering, CompletionFuzzy}
@@ -27,15 +26,12 @@ import dotty.tools.dotc.core.Symbols.*
 import dotty.tools.dotc.core.Types.*
 import dotty.tools.dotc.interactive.Completion
 import dotty.tools.dotc.interactive.Completion.Mode
-import dotty.tools.dotc.interactive.Interactive
 import dotty.tools.dotc.util.SourcePosition
 import dotty.tools.dotc.util.SrcPos
 import dotty.tools.pc.AutoImports.AutoImportsGenerator
 import dotty.tools.pc.buildinfo.BuildInfo
 import dotty.tools.pc.completions.OverrideCompletions.OverrideExtractor
 import dotty.tools.pc.utils.InteractiveEnrichments.*
-import dotty.tools.dotc.core.Denotations.SingleDenotation
-import dotty.tools.dotc.interactive.Interactive
 
 class Completions(
     text: String,
@@ -279,7 +275,6 @@ class Completions(
         val affix = if methodDenot.symbol.isConstructor && existsApply then
           adjustedPath match
             case (select @ Select(qual, _)) :: _ =>
-              val start = qual.span.start
               val insertRange = select.sourcePos.startPos.withEnd(completionPos.queryEnd).toLsp
 
               suffix
@@ -662,7 +657,7 @@ class Completions(
       .collect { case symbolic: CompletionValue.Symbolic => symbolic }
       .groupBy(_.symbol.fullName) // we somehow have to ignore proxy type
 
-    val filteredSymbolicCompletions = symbolicCompletionsMap.filter: (name, denots) =>
+    val filteredSymbolicCompletions = symbolicCompletionsMap.filter: (name, _) =>
       lazy val existsTypeWithoutSuffix: Boolean = !symbolicCompletionsMap
         .get(name.toTypeName)
         .forall(_.forall(sym => sym.snippetAffix.suffixes.nonEmpty))
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
index 11b0cd660f42..8cf66eee5aba 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
@@ -27,7 +27,6 @@ import dotty.tools.dotc.core.Types.TermRef
 import dotty.tools.dotc.core.Types.Type
 import dotty.tools.dotc.core.Types.TypeBounds
 import dotty.tools.dotc.core.Types.WildcardType
-import dotty.tools.dotc.util.SourcePosition
 import dotty.tools.pc.IndexedContext
 import dotty.tools.pc.utils.InteractiveEnrichments.*
 import scala.annotation.tailrec
@@ -295,7 +294,6 @@ object NamedArgCompletions:
       )
     }
 
-    // FIXME pass query here
     val prefix = ident
       .map(_.name.toString)
       .getOrElse("")
diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala
index a0dcb5276253..a738440c585d 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala
@@ -24,8 +24,6 @@ import dotty.tools.dotc.printing.RefinedPrinter
 import dotty.tools.dotc.printing.Texts.Text
 import dotty.tools.pc.AutoImports.AutoImportsGenerator
 import dotty.tools.pc.AutoImports.ImportSel
-import dotty.tools.pc.AutoImports.ImportSel.Direct
-import dotty.tools.pc.AutoImports.ImportSel.Rename
 import dotty.tools.pc.IndexedContext
 import dotty.tools.pc.IndexedContext.Result
 import dotty.tools.pc.Params
diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala
index 78635e540c43..7d29e6c4dda9 100644
--- a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala
@@ -8,9 +8,7 @@ import scala.meta.internal.metals.CompilerRangeParams
 import scala.language.unsafeNulls
 
 import dotty.tools.pc.utils.TestInlayHints
-import dotty.tools.pc.utils.TextEdits
 
-import org.eclipse.lsp4j.TextEdit
 
 class BaseInlayHintsSuite extends BasePCSuite {
 
@@ -55,4 +53,4 @@ class BaseInlayHintsSuite extends BasePCSuite {
       obtained,
     )
 
-}
\ No newline at end of file
+}
diff --git a/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala b/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala
index 82e697e6e9a1..4999e0ddbc69 100644
--- a/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala
+++ b/presentation-compiler/test/dotty/tools/pc/base/ReusableClassRunner.scala
@@ -13,22 +13,17 @@ class ReusableClassRunner(testClass: Class[BasePCSuite])
     testClass.getDeclaredConstructor().newInstance()
 
   override def createTest(): AnyRef = instance
-  override def withBefores(
-      method: FrameworkMethod,
-      target: Object,
-      statement: Statement
-  ): Statement =
-    statement
 
   override def withAfters(
       method: FrameworkMethod,
       target: Object,
       statement: Statement
   ): Statement =
+    val newStatement = super.withAfters(method, target, statement)
     new Statement():
       override def evaluate(): Unit =
         try
-          statement.evaluate()
+          newStatement.evaluate()
         finally
           if (isLastTestCase(method)) then instance.clean()
 
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala
new file mode 100644
index 000000000000..3fecba04fb77
--- /dev/null
+++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala
@@ -0,0 +1,166 @@
+package dotty.tools.pc.tests
+
+import dotty.tools.dotc.core.Contexts.Context
+import dotty.tools.pc.base.BasePCSuite
+import dotty.tools.pc.ScalaPresentationCompiler
+import org.junit.{Before, Test}
+
+import scala.language.unsafeNulls
+import scala.meta.internal.metals.EmptyCancelToken
+import scala.meta.internal.metals.CompilerOffsetParams
+import scala.meta.pc.OffsetParams
+import scala.concurrent.Future
+import scala.concurrent.Await
+import scala.meta.pc.VirtualFileParams
+import scala.concurrent.duration.*
+
+import java.util.Collections
+import java.nio.file.Paths
+import java.util.concurrent.CompletableFuture
+
+
+class CompilerCachingSuite extends BasePCSuite:
+
+  val timeout = 5.seconds
+
+  private def checkCompilationCount(params: VirtualFileParams, expected: Int): Unit =
+    presentationCompiler match
+      case pc: ScalaPresentationCompiler =>
+        val compilations= pc.compilerAccess.withNonInterruptableCompiler(Some(params))(-1, EmptyCancelToken) { driver =>
+          driver.compiler().currentCtx.runId
+        }.get(timeout.length, timeout.unit)
+        assertEquals(expected, compilations, s"Expected $expected compilations but got $compilations")
+      case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler")
+
+  private def getContext(params: VirtualFileParams): Context =
+    presentationCompiler match
+      case pc: ScalaPresentationCompiler =>
+        pc.compilerAccess.withNonInterruptableCompiler(Some(params))(null, EmptyCancelToken) { driver =>
+          driver.compiler().currentCtx
+        }.get(timeout.length, timeout.unit)
+      case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler")
+
+  @Before
+  def beforeEach: Unit =
+    presentationCompiler.restart()
+
+    // We want to run art least one compilation, so runId points at 3.
+    // This will ensure that we use the same driver, not recreate fresh one on each call
+    val dryRunParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "dryRun", 1, EmptyCancelToken)
+    checkCompilationCount(dryRunParams, 2)
+    val freshContext = getContext(dryRunParams)
+    presentationCompiler.complete(dryRunParams).get(timeout.length, timeout.unit)
+    checkCompilationCount(dryRunParams, 3)
+    val dryRunContext = getContext(dryRunParams)
+    assert(freshContext != dryRunContext)
+
+
+  @Test
+  def `cursor-compilation-does-not-corrupt-cache`: Unit =
+
+    val fakeParamsCursor = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = new", 15, EmptyCancelToken)
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken)
+
+    val contextPreCompilation = getContext(fakeParams)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextPostFirst = getContext(fakeParams)
+    assert(contextPreCompilation != contextPostFirst)
+    checkCompilationCount(fakeParams, 4)
+
+    presentationCompiler.complete(fakeParamsCursor).get(timeout.length, timeout.unit)
+    val contextPostCursor = getContext(fakeParamsCursor)
+    assert(contextPreCompilation != contextPostCursor)
+    assert(contextPostFirst == contextPostCursor)
+    checkCompilationCount(fakeParamsCursor, 4)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextPostSecond = getContext(fakeParams)
+    assert(contextPreCompilation != contextPostSecond)
+    assert(contextPostFirst == contextPostCursor)
+    assert(contextPostCursor == contextPostSecond)
+    checkCompilationCount(fakeParamsCursor, 4)
+
+  @Test
+  def `compilation-for-same-snippet-is-cached`: Unit =
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken)
+
+    val contextPreCompilation = getContext(fakeParams)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextPostFirst = getContext(fakeParams)
+    assert(contextPreCompilation != contextPostFirst)
+    checkCompilationCount(fakeParams, 4)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextPostSecond = getContext(fakeParams)
+    assert(contextPreCompilation != contextPostFirst)
+    assert(contextPostSecond == contextPostFirst)
+    checkCompilationCount(fakeParams, 4)
+
+  @Test
+  def `compilation-for-different-snippet-is-not-cached`: Unit =
+
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
+    val fakeParams2 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
+    val fakeParams3 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = print", 17, EmptyCancelToken)
+
+    checkCompilationCount(fakeParams, 3)
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    checkCompilationCount(fakeParams, 4)
+
+    presentationCompiler.complete(fakeParams2).get(timeout.length, timeout.unit)
+    checkCompilationCount(fakeParams2, 5)
+
+    presentationCompiler.complete(fakeParams3).get(timeout.length, timeout.unit)
+    checkCompilationCount(fakeParams3, 6)
+
+
+  private val testFunctions: List[OffsetParams => CompletableFuture[_]] = List(
+    presentationCompiler.complete(_),
+    presentationCompiler.convertToNamedArguments(_, Collections.emptyList()),
+    presentationCompiler.autoImports("a", _, false),
+    presentationCompiler.definition(_),
+    presentationCompiler.didChange(_),
+    presentationCompiler.documentHighlight(_),
+    presentationCompiler.hover(_),
+    presentationCompiler.implementAbstractMembers(_),
+    presentationCompiler.insertInferredType(_),
+    presentationCompiler.semanticTokens(_),
+    presentationCompiler.prepareRename(_),
+    presentationCompiler.rename(_, "a"),
+    presentationCompiler.signatureHelp(_),
+    presentationCompiler.typeDefinition(_)
+  )
+
+
+  @Test
+  def `different-api-calls-reuse-cache`: Unit =
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextBefore = getContext(fakeParams)
+
+    val differentContexts = testFunctions.map: f =>
+      f(fakeParams).get(timeout.length, timeout.unit)
+      checkCompilationCount(fakeParams, 4)
+      getContext(fakeParams)
+    .toSet
+
+    assert(differentContexts == Set(contextBefore))
+
+  @Test
+  def `different-api-calls-reuse-cache-parallel`: Unit =
+    import scala.jdk.FutureConverters.*
+    import scala.concurrent.ExecutionContext.Implicits.global
+
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken)
+
+    presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
+    val contextBefore = getContext(fakeParams)
+
+    val futures = testFunctions.map: f =>
+      f(fakeParams).asScala.map(_ => getContext(fakeParams))
+
+    val res = Await.result(Future.sequence(futures), timeout).toSet
+    assert(res == Set(contextBefore))
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala
index 010d0b14fa90..6a8759d0a0c9 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala
@@ -1,14 +1,10 @@
 package dotty.tools.pc.tests.completion
 
-import scala.meta.pc.SymbolDocumentation
 import scala.language.unsafeNulls
 
 import dotty.tools.pc.base.BaseCompletionSuite
-import dotty.tools.pc.utils.MockEntries
 
 import org.junit.Test
-import org.junit.Ignore
-import scala.collection.immutable.ListMapBuilder
 
 class CompletionExtraConstructorSuite extends BaseCompletionSuite:
 
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
index 20d56ab94938..fab21ffdee0a 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala
@@ -28,7 +28,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
       MockLocation("scala/Predef.Ensuring#ensuring(+2).", "Predef.scala"),
       MockLocation("scala/Predef.Ensuring#ensuring(+3).", "Predef.scala"),
       MockLocation("scala/collection/immutable/List#`::`().", "List.scala"),
-      MockLocation("scala/package.List.", "List.scala")
+      MockLocation("scala/package.List.", "package.scala")
     )
 
   override def definitions(offsetParams: OffsetParams): List[Location] =
@@ -123,7 +123,7 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
     check(
       """|
          |object Main {
-         |  /*scala/package.List. List.scala*/@@List(1)
+         |  /*scala/package.List. package.scala*/@@List(1)
          |}
          |""".stripMargin
     )
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
index 0b992fe98f08..3e7a2549cbe0 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTermSuite.scala
@@ -694,3 +694,11 @@ class HoverTermSuite extends BaseHoverSuite:
         |""".stripMargin,
       "extension [T](a: T) def *:[U <: Tuple](b: Wrap[U]): Wrap[T *: U]".hover
     )
+
+  @Test def `dont-ignore-???-in-path`: Unit =
+    check(
+      """object Obj:
+        |  val x = ?@@??
+        |""".stripMargin,
+      """def ???: Nothing""".stripMargin.hover
+  )
diff --git a/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala b/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala
index a37801b3c48c..3dabcded4e45 100644
--- a/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala
+++ b/presentation-compiler/test/dotty/tools/pc/utils/DefSymbolCollector.scala
@@ -3,7 +3,7 @@ package dotty.tools.pc.utils
 import scala.meta.pc.VirtualFileParams
 
 import dotty.tools.dotc.ast.tpd.*
-import dotty.tools.dotc.ast.{Trees, tpd}
+import dotty.tools.dotc.ast.Trees
 import dotty.tools.dotc.core.Symbols.*
 import dotty.tools.dotc.interactive.InteractiveDriver
 import dotty.tools.dotc.util.SourcePosition
diff --git a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala
index 9015a39ba9e7..459c41e3c8e5 100644
--- a/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala
+++ b/presentation-compiler/test/dotty/tools/pc/utils/MockSymbolSearch.scala
@@ -8,7 +8,6 @@ import scala.jdk.CollectionConverters.*
 import scala.jdk.OptionConverters.*
 import scala.meta.internal.metals.{ClasspathSearch, WorkspaceSymbolQuery}
 import scala.meta.pc.ContentType
-import scala.meta.pc.SymbolSearch.Result
 import scala.meta.pc.{
   ParentSymbols,
   SymbolDocumentation,
diff --git a/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala b/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala
index ef15121c6702..af4502d66b4b 100644
--- a/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala
+++ b/presentation-compiler/test/dotty/tools/pc/utils/PcAssertions.scala
@@ -4,7 +4,6 @@ import scala.language.unsafeNulls
 
 import dotty.tools.pc.completions.CompletionSource
 import dotty.tools.dotc.util.DiffUtil
-import dotty.tools.pc.utils.InteractiveEnrichments.*
 
 import org.hamcrest
 import org.hamcrest.*
@@ -127,7 +126,6 @@ trait PcAssertions:
     def getDetailedMessage(diff: String): String =
       val lines = diff.linesIterator.toList
       val sources = completionSources.padTo(lines.size, CompletionSource.Empty)
-      val maxLength = lines.map(_.length).maxOption.getOrElse(0)
       var completionIndex = 0
       lines.map: line =>
         if line.startsWith(Console.BOLD + Console.RED) || line.startsWith("  ") then
diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala
index a923b76b955c..b9d3fd411dcc 100644
--- a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala
+++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala
@@ -4,7 +4,6 @@ import scala.collection.mutable.ListBuffer
 
 import scala.meta.internal.jdk.CollectionConverters._
 import dotty.tools.pc.utils.InteractiveEnrichments.*
-import dotty.tools.pc.utils.TextEdits
 
 import org.eclipse.lsp4j.InlayHint
 import org.eclipse.lsp4j.TextEdit
@@ -67,4 +66,4 @@ object TestInlayHints {
   def removeInlayHints(text: String): String =
     text.replaceAll(raw"\/\*(.*?)\*\/", "").nn
 
-}
\ No newline at end of file
+}

From 009fc63b22c08d0946e574f5795b6ef00aaa4a69 Mon Sep 17 00:00:00 2001
From: rochala <rochala@tutanota.com>
Date: Tue, 27 Aug 2024 09:56:43 +0200
Subject: [PATCH 3/4] Address review comments

---
 .../dotty/tools/dotc/ast/NavigateAST.scala    |  2 +-
 .../dotty/tools/pc/AutoImportsProvider.scala  |  3 +-
 .../pc/completions/CompletionProvider.scala   | 28 +++++--
 .../pc/completions/NamedArgCompletions.scala  |  7 +-
 .../pc/completions/OverrideCompletions.scala  | 33 +++++---
 .../tools/pc/tests/CompilerCachingSuite.scala | 77 +++++++++----------
 6 files changed, 86 insertions(+), 64 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
index 429e0868667c..ed1473d79ad0 100644
--- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
+++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
@@ -98,7 +98,7 @@ object NavigateAST {
     def isBetterFit(currentBest: List[Positioned], candidate: List[Positioned]): Boolean =
       if currentBest.isEmpty && candidate.nonEmpty then true
       else if currentBest.nonEmpty && candidate.nonEmpty then
-        val bestSpan= currentBest.head.span
+        val bestSpan = currentBest.head.span
         val candidateSpan = candidate.head.span
 
         bestSpan != candidateSpan &&
diff --git a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
index 3d4864c73508..e35556ad11c9 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala
@@ -66,7 +66,8 @@ final class AutoImportsProvider(
     val results = symbols.result.filter(isExactMatch(_, name))
 
     if results.nonEmpty then
-      val correctedPos = CompletionPos.infer(pos, params, path, false).toSourcePosition
+      val correctedPos =
+        CompletionPos.infer(pos, params, path, wasCursorApplied = false).toSourcePosition
       val mkEdit =
         path match
           // if we are in import section just specify full name
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
index 78bf15614769..5578fab412d1 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala
@@ -38,6 +38,11 @@ import org.eclipse.lsp4j.Range as LspRange
 import org.eclipse.lsp4j.TextEdit
 import scala.meta.pc.CompletionItemPriority
 
+object CompletionProvider:
+  val allKeywords =
+    val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
+    Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)
+
 class CompletionProvider(
     search: SymbolSearch,
     cachingDriver: InteractiveDriver,
@@ -76,7 +81,20 @@ class CompletionProvider(
 
         val tpdPath = tpdPath0 match
           case Select(qual, name) :: tail
-            // If for any reason we end up in param after lifting, we want to inline the synthetic val
+            /** If for any reason we end up in param after lifting, we want to inline the synthetic val:
+             *  List(1).iterator.sliding@@ will be transformed into:
+             *
+             *  1| val $1$: Iterator[Int] = List.apply[Int]([1 : Int]*).iterator
+             *  2| {
+             *  3|   def $anonfun(size: Int, step: Int): $1$.GroupedIterator[Int] =
+             *  4|     $1$.sliding[Int](size, step)
+             *  5|   closure($anonfun)
+             *  6| }:((Int, Int) => Iterator[Int]#GroupedIterator[Int])
+             *
+             *  With completion being run at line 4 at @@:
+             *  4|     $1$.sliding@@[Int](size, step)
+             *
+             */
             if qual.symbol.is(Flags.Synthetic) && qual.symbol.name.isInstanceOf[DerivedName] =>
               qual.symbol.defTree match
                 case valdef: ValDef => Select(valdef.rhs, name) :: tail
@@ -138,10 +156,6 @@ class CompletionProvider(
     )
   end completions
 
-  val allKeywords =
-    val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
-    Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)
-
   /**
    * In case if completion comes from empty line like:
    * {{{
@@ -159,8 +173,8 @@ class CompletionProvider(
     val offset = params.offset().nn
     val query = Completion.naiveCompletionPrefix(text, offset)
 
-    if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart && !allKeywords.contains(query) then
-      false -> text
+    if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart
+      && !CompletionProvider.allKeywords.contains(query) then false -> text
     else
       val isStartMultilineComment =
 
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
index 8cf66eee5aba..dd3a910beb4f 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala
@@ -170,7 +170,7 @@ object NamedArgCompletions:
                   .zipWithIndex
                   .forall { case (pair, index) =>
                     FuzzyArgMatcher(m.tparams)
-                      .doMatch(allArgsProvided = index != 0)
+                      .doMatch(allArgsProvided = index != 0, ident)
                       .tupled(pair)
                   } =>
             m
@@ -385,12 +385,13 @@ class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context):
    * We check the args types not the result type.
    */
   def doMatch(
-      allArgsProvided: Boolean
+      allArgsProvided: Boolean,
+      ident: Option[Ident]
   )(expectedArgs: List[Symbols.Symbol], actualArgs: List[Tree]) =
     (expectedArgs.length == actualArgs.length ||
       (!allArgsProvided && expectedArgs.length >= actualArgs.length)) &&
       actualArgs.zipWithIndex.forall {
-        case (Ident(name), _) => true
+        case (arg: Ident, _) if ident.contains(arg) => true
         case (NamedArg(name, arg), _) =>
           expectedArgs.exists { expected =>
             expected.name == name && (!arg.hasType || arg.typeOpt.unfold
diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
index 28dc4ebe59c9..f5c15ca6df0e 100644
--- a/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
+++ b/presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala
@@ -530,8 +530,11 @@ object OverrideCompletions:
   object OverrideExtractor:
     def unapply(path: List[Tree])(using Context) =
       path match
-        // class FooImpl extends Foo:
-        //   def x|
+        // abstract class Val:
+        //   def hello: Int = 2
+        //
+        // class Main extends Val:
+        //   def h|
         case (dd: (DefDef | ValDef)) :: (t: Template) :: (td: TypeDef) :: _
             if t.parents.nonEmpty =>
           val completing =
@@ -547,12 +550,13 @@ object OverrideCompletions:
             )
           )
 
-        // class FooImpl extends Foo:
+        // abstract class Val:
+        //   def hello: Int = 2
+        //
+        // class Main extends Val:
         //   ov|
         case (ident: Ident) :: (t: Template) :: (td: TypeDef) :: _
-            if t.parents.nonEmpty && "override".startsWith(
-              ident.name.show.replace(Cursor.value, "")
-            ) =>
+            if t.parents.nonEmpty && "override".startsWith(ident.name.show.replace(Cursor.value, "")) =>
           Some(
             (
               td,
@@ -563,15 +567,13 @@ object OverrideCompletions:
             )
           )
 
+        // abstract class Val:
+        //   def hello: Int = 2
+        //
         // class Main extends Val:
         //    def@@
         case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
-            if t.parents.nonEmpty && "def".startsWith(
-              id.name.decoded.replace(
-                Cursor.value,
-                "",
-              )
-            ) =>
+            if t.parents.nonEmpty && "def".startsWith(id.name.decoded.replace(Cursor.value, "")) =>
           Some(
             (
               td,
@@ -581,6 +583,10 @@ object OverrideCompletions:
               None,
             )
           )
+
+        // abstract class Val:
+        //   def hello: Int = 2
+        //
         // class Main extends Val:
         //   he@@
         case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
@@ -595,6 +601,9 @@ object OverrideCompletions:
             )
           )
 
+        // abstract class Val:
+        //   def hello: Int = 2
+        //
         // class Main extends Val:
         //   hello@ // this transforms into this.hello, thus is a Select
         case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _
diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala
index 3fecba04fb77..5e13c07b9e5f 100644
--- a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala
+++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala
@@ -23,19 +23,19 @@ class CompilerCachingSuite extends BasePCSuite:
 
   val timeout = 5.seconds
 
-  private def checkCompilationCount(params: VirtualFileParams, expected: Int): Unit =
+  private def checkCompilationCount(expected: Int): Unit =
     presentationCompiler match
       case pc: ScalaPresentationCompiler =>
-        val compilations= pc.compilerAccess.withNonInterruptableCompiler(Some(params))(-1, EmptyCancelToken) { driver =>
+        val compilations = pc.compilerAccess.withNonInterruptableCompiler(None)(-1, EmptyCancelToken) { driver =>
           driver.compiler().currentCtx.runId
         }.get(timeout.length, timeout.unit)
         assertEquals(expected, compilations, s"Expected $expected compilations but got $compilations")
       case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler")
 
-  private def getContext(params: VirtualFileParams): Context =
+  private def getContext(): Context =
     presentationCompiler match
       case pc: ScalaPresentationCompiler =>
-        pc.compilerAccess.withNonInterruptableCompiler(Some(params))(null, EmptyCancelToken) { driver =>
+        pc.compilerAccess.withNonInterruptableCompiler(None)(null, EmptyCancelToken) { driver =>
           driver.compiler().currentCtx
         }.get(timeout.length, timeout.unit)
       case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler")
@@ -44,76 +44,73 @@ class CompilerCachingSuite extends BasePCSuite:
   def beforeEach: Unit =
     presentationCompiler.restart()
 
-    // We want to run art least one compilation, so runId points at 3.
+    // We want to run at least one compilation, so runId points at 3.
     // This will ensure that we use the same driver, not recreate fresh one on each call
     val dryRunParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "dryRun", 1, EmptyCancelToken)
-    checkCompilationCount(dryRunParams, 2)
-    val freshContext = getContext(dryRunParams)
+    checkCompilationCount(2)
+    val freshContext = getContext()
     presentationCompiler.complete(dryRunParams).get(timeout.length, timeout.unit)
-    checkCompilationCount(dryRunParams, 3)
-    val dryRunContext = getContext(dryRunParams)
+    checkCompilationCount(3)
+    val dryRunContext = getContext()
     assert(freshContext != dryRunContext)
 
 
   @Test
   def `cursor-compilation-does-not-corrupt-cache`: Unit =
+    val contextPreCompilation = getContext()
 
-    val fakeParamsCursor = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = new", 15, EmptyCancelToken)
     val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken)
-
-    val contextPreCompilation = getContext(fakeParams)
-
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextPostFirst = getContext(fakeParams)
+    val contextPostFirst = getContext()
     assert(contextPreCompilation != contextPostFirst)
-    checkCompilationCount(fakeParams, 4)
+    checkCompilationCount(4)
 
+    val fakeParamsCursor = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = new", 15, EmptyCancelToken)
     presentationCompiler.complete(fakeParamsCursor).get(timeout.length, timeout.unit)
-    val contextPostCursor = getContext(fakeParamsCursor)
+    val contextPostCursor = getContext()
     assert(contextPreCompilation != contextPostCursor)
     assert(contextPostFirst == contextPostCursor)
-    checkCompilationCount(fakeParamsCursor, 4)
+    checkCompilationCount(4)
 
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextPostSecond = getContext(fakeParams)
+    val contextPostSecond = getContext()
     assert(contextPreCompilation != contextPostSecond)
     assert(contextPostFirst == contextPostCursor)
     assert(contextPostCursor == contextPostSecond)
-    checkCompilationCount(fakeParamsCursor, 4)
+    checkCompilationCount(4)
 
   @Test
   def `compilation-for-same-snippet-is-cached`: Unit =
-    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken)
-
-    val contextPreCompilation = getContext(fakeParams)
+    val contextPreCompilation = getContext()
 
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 14, EmptyCancelToken)
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextPostFirst = getContext(fakeParams)
+    val contextPostFirst = getContext()
     assert(contextPreCompilation != contextPostFirst)
-    checkCompilationCount(fakeParams, 4)
+    checkCompilationCount(4)
 
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextPostSecond = getContext(fakeParams)
+    val contextPostSecond = getContext()
     assert(contextPreCompilation != contextPostFirst)
     assert(contextPostSecond == contextPostFirst)
-    checkCompilationCount(fakeParams, 4)
+    checkCompilationCount(4)
 
   @Test
   def `compilation-for-different-snippet-is-not-cached`: Unit =
 
-    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
-    val fakeParams2 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
-    val fakeParams3 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = print", 17, EmptyCancelToken)
 
-    checkCompilationCount(fakeParams, 3)
+    checkCompilationCount(3)
+    val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    checkCompilationCount(fakeParams, 4)
+    checkCompilationCount(4)
 
+    val fakeParams2 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = prin", 16, EmptyCancelToken)
     presentationCompiler.complete(fakeParams2).get(timeout.length, timeout.unit)
-    checkCompilationCount(fakeParams2, 5)
+    checkCompilationCount(5)
 
+    val fakeParams3 = CompilerOffsetParams(Paths.get("Test2.scala").toUri(), "def hello = print", 17, EmptyCancelToken)
     presentationCompiler.complete(fakeParams3).get(timeout.length, timeout.unit)
-    checkCompilationCount(fakeParams3, 6)
+    checkCompilationCount(6)
 
 
   private val testFunctions: List[OffsetParams => CompletableFuture[_]] = List(
@@ -137,14 +134,14 @@ class CompilerCachingSuite extends BasePCSuite:
   @Test
   def `different-api-calls-reuse-cache`: Unit =
     val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken)
-
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextBefore = getContext(fakeParams)
+
+    val contextBefore = getContext()
 
     val differentContexts = testFunctions.map: f =>
       f(fakeParams).get(timeout.length, timeout.unit)
-      checkCompilationCount(fakeParams, 4)
-      getContext(fakeParams)
+      checkCompilationCount(4)
+      getContext()
     .toSet
 
     assert(differentContexts == Set(contextBefore))
@@ -155,12 +152,12 @@ class CompilerCachingSuite extends BasePCSuite:
     import scala.concurrent.ExecutionContext.Implicits.global
 
     val fakeParams = CompilerOffsetParams(Paths.get("Test.scala").toUri(), "def hello = ne", 13, EmptyCancelToken)
-
     presentationCompiler.complete(fakeParams).get(timeout.length, timeout.unit)
-    val contextBefore = getContext(fakeParams)
+
+    val contextBefore = getContext()
 
     val futures = testFunctions.map: f =>
-      f(fakeParams).asScala.map(_ => getContext(fakeParams))
+      f(fakeParams).asScala.map(_ => getContext())
 
     val res = Await.result(Future.sequence(futures), timeout).toSet
     assert(res == Set(contextBefore))

From a9ac82915b98ce2085e1365a96ee8865fb4b1a65 Mon Sep 17 00:00:00 2001
From: rochala <rochala@tutanota.com>
Date: Wed, 28 Aug 2024 17:09:30 +0200
Subject: [PATCH 4/4] Make pathTo handle new pattern recovery mechanism

---
 compiler/src/dotty/tools/dotc/ast/NavigateAST.scala | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
index ed1473d79ad0..e77642a8e2b9 100644
--- a/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
+++ b/compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
@@ -77,7 +77,8 @@ object NavigateAST {
       var bestFit: List[Positioned] = path
       while (it.hasNext) do
         val path1 = it.next() match
-          case sel: untpd.Select if isTreeFromRecovery(sel) => path
+          case sel: untpd.Select if isRecoveryTree(sel) => path
+          case sel: untpd.Ident  if isPatternRecoveryTree(sel) => path
           case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path)
           case m: untpd.Modifiers => childPath(m.productIterator, path)
           case xs: List[?] => childPath(xs.iterator, path)
@@ -106,9 +107,12 @@ object NavigateAST {
           bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic
       else false
 
+    def isRecoveryTree(sel: untpd.Select): Boolean =
+      sel.span.isSynthetic
+        && (sel.name == StdNames.nme.??? && sel.qualifier.symbol.name == StdNames.nme.Predef)
 
-    def isTreeFromRecovery(p: untpd.Select): Boolean =
-      p.name == StdNames.nme.??? && p.qualifier.symbol.name == StdNames.nme.Predef && p.span.isSynthetic
+    def isPatternRecoveryTree(ident: untpd.Ident): Boolean =
+      ident.span.isSynthetic && StdNames.nme.WILDCARD == ident.name
 
     def envelops(a: Span, b: Span): Boolean =
       !b.exists || a.exists && (