Skip to content

Commit

Permalink
add info implementation to pc (#19812)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored Mar 8, 2024
1 parent 18645ee commit e2c65e6
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import scala.meta.internal.metals.EmptyReportContext
import scala.meta.internal.metals.ReportContext
import scala.meta.internal.metals.ReportLevel
import scala.meta.internal.metals.StdReportContext
import scala.meta.internal.mtags.CommonMtagsEnrichments.*
import scala.meta.internal.pc.CompilerAccess
import scala.meta.internal.pc.DefinitionResultImpl
import scala.meta.internal.pc.EmptyCompletionList
import scala.meta.internal.pc.EmptySymbolSearch
import scala.meta.internal.pc.PresentationCompilerConfigImpl
import scala.meta.pc.*
import scala.meta.pc.{PcSymbolInformation as IPcSymbolInformation}

import dotty.tools.dotc.reporting.StoreReporter
import dotty.tools.pc.completions.CompletionProvider
Expand All @@ -34,6 +36,7 @@ import dotty.tools.pc.buildinfo.BuildInfo
import org.eclipse.lsp4j.DocumentHighlight
import org.eclipse.lsp4j.TextEdit
import org.eclipse.lsp4j as l
import scala.meta.internal.pc.SymbolInformationProvider

case class ScalaPresentationCompiler(
buildTargetIdentifier: String = "",
Expand Down Expand Up @@ -184,6 +187,21 @@ case class ScalaPresentationCompiler(
def diagnosticsForDebuggingPurposes(): ju.List[String] =
List[String]().asJava

override def info(
symbol: String
): CompletableFuture[Optional[IPcSymbolInformation]] =
compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]](
None
)(
Optional.empty(),
EmptyCancelToken,
) { access =>
SymbolInformationProvider(using access.compiler().currentCtx)
.info(symbol)
.map(_.asJava)
.asJava
}

def semanticdbTextDocument(
filename: URI,
code: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package scala.meta.internal.pc

import scala.util.control.NonFatal

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
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.pc.utils.MtagsEnrichments.metalsDealias
import dotty.tools.pc.SemanticdbSymbols
import dotty.tools.pc.utils.MtagsEnrichments.allSymbols

class SymbolInformationProvider(using Context):
private def toSymbols(
pkg: String,
parts: List[(String, Boolean)],
): List[Symbol] =
def loop(
owners: List[Symbol],
parts: List[(String, Boolean)],
): List[Symbol] =
parts match
case (head, isClass) :: tl =>
val foundSymbols =
owners.flatMap { owner =>
val next =
if isClass then owner.info.member(typeName(head))
else owner.info.member(termName(head))
next.allSymbols
}
if foundSymbols.nonEmpty then loop(foundSymbols, tl)
else Nil
case Nil => owners

val pkgSym =
if pkg == "_empty_" then requiredPackage(nme.EMPTY_PACKAGE)
else requiredPackage(pkg)
loop(List(pkgSym), parts)
end toSymbols

def info(symbol: String): Option[PcSymbolInformation] =
val index = symbol.lastIndexOf("/")
val pkg = normalizePackage(symbol.take(index + 1))

def loop(
symbol: String,
acc: List[(String, Boolean)],
): List[(String, Boolean)] =
if symbol.isEmpty() then acc.reverse
else
val newSymbol = symbol.takeWhile(c => c != '.' && c != '#')
val rest = symbol.drop(newSymbol.size)
loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc)
val names =
loop(symbol.drop(index + 1).takeWhile(_ != '('), List.empty)

val foundSymbols =
try toSymbols(pkg, names)
catch case NonFatal(e) => Nil

val (searchedSymbol, alternativeSymbols) =
foundSymbols.partition: compilerSymbol =>
SemanticdbSymbols.symbolName(compilerSymbol) == symbol

searchedSymbol match
case Nil => None
case sym :: _ =>
val classSym = if sym.isClass then sym else sym.moduleClass
val parents =
if classSym.isClass
then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName)
else Nil
val dealisedSymbol =
if sym.isAliasType then sym.info.metalsDealias.typeSymbol else sym
val classOwner =
sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module))
val overridden = sym.denot.allOverriddenSymbols.toList

