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: Improve heuritics used for fallback symbol search #6642

Merged
merged 6 commits into from
Aug 6, 2024

Conversation

tgodzik
Copy link
Contributor

@tgodzik tgodzik commented Jul 30, 2024

Previously, we would just use name when nothing else was working, which could lead to a lot of false positives.

Now, we take into account imports, current package and fully qualified names to find the correct definition.

One case this might not work is with unimports, but that should not be a big deal as both will be found only if nothing was ever compiled.

Should fix #6641

@tgodzik tgodzik requested a review from kasiaMarek July 31, 2024 18:02
@tgodzik tgodzik marked this pull request as ready for review July 31, 2024 18:02
@@ -121,7 +121,7 @@ class DefinitionLspSuite
|import org.scalatest.funsuite.AnyFunSuite/*AnyFunSuite.scala*/
|object MainSuite/*L6*/ extends AnyFunSuite/*AnyFunSuite.scala*/ {
| test/*AnyFunSuiteLike.scala*/(testName/*<no symbol>*/) {
| val condition/*L8*/ = Main/*Main.scala:5*/.message/*Main.scala:4*/.contains/*String.java*/("Hello")
| val condition/*L8*/ = Main/*Main.scala:5*/.message/*<no symbol>*/.contains/*String.java*/("Hello")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is expected since there is no actual message anymore, it was changed and the code no longer parses. We would guess it previously based on outdated information.

Comment on lines 144 to 146
* the can belong to them. We don't care about things inside of
* classes, since they need to be accessed with a select on a typed
* variable instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't care about things inside of classes...
I didn't understand that comment at first. You just can that qualifiers in select can be only packages/objects, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reworded it to state that we only currently try to guess things inside the objects.

Comment on lines 73 to 88
val proposedCurrentPackageSymbol =
guessObjectOrClass(
trees
.packageAtPosition(path, pos)
.getOrElse("_empty_") +: proposedNameParts
)
Copy link
Contributor

Choose a reason for hiding this comment

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

You're not handling package objects and things like:

object O {
  class A
  val i: A@@ = ???
}

Also, in:

package a
package b

a.<..> should also be findable.

But here you should prefer the inner scope instead of finding all...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The first thing should be found by the presentation compiler without an issue, if it's not finding it the code might be too broken.

I added handling of the second case with a test, though it looks like this is also currently mostly handled by the presentation compiler correctly, but I checked manually anyway to see if the results are the same. We could have a test suite that tries to compare both, but I think it is enough to know it's working for now.

Copy link
Contributor Author

@tgodzik tgodzik left a comment

Choose a reason for hiding this comment

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

I had to change a bunch of things, since some tests started to fail. I've put the specific changes in separate commits

@@ -73,7 +73,7 @@ class PcDefinitionProvider(val compiler: MetalsGlobal, params: OffsetParams) {
if (
symbol == null ||
symbol == NoSymbol ||
symbol.isErroneous
symbol.isError
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an interesting change, thanks to this we got back the symbols at definitions in the fallback change. Without this change it would not go to definition if the symbol had a type error, which from the definition standpoint is not important. What is important is to know if it's an error symbol, which would not have any position to go to.

@@ -86,10 +86,14 @@ class PcDefinitionProvider(val compiler: MetalsGlobal, params: OffsetParams) {
symbol.pos.isDefined &&
symbol.pos.source.eq(unit.source)
) {
val namePos =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks to this we will have the same result from semanticdb and from presentation compiler. I think previously it was focused, because we would get wrong ranges on parameters, but I fixed it.

Without this fixed, one of the fallback references tests started failing because go to definition would return a result that could not enclose the cursor position so for:

val iamad@@efinition = 1

presentation compiler would return:

val <<>>iamadefinition = 1

and we wouldn't be able to know if the position of the cursor is at the definition (so we should find references)

@@ -108,9 +108,9 @@ class DefinitionLspSuite
|import java.util.concurrent.Future/*Future.java*/ // unused
|import scala.util.Failure/*Try.scala*/ // unused
|object Main/*L5*/ extends App/*App.scala*/ {
| val helloMessage/*<no symbol>*/ = Message/*Message.java:1*/.message/*Message.java:2*/
| val helloMessage/*L6*/ = Message/*Message.java:1*/.message/*Message.java:2*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks to the change I did presentation compiler can recover enough to know this is a separate symbol, though it will not manage to get a sensible type for it, which is fine.

@tgodzik
Copy link
Contributor Author

tgodzik commented Aug 2, 2024

This should be good to review, the issues I showed on the demo earlier today are not related to the changes.

tgodzik added 5 commits August 2, 2024 16:04
Previously, we would just use name when nothing else was working, which could lead to a lot of false positives.

Now, we take into account imports, current package and fully qualified names to find the correct definition.

One case this might not work is with unimports, but that should not be a big deal as both will be found only if nothing was ever compiled.
…essing

Turns out Scaladoc had a similar functionality, though I improved it a tiny bit and moved it to Symbol class.
Otherwise when looking for references with only presentation compiler working we wouldn't get the correct positions we expect.
@tgodzik
Copy link
Contributor Author

tgodzik commented Aug 2, 2024

Ach, actually I moved the tests to slow tests to have them run for both Scala 2 and 3

@tgodzik tgodzik merged commit d4f0e1e into scalameta:main Aug 6, 2024
22 of 24 checks passed
@tgodzik tgodzik deleted the fix-heuristic branch August 6, 2024 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove Go To Definition fallback behaviour
2 participants