Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom matchers for completions (fuzzy search for presentation compiler) #19850

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ object Implicits:
|must be more specific than $target""" :: Nil

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"
end TooUnspecific
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 @@ -421,7 +425,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,7 +96,10 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
|
|def main = "foo".iden@@
|""".stripMargin,
"""|identity: String (implicit)
"""|identity: String (implicit)
|indent(x$0: Int): String
|stripIndent(): String
|isDefinedAt(idx: Int): Boolean
|""".stripMargin // identity2 won't be available
)

Expand Down Expand Up @@ -152,7 +158,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 +181,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 +270,8 @@ class CompletionExtensionSuite extends BaseCompletionSuite:
| def main = 100.inc@@
|""".stripMargin,
"""|incr: Int (extension)
|asInstanceOf[X0]: X0
|isInstanceOf[X0]: Boolean
|""".stripMargin
)

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

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

Expand All @@ -304,6 +318,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 +407,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 +430,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 @@ -691,28 +691,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
Loading