Skip to content

Commit

Permalink
Add custom matchers for completions (fuzzy search for presentation co…
Browse files Browse the repository at this point in the history
…mpiler) (#19850)

Fixes scalameta/metals#4656
Fixes #5507
Fixes #17706

To ensure CI output will be the same on all runners, I'm scheduling runs
on all jvm versions + windows.

[test_windows_full]
[test_java8]
[test_java11]
[test_java15]
[test_java17]
[test_java18]
[test_java19]
[Cherry-picked acfc621][modified]
  • Loading branch information
WojciechMazur committed Jul 5, 2024
1 parent a36fdd1 commit 953b7a7
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 76 deletions.
20 changes: 10 additions & 10 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ object Completion:
mode: Mode,
rawPrefix: String,
tpdPath: List[tpd.Tree],
untpdPath: List[untpd.Tree]
untpdPath: List[untpd.Tree],
customMatcher: Option[Name => Boolean] = None
)(using Context): CompletionMap =
val adjustedPath = typeCheckExtensionConstructPath(untpdPath, tpdPath, pos)
computeCompletions(pos, mode, rawPrefix, adjustedPath)
computeCompletions(pos, mode, rawPrefix, adjustedPath, customMatcher)

/**
* Inspect `path` to determine what kinds of symbols should be considered.
Expand Down Expand Up @@ -193,11 +194,12 @@ object Completion:
.flatten.getOrElse(tpdPath)

private def computeCompletions(
pos: SourcePosition, mode: Mode, rawPrefix: String, adjustedPath: List[tpd.Tree]
pos: SourcePosition, mode: Mode, rawPrefix: String, adjustedPath: List[tpd.Tree], matches: Option[Name => Boolean]
)(using Context): CompletionMap =
val hasBackTick = rawPrefix.headOption.contains('`')
val prefix = if hasBackTick then rawPrefix.drop(1) else rawPrefix
val completer = new Completer(mode, prefix, pos)
val matches0 = matches.getOrElse(_.startsWith(prefix))
val completer = new Completer(mode, pos, matches0)

val result = adjustedPath match
// Ignore synthetic select from `This` because in code it was `Ident`
Expand All @@ -209,7 +211,6 @@ object Completion:
case _ => completer.scopeCompletions

interactiv.println(i"""completion info with pos = $pos,
| prefix = ${completer.prefix},
| term = ${completer.mode.is(Mode.Term)},
| type = ${completer.mode.is(Mode.Type)},
| scope = ${completer.mode.is(Mode.Scope)},
Expand Down Expand Up @@ -311,13 +312,13 @@ object Completion:

/** Computes code completions depending on the context in which completion is requested
* @param mode Should complete names of terms, types or both
* @param prefix The prefix that all suggested completions should start with
* @param pos Cursor position where completion was requested
* @param matches Function taking name used to filter completions
*
* For the results of all `xyzCompletions` methods term names and type names are always treated as different keys in the same map
* and they never conflict with each other.
*/
class Completer(val mode: Mode, val prefix: String, pos: SourcePosition):
class Completer(val mode: Mode, pos: SourcePosition, matches: Name => Boolean):
/** Completions for terms and types that are currently in scope:
* the members of the current class, local definitions and the symbols that have been imported,
* recursively adding completions from outer scopes.
Expand Down Expand Up @@ -524,7 +525,7 @@ object Completion:
// There are four possible ways for an extension method to be applicable

// 1. The extension method is visible under a simple name, by being defined or inherited or imported in a scope enclosing the reference.
val termCompleter = new Completer(Mode.Term, prefix, pos)
val termCompleter = new Completer(Mode.Term, pos, matches)
val extMethodsInScope = termCompleter.scopeCompletions.toList.flatMap:
case (name, denots) => denots.collect:
case d: SymDenotation if d.isTerm && d.termRef.symbol.is(Extension) => (d.termRef, name.asTermName)
Expand Down Expand Up @@ -556,7 +557,7 @@ object Completion:
* 2. satisfy [[Completion.isValidCompletionSymbol]]
*/
private def include(denot: SingleDenotation, nameInScope: Name)(using Context): Boolean =
nameInScope.startsWith(prefix) &&
matches(nameInScope) &&
completionsFilter(NoType, nameInScope) &&
isValidCompletionSymbol(denot.symbol, mode)

Expand Down Expand Up @@ -605,7 +606,6 @@ object Completion:
private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = {
val typer = ctx.typer
val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits
conversions.map(_.tree.typeOpt)

interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %")
conversions
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,8 @@ object Implicits:
|must be more specific than $target"""

override def msg(using Context) =
super.msg.append("\nThe expected type $target is not specific enough, so no search was attempted")
super.msg.append(i"\nThe expected type $target is not specific enough, so no search was attempted")

override def toString = s"TooUnspecific"

/** An ambiguous implicits failure */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.core.Comments.Comment
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Denotations.SingleDenotation
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.NameOps.*
Expand All @@ -26,14 +27,13 @@ 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.completions.OverrideCompletions.OverrideExtractor
import dotty.tools.pc.buildinfo.BuildInfo
import dotty.tools.pc.completions.OverrideCompletions.OverrideExtractor
import dotty.tools.pc.utils.MtagsEnrichments.*
import dotty.tools.dotc.core.Denotations.SingleDenotation


class Completions(
text: String,
Expand Down Expand Up @@ -102,9 +102,13 @@ class Completions(
end if
end includeSymbol

lazy val fuzzyMatcher: Name => Boolean = name =>
if completionMode.is(Mode.Member) then CompletionFuzzy.matchesSubCharacters(completionPos.query, name.toString)
else CompletionFuzzy.matches(completionPos.query, name.toString)

def enrichedCompilerCompletions(qualType: Type): (List[CompletionValue], SymbolSearch.Result) =
val compilerCompletions = Completion
.rawCompletions(completionPos.originalCursorPosition, completionMode, completionPos.query, path, adjustedPath)
.rawCompletions(completionPos.originalCursorPosition, completionMode, completionPos.query, path, adjustedPath, Some(fuzzyMatcher))

compilerCompletions
.toList
Expand Down Expand Up @@ -423,7 +427,7 @@ class Completions(

// class Fo@@
case (td: TypeDef) :: _
if Fuzzy.matches(
if CompletionFuzzy.matches(
td.symbol.name.decoded.replace(Cursor.value, "").nn,
filename
) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ abstract class BaseCompletionSuite extends BasePCSuite:

if (assertSingleItem && items.length != 1) then
fail(
s"expected single completion item, obtained ${items.length} items.\n${items}"
s"expected single completion item, obtained ${items.length} items.\n${items.map(_.getLabel.nn + "\n")}"
)

if (items.size <= itemIndex) then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ class CompletionDocSuite extends BaseCompletionSuite:
|Found documentation for scala/collection/Iterator.
|Iterator scala.collection
|""".stripMargin,

includeDocs = true
includeDocs = true,
topLines = Some(1)
)

@Test def `scala5` =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (extension)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

@Test def `simple-old-syntax` =
check(
"""|package example
"""package example
|
|object Test:
| implicit class TestOps(a: Int):
| def testOps(b: Int): String = ???
|
|def main = 100.test@@
|""".stripMargin,
"""|testOps(b: Int): String (implicit)
|""".stripMargin
"""testOps(b: Int): String (implicit)
|""".stripMargin,
topLines = Some(1)
)

@Test def `simple2` =
Expand Down Expand Up @@ -93,8 +96,10 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|
|def main = "foo".iden@@
|""".stripMargin,
"""|identity: String (implicit)
|""".stripMargin // identity2 won't be available
"""|identity: String (implicit)
|""".stripMargin, // identity2 won't be available
filter = _.contains("(implicit)")

)

