Skip to content

Commit 371b551

Browse files
Backport "add info implementation to pc" to LTS (#20980)
Backports #19812 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 77048cc + 64996f4 commit 371b551

File tree

4 files changed

+197
-1
lines changed

4 files changed

+197
-1
lines changed

presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala

+18
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import scala.meta.internal.metals.EmptyReportContext
1919
import scala.meta.internal.metals.ReportContext
2020
import scala.meta.internal.metals.ReportLevel
2121
import scala.meta.internal.metals.StdReportContext
22+
import scala.meta.internal.mtags.CommonMtagsEnrichments.*
2223
import scala.meta.internal.pc.CompilerAccess
2324
import scala.meta.internal.pc.DefinitionResultImpl
2425
import scala.meta.internal.pc.EmptyCompletionList
2526
import scala.meta.internal.pc.EmptySymbolSearch
2627
import scala.meta.internal.pc.PresentationCompilerConfigImpl
2728
import scala.meta.pc.*
29+
import scala.meta.pc.{PcSymbolInformation as IPcSymbolInformation}
2830

2931
import dotty.tools.dotc.reporting.StoreReporter
3032
import dotty.tools.pc.completions.CompletionProvider
@@ -34,6 +36,7 @@ import dotty.tools.pc.buildinfo.BuildInfo
3436
import org.eclipse.lsp4j.DocumentHighlight
3537
import org.eclipse.lsp4j.TextEdit
3638
import org.eclipse.lsp4j as l
39+
import scala.meta.internal.pc.SymbolInformationProvider
3740

3841
case class ScalaPresentationCompiler(
3942
buildTargetIdentifier: String = "",
@@ -184,6 +187,21 @@ case class ScalaPresentationCompiler(
184187
def diagnosticsForDebuggingPurposes(): ju.List[String] =
185188
List[String]().asJava
186189

190+
override def info(
191+
symbol: String
192+
): CompletableFuture[Optional[IPcSymbolInformation]] =
193+
compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]](
194+
None
195+
)(
196+
Optional.empty(),
197+
EmptyCancelToken,
198+
) { access =>
199+
SymbolInformationProvider(using access.compiler().currentCtx)
200+
.info(symbol)
201+
.map(_.asJava)
202+
.asJava
203+
}
204+
187205
def semanticdbTextDocument(
188206
filename: URI,
189207
code: String
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package scala.meta.internal.pc
2+
3+
import scala.util.control.NonFatal
4+
5+
import scala.meta.pc.PcSymbolKind
6+
import scala.meta.pc.PcSymbolProperty
7+
8+
import dotty.tools.dotc.core.Contexts.Context
9+
import dotty.tools.dotc.core.Denotations.Denotation
10+
import dotty.tools.dotc.core.Denotations.MultiDenotation
11+
import dotty.tools.dotc.core.Flags
12+
import dotty.tools.dotc.core.Names.*
13+
import dotty.tools.dotc.core.StdNames.nme
14+
import dotty.tools.dotc.core.Symbols.*
15+
import dotty.tools.pc.utils.MtagsEnrichments.metalsDealias
16+
import dotty.tools.pc.SemanticdbSymbols
17+
import dotty.tools.pc.utils.MtagsEnrichments.allSymbols
18+
19+
class SymbolInformationProvider(using Context):
20+
private def toSymbols(
21+
pkg: String,
22+
parts: List[(String, Boolean)],
23+
): List[Symbol] =
24+
def loop(
25+
owners: List[Symbol],
26+
parts: List[(String, Boolean)],
27+
): List[Symbol] =
28+
parts match
29+
case (head, isClass) :: tl =>
30+
val foundSymbols =
31+
owners.flatMap { owner =>
32+
val next =
33+
if isClass then owner.info.member(typeName(head))
34+
else owner.info.member(termName(head))
35+
next.allSymbols
36+
}
37+
if foundSymbols.nonEmpty then loop(foundSymbols, tl)
38+
else Nil
39+
case Nil => owners
40+
41+
val pkgSym =
42+
if pkg == "_empty_" then requiredPackage(nme.EMPTY_PACKAGE)
43+
else requiredPackage(pkg)
44+
loop(List(pkgSym), parts)
45+
end toSymbols
46+
47+
def info(symbol: String): Option[PcSymbolInformation] =
48+
val index = symbol.lastIndexOf("/")
49+
val pkg = normalizePackage(symbol.take(index + 1))
50+
51+
def loop(
52+
symbol: String,
53+
acc: List[(String, Boolean)],
54+
): List[(String, Boolean)] =
55+
if symbol.isEmpty() then acc.reverse
56+
else
57+
val newSymbol = symbol.takeWhile(c => c != '.' && c != '#')
58+
val rest = symbol.drop(newSymbol.size)
59+
loop(rest.drop(1), (newSymbol, rest.headOption.exists(_ == '#')) :: acc)
60+
val names =
61+
loop(symbol.drop(index + 1).takeWhile(_ != '('), List.empty)
62+
63+
val foundSymbols =
64+
try toSymbols(pkg, names)
65+
catch case NonFatal(e) => Nil
66+
67+
val (searchedSymbol, alternativeSymbols) =
68+
foundSymbols.partition: compilerSymbol =>
69+
SemanticdbSymbols.symbolName(compilerSymbol) == symbol
70+
71+
searchedSymbol match
72+
case Nil => None
73+
case sym :: _ =>
74+
val classSym = if sym.isClass then sym else sym.moduleClass
75+
val parents =
76+
if classSym.isClass
77+
then classSym.asClass.parentSyms.map(SemanticdbSymbols.symbolName)
78+
else Nil
79+
val dealisedSymbol =
80+
if sym.isAliasType then sym.info.metalsDealias.typeSymbol else sym
81+
val classOwner =
82+
sym.ownersIterator.drop(1).find(s => s.isClass || s.is(Flags.Module))
83+
val overridden = sym.denot.allOverriddenSymbols.toList
84+
85+
val pcSymbolInformation =
86+
PcSymbolInformation(
87+
symbol = SemanticdbSymbols.symbolName(sym),
88+
kind = getSymbolKind(sym),
89+
parents = parents,
90+
dealiasedSymbol = SemanticdbSymbols.symbolName(dealisedSymbol),
91+
classOwner = classOwner.map(SemanticdbSymbols.symbolName),
92+
overriddenSymbols = overridden.map(SemanticdbSymbols.symbolName),
93+
alternativeSymbols =
94+
alternativeSymbols.map(SemanticdbSymbols.symbolName),
95+
properties =
96+
if sym.is(Flags.Abstract) then List(PcSymbolProperty.ABSTRACT)
97+
else Nil,
98+
)
99+
100+
Some(pcSymbolInformation)
101+
end match
102+
end info
103+
104+
private def getSymbolKind(sym: Symbol): PcSymbolKind =
105+
if sym.isAllOf(Flags.JavaInterface) then PcSymbolKind.INTERFACE
106+
else if sym.is(Flags.Trait) then PcSymbolKind.TRAIT
107+
else if sym.isConstructor then PcSymbolKind.CONSTRUCTOR
108+
else if sym.isPackageObject then PcSymbolKind.PACKAGE_OBJECT
109+
else if sym.isClass then PcSymbolKind.CLASS
110+
else if sym.is(Flags.Macro) then PcSymbolKind.MACRO
111+
else if sym.is(Flags.Local) then PcSymbolKind.LOCAL
112+
else if sym.is(Flags.Method) then PcSymbolKind.METHOD
113+
else if sym.is(Flags.Param) then PcSymbolKind.PARAMETER
114+
else if sym.is(Flags.Package) then PcSymbolKind.PACKAGE
115+
else if sym.is(Flags.TypeParam) then PcSymbolKind.TYPE_PARAMETER
116+
else if sym.isType then PcSymbolKind.TYPE
117+
else PcSymbolKind.UNKNOWN_KIND
118+
119+
private def normalizePackage(pkg: String): String =
120+
pkg.replace("/", ".").nn.stripSuffix(".")
121+
122+
end SymbolInformationProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package dotty.tools.pc.tests.info
2+
3+
import scala.meta.internal.jdk.CollectionConverters._
4+
import scala.meta.pc.PcSymbolKind
5+
import scala.meta.pc.PcSymbolProperty
6+
7+
import scala.meta.pc.PcSymbolInformation
8+
import dotty.tools.pc.base.BasePCSuite
9+
import scala.language.unsafeNulls
10+
import org.junit.Test
11+
12+
class InfoSuite extends BasePCSuite {
13+
14+
def getInfo(symbol: String): PcSymbolInformation = {
15+
val result = presentationCompiler.info(symbol).get()
16+
assertEquals(true, result.isPresent(), s"no info returned for symbol $symbol")
17+
assertNoDiff(result.get().symbol(), symbol)
18+
result.get()
19+
}
20+
21+
@Test def `list` =
22+
val info = getInfo("scala/collection/immutable/List#")
23+
assertEquals(true, info.properties().contains(PcSymbolProperty.ABSTRACT), s"class List should be abstract")
24+
assertEquals(
25+
true,
26+
info.parents().contains("scala/collection/immutable/LinearSeq#"),
27+
"class List should extend LinearSeq"
28+
)
29+
30+
@Test def `empty-list-constructor` =
31+
val info = getInfo("scala/collection/immutable/List.empty().")
32+
assertNoDiff(info.classOwner(), "scala/collection/immutable/List.")
33+
assertEquals(info.kind(), PcSymbolKind.METHOD, "List.empty() should be a method")
34+
35+
@Test def `assert` =
36+
val info = getInfo("scala/Predef.assert().")
37+
assertEquals(info.kind(), PcSymbolKind.METHOD, "assert() should be a method")
38+
assertNoDiff(info.classOwner(), "scala/Predef.")
39+
assertEquals(
40+
info.alternativeSymbols().asScala.mkString("\n"),
41+
"scala/Predef.assert(+1).",
42+
"there should be a single alternative symbol to assert()"
43+
)
44+
45+
@Test def `flatMap` =
46+
val info = getInfo("scala/collection/immutable/List#flatMap().")
47+
assertEquals(info.kind(), PcSymbolKind.METHOD, "List.flatMap() should be a method")
48+
assertNoDiff(info.classOwner(), "scala/collection/immutable/List#")
49+
assertNoDiff(
50+
info.overriddenSymbols().asScala.mkString("\n"),
51+
"""|scala/collection/StrictOptimizedIterableOps#flatMap().
52+
|scala/collection/IterableOps#flatMap().
53+
|scala/collection/IterableOnceOps#flatMap().
54+
|""".stripMargin
55+
)
56+
}

project/Build.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ object Build {
11281128
BuildInfoPlugin.buildInfoDefaultSettings
11291129

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

11331133
Seq(
11341134
resolvers ++= Resolver.sonatypeOssRepos("snapshots"),

0 commit comments

Comments
 (0)