gopls: add shadowed semantic token modifier
Fixes #75368
diff --git a/gopls/internal/golang/semtok.go b/gopls/internal/golang/semtok.go
index f0286ff..674b2cf 100644
--- a/gopls/internal/golang/semtok.go
+++ b/gopls/internal/golang/semtok.go
@@ -622,6 +622,14 @@
mods = append(mods, semtok.ModDefinition)
tok, mods = tv.appendObjectModifiers(mods, obj)
+ // obj.Parent.Parent is the surrounding scope. If we can find another declaration
+ // starting from there, we have a shadowed identifier.
+ if obj.Parent().Parent() != nil {
+ _, shadowed := obj.Parent().Parent().LookupParent(id.Name, id.Pos())
+ if shadowed != nil {
+ mods = append(mods, semtok.ModShadowed)
+ }
+ }
} else if obj, ok = tv.info.Uses[id]; ok {
// use
tok, mods = tv.appendObjectModifiers(mods, obj)
diff --git a/gopls/internal/protocol/semtok/semtok.go b/gopls/internal/protocol/semtok/semtok.go
index 9922839..e38acae 100644
--- a/gopls/internal/protocol/semtok/semtok.go
+++ b/gopls/internal/protocol/semtok/semtok.go
@@ -5,7 +5,9 @@
// The semtok package provides an encoder for LSP's semantic tokens.
package semtok
-import "sort"
+import (
+ "sort"
+)
// A Token provides the extent and semantics of a token.
type Token struct {
@@ -111,6 +113,8 @@
ModSlice Modifier = "slice"
ModString Modifier = "string"
ModStruct Modifier = "struct"
+
+ ModShadowed Modifier = "shadowed" // shadowed identifiers e.g. variable
)
// TokenModifiers is a slice of modifiers gopls will return as its server
@@ -133,6 +137,8 @@
ModSlice,
ModString,
ModStruct,
+
+ ModShadowed,
}
// Encode returns the LSP encoding of a sequence of tokens.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I think this CL is ready for review.
names := s.Names()Alan DonovanThis is potentially expensive, as it allocates + sorts. But there is no better alternative (maybe there is a proposal already for some new API that would be better in terms of perf?).
Mateusz Poliwczak(see L128)
Acknowledged
tv.shadowing[s.Lookup(name)] = struct{}{}Alan DonovanTODO: position is also important
Mateusz PoliwczakIndeed. Rather than allocate a fully populated map, why not just call scope.LookupParent for each identifier encountered? This computes the correct answer at a given position. Then measure, then optimize.
Alan DonovanOk, so the perf looks like this:
```
goos: linux
goarch: amd64
pkg: golang.org/x/tools/gopls/internal/test/integration/bench
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
│ /tmp/before │ /tmp/after │
│ sec/op │ sec/op vs base │
SemanticTokens-8 10.30m ± 2% 10.23m ± 1% ~ (p=0.307 n=25)│ /tmp/before │ /tmp/after │
│ B/op │ B/op vs base │
SemanticTokens-8 2.709Mi ± 0% 2.712Mi ± 0% +0.10% (p=0.000 n=25)│ /tmp/before │ /tmp/after │
│ allocs/op │ allocs/op vs base │
SemanticTokens-8 13.90k ± 0% 13.94k ± 0% +0.30% (p=0.000 n=25)
```There is basically no change. I think we are good here with this approach, will work on more tests now.
Mateusz PoliwczakThat's promising. How does an unmemoized LookupParent compare? Let's start with correct code and then optimize.
Alan DonovanThat's promising. How does an unmemoized LookupParent compare? Let's start with correct code and then optimize.
Oh sorry I should have mentioned that, that benchmark is with the unmemoized LookupParent approach (as in the latest patchset).
Mateusz PoliwczakGreat!
Acknowledged
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
- `"shadowing"`Oh, also need to do this.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
Oh, also need to do this.
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
and a non-standard `"shadowing"` modifier that marks declarationseach identifier that refers to a symbol that shadows an existing symbol...
For example, in Neovim, such declarations can be highlighted with
bold and underline, with:
```lua
vim.api.nvim_set_hl(0, '@lsp.mod.shadowing', { bold = true, underline = true })
```This information doesn't really belong here. We don't have a great solution for where editor-specific information should live, but I'm inclined toward moving it all out of the primary docs and into the editor/ directory.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
and a non-standard `"shadowing"` modifier that marks declarationseach identifier that refers to a symbol that shadows an existing symbol...
Done
For example, in Neovim, such declarations can be highlighted with
bold and underline, with:
```lua
vim.api.nvim_set_hl(0, '@lsp.mod.shadowing', { bold = true, underline = true })
```This information doesn't really belong here. We don't have a great solution for where editor-specific information should live, but I'm inclined toward moving it all out of the primary docs and into the editor/ directory.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
if obj, ok := tv.info.Defs[id]; obj != nil {Mateusz PoliwczakThis if/else reproduces the case analysis in the caller. Can we combine them? i.e. move L623-627 to L654 etc.
Alan DonovanDone
Sorry, when I suggested this the total amount of code seemed small, but the current draft now puts a lot of detailed logic in the `ident` case, obscuring the structure. Let's revert to having an isShadowing helper that reports whether to add the "shadow" modifier. It's ok if it re-does the Defs lookup.
// Ignore identifiers in function types, that are not part of a function
// decl or literal. There is no way to use these identifiers directly,
// so no need to report such modifiers for these.I think what this comment is trying to say is:
// A FuncType may be the signature of a Func{Decl,Lit} or just a func type.
// Any object declared within a FuncType must be a TypeParam, Param, or Result.
// The only reference to such an object can be within a function body.
// Since a func type has no function body, there can be no references to the
// object, and shadowing is irrelevant. So omit the modifier in that case.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |