Skip to content

Commit

Permalink
Backport "change mock symbol search" to LTS (#22086)
Browse files Browse the repository at this point in the history
Backports #21296 to the 3.3.5.

PR submitted by the release tooling.
[skip ci]
  • Loading branch information
WojciechMazur authored Dec 3, 2024
2 parents 1744db7 + 71dfbc0 commit 4eac650
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 34 deletions.
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 _ =>

0 comments on commit 4eac650

Please sign in to comment.