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

feature: go to implementations for dependency sources #5623

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ebf5157
feature: index overridden symbols in top level
kasiaMarek Aug 31, 2023
7b95aba
feature: collect overridden symbols in Scala toplevel mtags
kasiaMarek Aug 31, 2023
950aa04
feature: index overriden symbols
kasiaMarek Sep 4, 2023
1d4dada
feature: use overridden index information for finding implementations…
kasiaMarek Sep 5, 2023
5fa2a66
store type hierarchy information
kasiaMarek Sep 6, 2023
57694a8
bugfix: cover `class A extends b.B` when indexing type hierarchy
kasiaMarek Sep 7, 2023
8a6c7b6
tests
kasiaMarek Sep 7, 2023
ed822f6
add benchmark
kasiaMarek Sep 7, 2023
97bca82
review fixes
kasiaMarek Sep 15, 2023
46fb233
review fixes
kasiaMarek Oct 27, 2023
ede5b9a
get rid of enriched document
kasiaMarek Oct 30, 2023
25bf321
use `toAbsolutePath` in JarTopLevels
kasiaMarek Oct 30, 2023
93afe40
Merge branch 'dev' into implementations-for-dependecy-sources
kasiaMarek Nov 21, 2023
83436ad
fixes after merge
kasiaMarek Nov 21, 2023
357b3c0
fix resolving if symbol is defined in the workspace
kasiaMarek Nov 23, 2023
b56da77
add find parents in pc
kasiaMarek Dec 14, 2023
1285672
delete position from classLocation
kasiaMarek Dec 14, 2023
fc5d681
Merge branch 'dev' into implementations-for-dependecy-sources
kasiaMarek Dec 15, 2023
9cd338f
Merge branch 'dev' into implementations-for-dependecy-sources
kasiaMarek Dec 27, 2023
63ae934
Merge branch 'dev' into implementations-for-dependecy-sources
kasiaMarek Jan 2, 2024
f280dd6
delete primary key in type hierarchy
kasiaMarek Jan 2, 2024
5a903ad
fix find parents for Scala 3
kasiaMarek Jan 2, 2024
c0299e5
remove usage of global symbol table from go to implementation
kasiaMarek Jan 16, 2024
22b41d3
move unused methods from implementation provider
kasiaMarek Jan 16, 2024
ecdc166
don't use scala pc for java
kasiaMarek Jan 16, 2024
1d86b8e
make sure found implementations are within build target
kasiaMarek Jan 16, 2024
8c6fdbc
fix oom + format
kasiaMarek Jan 16, 2024
e4b2833
Merge remote-tracking branch 'upstream/main' into implementations-for…
kasiaMarek Feb 9, 2024
3542435
review changes
kasiaMarek Feb 9, 2024
0c22ffc
use scala pc for java files
kasiaMarek Feb 9, 2024
2de93af
Merge remote-tracking branch 'upstream' into implementations-for-depe…
kasiaMarek Feb 20, 2024
80d64f9
make `info` return result exactly for searched symbol
kasiaMarek Feb 23, 2024
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
14 changes: 14 additions & 0 deletions metals-bench/src/main/scala/bench/MetalsBench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ class MetalsBench {
}
}

@Benchmark
@BenchmarkMode(Array(Mode.SingleShotTime))
def typeHierarchyIndex(): Unit = {
scalaDependencySources.inputs.foreach { input =>
implicit val rc: ReportContext = EmptyReportContext
new ScalaToplevelMtags(
input,
includeInnerClasses = true,
includeMembers = false,
dialects.Scala213,
).index()
}
}