val pcSymbolInformation =
PcSymbolInformation(
symbol = SemanticdbSymbols.symbolName(sym),
kind = getSymbolKind(sym),
parents = parents,
dealiasedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol),
classOwner = classOwner.map(SemanticdbSymbols.symbolName),
overriddenSymbols = overridden.map(SemanticdbSymbols.symbolName),
alternativeSymbols =
alternativeSymbols.map(SemanticdbSymbols.symbolName),
properties =
if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT)
else Nil,
)

Some(pcSymbolInformation)
end match
end info

private def getSymbolKind(sym: Symbol): PcSymbolKind =
if sym.isAllOf(Flags.JavaInterface) then PcSymbolKind.INTERFACE
else if sym.is(Flags.Trait) then PcSymbolKind.TRAIT
else if sym.isConstructor then PcSymbolKind.CONSTRUCTOR
else if sym.isPackageObject then PcSymbolKind.PACKAGE_OBJECT
else if sym.isClass then PcSymbolKind.CLASS
else if sym.is(Flags.Macro) then PcSymbolKind.MACRO
else if sym.is(Flags.Local) then PcSymbolKind.LOCAL
else if sym.is(Flags.Method) then PcSymbolKind.METHOD
else if sym.is(Flags.Param) then PcSymbolKind.PARAMETER
else if sym.is(Flags.Package) then PcSymbolKind.PACKAGE
else if sym.is(Flags.TypeParam) then PcSymbolKind.TYPE_PARAMETER
else if sym.isType then PcSymbolKind.TYPE
else PcSymbolKind.UNKNOWN_KIND

private def normalizePackage(pkg: String): String =
pkg.replace("/", ".").nn.stripSuffix(".")

end SymbolInformationProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package dotty.tools.pc.tests.info

import scala.meta.internal.jdk.CollectionConverters._
import scala.meta.pc.PcSymbolKind
import scala.meta.pc.PcSymbolProperty

import scala.meta.pc.PcSymbolInformation
import dotty.tools.pc.base.BasePCSuite
import scala.language.unsafeNulls
import org.junit.Test

class InfoSuite extends BasePCSuite {

def getInfo(symbol: String): PcSymbolInformation = {
val result = presentationCompiler.info(symbol).get()
assertEquals(true, result.isPresent(), s"no info returned for symbol $symbol")
assertNoDiff(result.get().symbol(), symbol)
result.get()
}

@Test def `list` =
val info = getInfo("scala/collection/immutable/List#")
assertEquals(true, info.properties().contains(PcSymbolProperty.ABSTRACT), s"class List should be abstract")
assertEquals(
true,
info.parents().contains("scala/collection/immutable/LinearSeq#"),
"class List should extend LinearSeq"
)

@Test def `empty-list-constructor` =
val info = getInfo("scala/collection/immutable/List.empty().")
assertNoDiff(info.classOwner(), "scala/collection/immutable/List.")
assertEquals(info.kind(), PcSymbolKind.METHOD, "List.empty() should be a method")

@Test def `assert` =
val info = getInfo("scala/Predef.assert().")
assertEquals(info.kind(), PcSymbolKind.METHOD, "assert() should be a method")
assertNoDiff(info.classOwner(), "scala/Predef.")
assertEquals(
info.alternativeSymbols().asScala.mkString("\n"),
"scala/Predef.assert(+1).",
"there should be a single alternative symbol to assert()"
)

@Test def `flatMap` =
val info = getInfo("scala/collection/immutable/List#flatMap().")
assertEquals(info.kind(), PcSymbolKind.METHOD, "List.flatMap() should be a method")
assertNoDiff(info.classOwner(), "scala/collection/immutable/List#")
assertNoDiff(
info.overriddenSymbols().asScala.mkString("\n"),
"""|scala/collection/StrictOptimizedIterableOps#flatMap().
|scala/collection/IterableOps#flatMap().
|scala/collection/IterableOnceOps#flatMap().
|""".stripMargin
)
}
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ object Build {
BuildInfoPlugin.buildInfoDefaultSettings

lazy val presentationCompilerSettings = {
val mtagsVersion = "1.2.2+25-bb9dfbb9-SNAPSHOT"
val mtagsVersion = "1.2.2+44-42e0515a-SNAPSHOT"

Seq(
resolvers ++= Resolver.sonatypeOssRepos("snapshots"),
Expand Down

0 comments on commit e2c65e6

Please sign in to comment.