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

improvement: use pc for go to def when stale semanticdb #7028

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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 @@ -4,6 +4,7 @@ import java.{util => ju}

import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.util.Try

import scala.meta.inputs.Input
import scala.meta.inputs.Position.Range
Expand Down Expand Up @@ -59,7 +60,6 @@ final class DefinitionProvider(
scalaVersionSelector: ScalaVersionSelector,
saveDefFileToDisk: Boolean,
sourceMapper: SourceMapper,
warnings: () => Warnings,
)(implicit ec: ExecutionContext, rc: ReportContext) {

private val fallback = new FallbackDefinitionProvider(trees, index)
Expand All @@ -78,50 +78,62 @@ final class DefinitionProvider(
val scaladocDefinitionProvider =
new ScaladocDefinitionProvider(buffers, trees, destinationProvider)

private def isAmmonnite(path: AbsolutePath): Boolean =
path.isAmmoniteScript && buildTargets
.inverseSources(path)
.flatMap(buildTargets.targetData)
.exists(_.isAmmonite)

def definition(
path: AbsolutePath,
params: TextDocumentPositionParams,
token: CancelToken,
): Future[DefinitionResult] = {
val fromSemanticdb =
semanticdbs().textDocument(path).documentIncludingStale
val fromSnapshot = fromSemanticdb match {
case Some(doc) =>
definitionFromSnapshot(path, params, doc)
case _ =>
DefinitionResult.empty
}
val fromCompilerOrSemanticdb =
fromSnapshot match {
case defn if defn.isEmpty && path.isScalaFilename =>
): Future[DefinitionResult] =
for {
fromCompiler <-
if (path.isScalaFilename && !isAmmonnite(path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we just keep the current order for Ammonite? With this change we would not use compiler for Ammonite, right?

I wonder if we could just adjust tests to take that into account?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can also fix it and map generated Ammonite files to source files like we do for Scala CLI, if we want to still have Ammonite support.

Reordering is only half of a fix since for local symbols using pc will lead to generated sources. But I can also do that, if don't know where we are with Ammonite support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I raised the topic on discord again and will start pushing it. In the meantime I don't think we want to work on any fixes, which we will be removing in the future.

So if it's too much work let's just leave it as is then.

compilers().definition(params, token)
case defn @ DefinitionResult(_, symbol, _, _, querySymbol)
if symbol != querySymbol && path.isScalaFilename =>
compilers().definition(params, token).map { compilerDefn =>
if (compilerDefn.isEmpty || compilerDefn.querySymbol == querySymbol)
defn
else compilerDefn.copy(semanticdb = defn.semanticdb)
}
case defn =>
if (fromSemanticdb.isEmpty) {
warnings().noSemanticdb(path)
}
Future.successful(defn)
else Future.successful(DefinitionResult.empty)
} yield {
if (!fromCompiler.isEmpty) {
val pathToDef =
fromCompiler.locations.asScala.head.getUri.toAbsolutePath
fromCompiler.copy(semanticdb =
semanticdbs().textDocument(pathToDef).documentIncludingStale
)
} else {
val reportBuilder =
new DefinitionProviderReportBuilder(path, params, fromCompiler)
val fromSemanticDB =
semanticdbs()
.textDocument(path)
.documentIncludingStale
.map(definitionFromSnapshot(path, params, _))
fromSemanticDB.foreach(reportBuilder.withSemanticDBResult(_))
val result = fromSemanticDB match {
case Some(definition)
if !definition.isEmpty || definition.symbol.endsWith("/") =>
definition
case _ =>
val isScala3 =
ScalaVersions.isScala3Version(
scalaVersionSelector.scalaVersionForPath(path)
)

val fromScalaDoc =
scaladocDefinitionProvider.definition(path, params, isScala3)
fromScalaDoc.foreach(_ => reportBuilder.withFoundScaladocDef())
fromScalaDoc
.orElse(
fallback
.search(path, params.getPosition(), isScala3, reportBuilder)
)
.getOrElse(fromCompiler)
}
reportBuilder.build().foreach(rc.unsanitized.create(_))
result
}

fromCompilerOrSemanticdb.map { definition =>
if (definition.isEmpty && !definition.symbol.endsWith("/")) {
val isScala3 =
ScalaVersions.isScala3Version(
scalaVersionSelector.scalaVersionForPath(path)
)
scaladocDefinitionProvider
.definition(path, params, isScala3)
.orElse(fallback.search(path, params.getPosition(), isScala3))
.getOrElse(definition)
} else definition
}
}

def definition(
path: AbsolutePath,
Expand Down Expand Up @@ -508,3 +520,89 @@ class DestinationProvider(
}
}
}

class DefinitionProviderReportBuilder(
path: AbsolutePath,
params: TextDocumentPositionParams,
compilerDefn: DefinitionResult,
) {
private var semanticDBDefn: Option[DefinitionResult] = None

private var fallbackDefn: Option[DefinitionResult] = None
private var nonLocalGuesses: List[String] = List.empty

private var fundScaladocDef = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private var fundScaladocDef = false
private var foundScaladocDef = false


private var error: Option[Throwable] = None

def withSemanticDBResult(result: DefinitionResult): this.type = {
semanticDBDefn = Some(result)
this
}

def withFallbackResult(result: DefinitionResult): this.type = {
fallbackDefn = Some(result)
this
}

def withError(e: Throwable): this.type = {
error = Some(e)
this
}

def withNonLocalGuesses(guesses: List[String]): this.type = {
nonLocalGuesses = guesses
this
}

def withFoundScaladocDef(): this.type = {
fundScaladocDef = true
this
}

def build(): Option[Report] =
Option.when(!fundScaladocDef) {
Report(
"empty-definition",
s"""|empty definition using pc, found symbol in pc: ${compilerDefn.querySymbol}
|${semanticDBDefn match {
case None =>
s"semanticdb not found"
case Some(defn) if defn.isEmpty =>
s"empty definition using semanticdb"
case Some(defn) =>
s"found definition using semanticdb; symbol ${defn.symbol}"
}}
|${fallbackDefn.map {
case defn if defn.isEmpty =>
s"""|empty definition using fallback
|non-local guesses:
|${nonLocalGuesses.mkString("\t -", "\n\t -", "")}
|"""
case defn =>
s"found definition using fallback; symbol ${defn.symbol}"
}}
|Document text:
|
|```scala
|${Try(path.readText).toOption.getOrElse("Failed to read text")}
|```
|""".stripMargin,
s"empty definition using pc, found symbol in pc: ${compilerDefn.querySymbol}",
path = Some(path.toURI),
id = querySymbol.orElse(
Some(s"${path.toURI}:${params.getPosition().getLine()}")
),
error = error,
)
}

private def querySymbol: Option[String] =
compilerDefn.querySymbol match {
case "" =>
semanticDBDefn
.map(_.querySymbol)
.orElse(fallbackDefn.map(_.querySymbol))
case q => Some(q)
}
}
Loading
Loading