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

Backport "change mock symbol search" to LTS #22086

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.core.Names.*
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.pc.utils.InteractiveEnrichments.companion

class CompilerSearchVisitor(
visitSymbol: Symbol => Boolean
Expand Down Expand Up @@ -91,11 +92,12 @@ class CompilerSearchVisitor(
range: org.eclipse.lsp4j.Range
): Int =
val gsym = SemanticdbSymbols.inverseSemanticdbSymbol(symbol).headOption
gsym
.filter(isAccessible)
.map(visitSymbol)
.map(_ => 1)
.getOrElse(0)
val matching = for
sym0 <- gsym.toList
sym <- if sym0.companion.is(Flags.Synthetic) then List(sym0, sym0.companion) else List(sym0)
if isAccessible(sym)
yield visitSymbol(sym)
matching.size

def shouldVisitPackage(pkg: String): Boolean =
isAccessible(requiredPackage(normalizePackage(pkg)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -937,3 +937,13 @@ class CompletionWorkspaceSuite extends BaseCompletionSuite:
|""".stripMargin,
""
)

@Test def `metals-i6593` =
check(
"""|package a:
| class UniqueObject
|package b:
| val i = Uniq@@
|""".stripMargin,
"UniqueObject(): UniqueObject - a"
)
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@
package dotty.tools.pc.utils

import dotty.tools.dotc.ast.untpd.*
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.pc.CompilerSearchVisitor
import dotty.tools.pc.utils.InteractiveEnrichments.decoded

import java.io.File
import java.nio.file.Paths

import scala.collection.mutable
import scala.meta.internal.metals.{
CompilerVirtualFileParams,
Fuzzy,
WorkspaceSymbolQuery
}
import scala.meta.pc.SymbolSearchVisitor
import scala.language.unsafeNulls
import scala.meta.internal.metals.CompilerVirtualFileParams
import scala.meta.internal.metals.Fuzzy
import scala.meta.internal.metals.WorkspaceSymbolQuery
import scala.meta.pc.SymbolSearchVisitor

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.dotc.semanticdb.SemanticSymbolBuilder
import dotty.tools.pc.CompilerSearchVisitor
import TestingWorkspaceSearch.*

object TestingWorkspaceSearch:
def empty: TestingWorkspaceSearch = new TestingWorkspaceSearch(Nil)
class Disambiguator:
val nameMap = mutable.Map[String, Int]()
def methodPart(name: String) =
val i = nameMap.getOrElse(name, 0)
nameMap.put(name, i + 1)
if i == 0 then "()."
else s"(+$i)."

case class ParentSymbol(symbol: SearchSymbol, fileName: String):
private val dis: Disambiguator = new Disambiguator
private def isPackage = symbol.lastOption.exists(_.suffix == "/")
private def isMethod = symbol.lastOption.exists(_.suffix.endsWith(")."))
private def isInit = symbol.lastOption.exists(_.name == "<init>")
private def filePackage = SymbolPart(fileName, "$package.")
private def member(part: SymbolPart)=
if isPackage then Some(symbol :+ filePackage :+ part)
else if isMethod then
if isInit then Some(symbol.dropRight(1) :+ part)
else None
else Some(symbol :+ part)
def makeMethod(newPart: String) = member(SymbolPart(newPart, dis.methodPart(newPart)))
def makeVal(newPart: String) =
member(SymbolPart(newPart, "."))
def makeTypeAlias(newPart: String) = member(SymbolPart(newPart, "#"))
def makeType(newPart: String) = symbol :+ SymbolPart(newPart, "#")
def makeTerm(newPart: String) = symbol :+ SymbolPart(newPart, ".")
def makePackage(parts: List[String], isPackageObject: Boolean = false) =
val suffix = if isPackageObject then "/package." else "/"
parts match
case "<empty>" :: Nil => List(SymbolPart("_empty_", suffix))
case list if symbol.map(_.name) == List("_empty_") => list.map(SymbolPart(_, suffix))
case list => symbol ++ list.map(SymbolPart(_, suffix))

object ParentSymbol:
def empty(fileName: String) = ParentSymbol(Nil, fileName)

case class SymbolPart(name: String, suffix: String)
type SearchSymbol = List[SymbolPart]

class TestingWorkspaceSearch(classpath: Seq[String]):
val inputs: mutable.Map[String, String] = mutable.Map.empty[String, String]
Expand All @@ -30,8 +68,41 @@ class TestingWorkspaceSearch(classpath: Seq[String]):
defaultFlags ++
List("-classpath", classpath.mkString(File.pathSeparator))

private class SymbolCollector extends UntypedTreeAccumulator[List[Tree]]:
override def apply(x: List[Tree], tree: Tree)(using Context): List[Tree] = tree :: x

private def newSymbol(tree: Tree, parent: ParentSymbol)(using Context): Option[SearchSymbol] =
tree match
case PackageDef(name, _) =>
Some(parent.makePackage(namesFromSelect(name).reverse))
case m @ ModuleDef(name, _) if m.mods.is(Flags.Package) =>
Some(parent.makePackage(List(name.decoded), isPackageObject = true))
case ModuleDef(name, _) =>
Some(parent.makeTerm(name.decoded))
case ValDef(name, _, _) =>
parent.makeVal(name.decoded)
case t @ TypeDef(name, _: Template) if !t.mods.is(Flags.Implicit) =>
Some(parent.makeType(name.decoded))
case TypeDef(name, _) =>
parent.makeTypeAlias(name.decoded)
case DefDef(name, _, _, _) =>
parent.makeMethod(name.decoded)
case _ => None

def traverse(acc: List[SearchSymbol], tree: Tree, parent: ParentSymbol)(using Context): List[SearchSymbol] =
val symbol = newSymbol(tree, parent)
val res = symbol.filter(_.lastOption.exists(_.suffix != "/")).map(_ :: acc).getOrElse(acc)
val children = foldOver(Nil, tree).reverse
val newParent = symbol.map(ParentSymbol(_, parent.fileName)).getOrElse(parent)
children.foldLeft(res)((a, c) => traverse(a, c, newParent))

val driver = new InteractiveDriver(settings)

private def namesFromSelect(select: Tree)(using Context): List[String] =
select match
case Select(qual, name) => name.decoded :: namesFromSelect(qual)
case Ident(name) => List(name.decoded)

def search(
query: WorkspaceSymbolQuery,
visitor: SymbolSearchVisitor,
Expand All @@ -41,21 +112,17 @@ class TestingWorkspaceSearch(classpath: Seq[String]):

visitor match
case visitor: CompilerSearchVisitor =>
inputs.map { (path, text) =>

val nioPath = Paths.get(path)
val uri = nioPath.toUri()
val symbols = DefSymbolCollector(driver, CompilerVirtualFileParams(uri, text)).namedDefSymbols

// We have to map symbol from this Context, to one in PresentationCompiler
// To do it we are searching it with semanticdb symbol
val semanticSymbolBuilder = SemanticSymbolBuilder()
symbols
.filter((symbol, _) => filter(symbol))
.filter((_, name) => Fuzzy.matches(query.query, name))
.map(symbol => semanticSymbolBuilder.symbolName(symbol._1))
.map(
visitor.visitWorkspaceSymbol(Paths.get(""), _, null, null)
)
}
inputs.map: (path, text) =>
val nio = Paths.get(path)
val uri = nio.toUri()
driver.run(uri, text)
val run = driver.currentCtx.run
val unit = run.units.head
val symbols = SymbolCollector().traverse(Nil, unit.untpdTree, ParentSymbol.empty(nio.getFileName().toString().stripSuffix(".scala")))
symbols.foreach: sym =>
val name = sym.last.name
if Fuzzy.matches(query.query, name)
then
val symbolsString = sym.map{ case SymbolPart(name, suffix) => name ++ suffix}.mkString
visitor.visitWorkspaceSymbol(Paths.get(""), symbolsString, null, null)
case _ =>
Loading