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 "add info implementation to pc" to LTS #20980

Merged
merged 1 commit into from
Jul 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 @@ -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 @@ -1128,7 +1128,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