@Test def `filter-by-type-subtype` =
Expand Down Expand Up @@ -152,7 +157,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def incr: Int = num + 1
|
|def main = 100.incr
|""".stripMargin
|""".stripMargin,
assertSingleItem = false
)

@Test def `simple-edit-old` =
Expand All @@ -174,7 +180,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def incr: Int = num + 1
|
|def main = 100.incr
|""".stripMargin
|""".stripMargin,
assertSingleItem = false
)

@Test def `simple-edit-suffix` =
Expand Down Expand Up @@ -262,6 +269,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (extension)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

Expand All @@ -276,6 +285,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (implicit)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

Expand All @@ -290,6 +301,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (extension)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

Expand All @@ -304,6 +317,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (implicit)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

Expand Down Expand Up @@ -391,7 +406,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|testVal: Int (implicit)
|testVar: Int (implicit)
|testOps(b: Int): String (implicit)
|""".stripMargin
|""".stripMargin,
topLines = Some(4)
)

@Test def `implicit-val-edit` =
Expand All @@ -413,5 +429,6 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| val testVal: Int = 42
|
|def main = 100.testVal
|""".stripMargin
|""".stripMargin,
assertSingleItem = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -695,28 +695,26 @@ class CompletionKeywordSuite extends BaseCompletionSuite:

@Test def `derives-with-extends` =
check(
"""
|package foo
|
|trait Bar {}
|trait Baz {}
|
|class Foo(x: Int) extends Bar with Baz der@@
""".stripMargin,
"""|package foo
|
|trait Bar {}
|trait Baz {}
|
|class Foo(x: Int) extends Bar with Baz der@@
|""".stripMargin,
"""|derives
|""".stripMargin
)

@Test def `derives-with-constructor-extends` =
check(
"""
|package foo
|
|trait Bar {}
|class Baz(b: Int) {}
|
|class Foo(x: Int) extends Bar with Baz(1) der@@
""".stripMargin,
"""|package foo
|
|trait Bar {}
|class Baz(b: Int) {}
|
|class Foo(x: Int) extends Bar with Baz(1) der@@
|""".stripMargin,
"""|derives
|""".stripMargin
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,12 +925,15 @@ class CompletionOverrideSuite extends BaseCompletionSuite:
| def@@
|}
|""".stripMargin,

"""|def hello1: Int
|override def equals(x$0: Any): Boolean
|override def hashCode(): Int
|override def toString(): String
|override val hello2: Int
|""".stripMargin,
includeDetail = false,
topLines = Some(3)
topLines = Some(5)
)

@Test def `path-dependent` =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ class CompletionSnippetNegSuite extends BaseCompletionSuite:

@Test def `member` =
checkSnippet(
"""
|object Main {
| List.appl@@
|}
|""".stripMargin,
"apply"
"""|object Main {
| List.appl@@
|}
|""".stripMargin,
"""|apply
|unapplySeq""".stripMargin
)

@Test def `scope` =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CompletionSnippetSuite extends BaseCompletionSuite:
|}
|""".stripMargin,
"""|apply($0)
|unapplySeq($0)
|""".stripMargin
)

Expand Down Expand Up @@ -429,7 +430,8 @@ class CompletionSnippetSuite extends BaseCompletionSuite:
| extension (s: String)
| def bar = 0
| val bar = "abc".bar
""".stripMargin
""".stripMargin,
filter = _.contains("bar: Int")
)

// https://github.com/scalameta/metals/issues/4004
Expand All @@ -446,5 +448,6 @@ class CompletionSnippetSuite extends BaseCompletionSuite:
| extension (s: String)
| def bar() = 0
| val bar = "abc".bar()
""".stripMargin
""".stripMargin,
filter = _.contains("bar: Int")
)
Loading

0 comments on commit 953b7a7

Please sign in to comment.