@Benchmark
@BenchmarkMode(Array(Mode.SingleShotTime))
def scalaTokenize(): Unit = {
Expand Down
16 changes: 16 additions & 0 deletions metals/src/main/resources/db/migration/V5__Jar_type_hierarchy.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Type hierarchy information, e.g. symbol: "a/MyException#", extended_name: "Exception"
create table type_hierarchy(
symbol varchar not null,
parent_name varchar not null,
parent_name_offset int,
path varchar not null,
jar int,
kasiaMarek marked this conversation as resolved.
Show resolved Hide resolved
is_resolved bit,
foreign key (jar) references indexed_jar (id) on delete cascade,
primary key (jar, path, symbol, parent_name, parent_name_offset)
);

create index type_hierarchy_jar on type_hierarchy(jar);

alter table indexed_jar
add type_hierarchy_indexed bit
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ import java.nio.file.Path
case class ClassLocation(
symbol: String,
file: Option[Path],
// position of the reference to the overridden symbol for lazy resolution, e.g.
// class AClass extends @@BClass
pos: Option[Int] = None,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import scala.collection.concurrent.TrieMap
import scala.collection.mutable

import scala.meta.internal.metals.BuildTargets
import scala.meta.internal.metals.DefinitionProvider
import scala.meta.internal.semanticdb.SymbolInformation
import scala.meta.internal.symtab.GlobalSymbolTable
import scala.meta.io.AbsolutePath
Expand All @@ -26,11 +27,16 @@ final class GlobalClassTable(
def globalContextFor(
source: AbsolutePath,
implementationsInPath: ImplementationCache,
definitionProvider: DefinitionProvider,
implementationsInDependencySources: Map[String, Set[ClassLocation]],
): Option[InheritanceContext] = {
for {
symtab <- globalSymbolTableFor(source)
} yield {
calculateIndex(symtab, implementationsInPath)
calculateIndex(symtab, implementationsInPath).toGlobal(
definitionProvider,
implementationsInDependencySources,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scala.meta.internal.implementation

import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
Expand All @@ -9,17 +10,23 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.util.control.NonFatal

import scala.meta.internal.io.FileIO
import scala.meta.internal.metals.Buffers
import scala.meta.internal.metals.BuildTargets
import scala.meta.internal.metals.DefinitionProvider
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.ReportContext
import scala.meta.internal.metals.ScalaVersionSelector
import scala.meta.internal.metals.ScalaVersions
import scala.meta.internal.metals.SemanticdbFeatureProvider
import scala.meta.internal.mtags.GlobalSymbolIndex
import scala.meta.internal.mtags.IndexingResult
import scala.meta.internal.mtags.Mtags
import scala.meta.internal.mtags.OverriddenSymbol
import scala.meta.internal.mtags.ResolvedOverriddenSymbol
import scala.meta.internal.mtags.Semanticdbs
import scala.meta.internal.mtags.SymbolDefinition
import scala.meta.internal.mtags.UnresolvedOverriddenSymbol
import scala.meta.internal.mtags.{Symbol => MSymbol}
import scala.meta.internal.parsing.Trees
import scala.meta.internal.semanticdb.ClassSignature
Expand Down Expand Up @@ -53,6 +60,8 @@ final class ImplementationProvider(
private val globalTable = new GlobalClassTable(buildTargets)
private val implementationsInPath =
new ConcurrentHashMap[Path, Map[String, Set[ClassLocation]]]
private val implementationsInDependencySources =
new ConcurrentHashMap[String, Set[ClassLocation]]

override def reset(): Unit = {
implementationsInPath.clear()
Expand All @@ -70,6 +79,44 @@ final class ImplementationProvider(
)
}

def addTypeHierarchy(results: List[IndexingResult]): Unit = for {
IndexingResult(path, _, overrides) <- results
(overridesSymbol, overriddenSymbols) <- overrides
overridden <- overriddenSymbols
} addTypeHierarchyElement(path, overridesSymbol, overridden)

def addTypeHierarchyElements(
elements: List[(AbsolutePath, String, OverriddenSymbol)]
): Unit = elements.foreach { case (path, overridesSymbol, overridden) =>
addTypeHierarchyElement(path, overridesSymbol, overridden)
}

private def addTypeHierarchyElement(
path: AbsolutePath,
overridesSymbol: String,
overridden: OverriddenSymbol,
): Unit = {
def createUpdate(
newSymbol: ClassLocation
): (String, Set[ClassLocation]) => Set[ClassLocation] = {
case (_, null) => Set(newSymbol)
case (_, previous) => previous + newSymbol
}
overridden match {
case ResolvedOverriddenSymbol(symbol) =>
val update = createUpdate(
ClassLocation(overridesSymbol, Some(path.toNIO))
)
implementationsInDependencySources.compute(symbol, update(_, _))
case UnresolvedOverriddenSymbol(name, pos) =>
val update =
createUpdate(
ClassLocation(overridesSymbol, Some(path.toNIO), Some(pos))
)
implementationsInDependencySources.compute(name, update(_, _))
}
}

private def computeInheritance(
documents: TextDocuments
): Map[String, Set[ClassLocation]] = {
Expand Down Expand Up @@ -140,6 +187,8 @@ final class ImplementationProvider(
globalTable.globalContextFor(
source,
implementationsInPath.asScala.toMap,
definitionProvider,
implementationsInDependencySources.asScala.toMap,
)
// symbol is in workspace,
// we might need to search different places for related symbols
Expand Down Expand Up @@ -294,12 +343,7 @@ final class ImplementationProvider(
file <- files
locations = locationsByFile(file)
implPath = AbsolutePath(file)
implDocument <- findSemanticdb(implPath).toIterable
distance = buffer.tokenEditDistance(
implPath,
implDocument.text,
trees,
)
implDocument <- findSemanticdb(implPath, handleJars = true)
implLocation <- locations
implSymbol <- findImplementationSymbol(
parentSymbol,
Expand All @@ -315,7 +359,17 @@ final class ImplementationProvider(
source,
)
range <- implOccurrence.range
revised <- distance.toRevised(range.toLsp)
revised <-
if (implPath.isJarFileSystem) {
Some(range.toLsp)
} else {
val distance = buffer.tokenEditDistance(
implPath,
implDocument.text,
trees,
)
distance.toRevised(range.toLsp)
}
} { allLocations.add(new Location(file.toUri.toString, revised)) }
}

Expand All @@ -324,55 +378,85 @@ final class ImplementationProvider(
classContext <- inheritanceContext.toIterable
parentSymbol <- classContext.findSymbol(symbol).toIterable
symbolClass <- classFromSymbol(parentSymbol, classContext.findSymbol)
locationsByFile = findImplementation(
symbolClass.symbol,
classContext,
source.toNIO,
)
files <- locationsByFile.keySet.grouped(
Math.max(locationsByFile.size / cores, 1)
)
} yield findImplementationLocations(files, locationsByFile, parentSymbol)
} yield {
for {
locationsByFile <- findImplementation(
symbolClass.symbol,
classContext,
source.toNIO,
)
files = locationsByFile.keySet.grouped(
Math.max(locationsByFile.size / cores, 1)
)
collected <-
Future.sequence(
files.map(
findImplementationLocations(_, locationsByFile, parentSymbol)
)
)
} yield collected
}
Future.sequence(splitJobs).map { _ =>
allLocations.asScala.toSeq
}
}

private def findSemanticdb(fileSource: AbsolutePath): Option[TextDocument] = {
private def findSemanticdb(
fileSource: AbsolutePath,
handleJars: Boolean = false,
): Option[TextDocument] = {
if (fileSource.isJarFileSystem)
None
if (!handleJars) None
else Some(semanticdbForJarFile(fileSource))
else
semanticdbs
.textDocument(fileSource)
.documentIncludingStale
}

private def semanticdbForJarFile(fileSource: AbsolutePath) = {
val dialect = ScalaVersions.dialectForDependencyJar(fileSource.filename)
FileIO.slurp(fileSource, StandardCharsets.UTF_8)
val textDocument = Mtags.index(fileSource, dialect)
textDocument
}

private def findImplementation(
symbol: String,
classContext: InheritanceContext,
file: Path,
): Map[Path, Set[ClassLocation]] = {
): Future[Map[Path, Set[ClassLocation]]] = {

def loop(symbol: String, currentPath: Option[Path]): Set[ClassLocation] = {
def loop(
symbol: String,
currentPath: Option[Path],
): Future[Set[ClassLocation]] = {
val directImplementations =
classContext.getLocations(symbol).filterNot { loc =>
// we are not interested in local symbols from outside the workspace
(loc.symbol.isLocal && loc.file.isEmpty) ||
// for local symbols, inheritance should only be picked up in the same file
// otherwise there can be a name collision between files
// local1' is from file A, local2 extends local1''
// but both local2 and local1'' are from file B
// clearly, local2 shouldn't be considered for local1'
(symbol.isLocal && loc.symbol.isLocal && loc.file != currentPath)
}
directImplementations ++ directImplementations
.flatMap { loc => loop(loc.symbol, loc.file) }
classContext
.getLocations(symbol)
.map(_.filterNot { loc =>
// we are not interested in local symbols from outside the workspace
(loc.symbol.isLocal && loc.file.isEmpty) ||
// for local symbols, inheritance should only be picked up in the same file
// otherwise there can be a name collision between files
// local1' is from file A, local2 extends local1''
// but both local2 and local1'' are from file B
// clearly, local2 shouldn't be considered for local1'
(symbol.isLocal && loc.symbol.isLocal && loc.file != currentPath)
})
directImplementations.flatMap { directImplementations =>
Future
.sequence(directImplementations.map { loc =>
loop(loc.symbol, loc.file)
})
.map(rec => directImplementations ++ rec.flatten)
}
}

loop(symbol, Some(file)).groupBy(_.file).collect {
loop(symbol, Some(file)).map(_.groupBy(_.file).collect {
case (Some(path), locs) =>
path -> locs
}
})
}

private def findSymbolInformation(
Expand Down
Loading
Loading