Skip to content

Commit f80fb1d

Browse files
muirdmstamblerre
authored andcommitted
internal/lsp: refactor find-references and rename
The main goal is to push the package variant logic from internal/lsp into internal/lsp/source so all users of internal/lsp/source benefit. "references" and "rename" now have top-level source.References() and source.Rename() entry points (as opposed to hanging off source.Identifier()). I expanded objectsAtProtocolPos() to know about implicit objects (type switch and import spec), and to handle *ast.ImportSpec generically. This gets rid of special case handling of *types.PkgName in various places. The biggest practical benefit, though, is that "references" no longer needs to compute the objectpath for every types.Object comparison it does, instead using direct types.Object equality. This speeds up "references" and "rename" a lot. Two other notable improvements that fell out of not using source.Identifier()'s logic: - Finding references on an embedded field now shows references to the field, not the type being embedded. - Finding references on an imported object now works correctly (previously it searched the importing package's dependents rather than the imported package's dependents). Finally, I refactored findIdentifier() to use pathEnclosingObjNode() instead of astutil.PathEnclosingInterval. Now we only need a single call to get the path because pathEnclosingObjNode() has the "try pos || try pos-1" logic built in. Change-Id: I667be9bed6ad83912404b90257c5c1485b3a7025 Reviewed-on: https://go-review.googlesource.com/c/tools/+/211999 Run-TryBot: Muir Manders <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent 7042ee6 commit f80fb1d

File tree

10 files changed

+316
-351
lines changed

10 files changed

+316
-351
lines changed

internal/lsp/references.go

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -27,62 +27,23 @@ func (s *Server) references(ctx context.Context, params *protocol.ReferenceParam
2727
if fh.Identity().Kind != source.Go {
2828
return nil, nil
2929
}
30-
phs, err := snapshot.PackageHandles(ctx, fh)
30+
31+
references, err := source.References(ctx, view.Snapshot(), fh, params.Position, params.Context.IncludeDeclaration)
3132
if err != nil {
32-
return nil, nil
33+
return nil, err
3334
}
3435

35-
// Get the location of each reference to return as the result.
36-
var (
37-
locations []protocol.Location
38-
seen = make(map[span.Span]bool)
39-
lastIdent *source.IdentifierInfo
40-
)
41-
for _, ph := range phs {
42-
ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.SpecificPackageHandle(ph.ID()))
43-
if err != nil {
44-
return nil, err
45-
}
46-
47-
lastIdent = ident
48-
49-
references, err := ident.References(ctx)
36+
var locations []protocol.Location
37+
for _, ref := range references {
38+
refRange, err := ref.Range()
5039
if err != nil {
5140
return nil, err
5241
}
5342

54-
for _, ref := range references {
55-
refSpan, err := ref.Span()
56-
if err != nil {
57-
return nil, err
58-
}
59-
if seen[refSpan] {
60-
continue // already added this location
61-
}
62-
seen[refSpan] = true
63-
refRange, err := ref.Range()
64-
if err != nil {
65-
return nil, err
66-
}
67-
locations = append(locations, protocol.Location{
68-
URI: protocol.NewURI(ref.URI()),
69-
Range: refRange,
70-
})
71-
}
72-
}
73-
74-
// Only add the identifier's declaration if the client requests it.
75-
if params.Context.IncludeDeclaration && lastIdent != nil {
76-
rng, err := lastIdent.Declaration.Range()
77-
if err != nil {
78-
return nil, err
79-
}
80-
locations = append([]protocol.Location{
81-
{
82-
URI: protocol.NewURI(lastIdent.Declaration.URI()),
83-
Range: rng,
84-
},
85-
}, locations...)
43+
locations = append(locations, protocol.Location{
44+
URI: protocol.NewURI(ref.URI()),
45+
Range: refRange,
46+
})
8647
}
8748

8849
return locations, nil

internal/lsp/rename.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*pr
2626
if fh.Identity().Kind != source.Go {
2727
return nil, nil
2828
}
29-
ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.WidestPackageHandle)
30-
if err != nil {
31-
return nil, nil
32-
}
33-
edits, err := ident.Rename(ctx, params.NewName)
29+
30+
edits, err := source.Rename(ctx, snapshot, fh, params.Position, params.NewName)
3431
if err != nil {
3532
return nil, err
3633
}
34+
3735
var docChanges []protocol.TextDocumentEdit
3836
for uri, e := range edits {
3937
fh, err := snapshot.GetFile(uri)
@@ -61,13 +59,10 @@ func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRena
6159
if fh.Identity().Kind != source.Go {
6260
return nil, nil
6361
}
64-
ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.WidestPackageHandle)
65-
if err != nil {
66-
return nil, nil // ignore errors
67-
}
62+
6863
// Do not return errors here, as it adds clutter.
6964
// Returning a nil result means there is not a valid rename.
70-
item, err := ident.PrepareRename(ctx)
65+
item, err := source.PrepareRename(ctx, snapshot, fh, params.Position)
7166
if err != nil {
7267
return nil, nil // ignore errors
7368
}

internal/lsp/source/identifier.go

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,6 @@ type Declaration struct {
4646
obj types.Object
4747
}
4848

49-
func (i *IdentifierInfo) DeclarationReferenceInfo() *ReferenceInfo {
50-
return &ReferenceInfo{
51-
Name: i.Declaration.obj.Name(),
52-
mappedRange: i.Declaration.mappedRange,
53-
obj: i.Declaration.obj,
54-
ident: i.ident,
55-
pkg: i.pkg,
56-
isDeclaration: true,
57-
}
58-
}
59-
6049
// Identifier returns identifier information for a position
6150
// in a file, accounting for a potentially incomplete selector.
6251
func Identifier(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position, selectPackage PackagePolicy) (*IdentifierInfo, error) {
@@ -84,62 +73,49 @@ func Identifier(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto
8473

8574
var ErrNoIdentFound = errors.New("no identifier found")
8675

87-
func findIdentifier(ctx context.Context, snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
88-
if result, err := identifier(ctx, snapshot, pkg, file, pos); err != nil || result != nil {
89-
return result, err
90-
}
91-
// If the position is not an identifier but immediately follows
92-
// an identifier or selector period (as is common when
93-
// requesting a completion), use the path to the preceding node.
94-
ident, err := identifier(ctx, snapshot, pkg, file, pos-1)
95-
if ident == nil && err == nil {
96-
err = ErrNoIdentFound
97-
}
98-
return ident, err
99-
}
100-
101-
// identifier checks a single position for a potential identifier.
102-
func identifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
103-
var err error
104-
76+
func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
10577
// Handle import specs separately, as there is no formal position for a package declaration.
10678
if result, err := importSpec(s, pkg, file, pos); result != nil || err != nil {
10779
return result, err
10880
}
109-
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
81+
path := pathEnclosingObjNode(file, pos)
11082
if path == nil {
111-
return nil, errors.Errorf("can't find node enclosing position")
83+
return nil, ErrNoIdentFound
11284
}
11385

11486
view := s.View()
87+
88+
ident, _ := path[0].(*ast.Ident)
89+
if ident == nil {
90+
return nil, ErrNoIdentFound
91+
}
92+
11593
result := &IdentifierInfo{
11694
Snapshot: s,
11795
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
11896
pkg: pkg,
119-
ident: searchForIdent(path[0]),
97+
ident: ident,
12098
enclosing: searchForEnclosing(pkg, path),
12199
}
122100

123-
// No identifier at the given position.
124-
if result.ident == nil {
125-
return nil, nil
126-
}
127101
var wasEmbeddedField bool
128102
for _, n := range path[1:] {
129103
if field, ok := n.(*ast.Field); ok {
130104
wasEmbeddedField = len(field.Names) == 0
131105
break
132106
}
133107
}
108+
134109
result.Name = result.ident.Name
110+
var err error
135111
if result.mappedRange, err = posToMappedRange(view, pkg, result.ident.Pos(), result.ident.End()); err != nil {
136112
return nil, err
137113
}
138114
result.Declaration.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
139115
if result.Declaration.obj == nil {
140116
// If there was no types.Object for the declaration, there might be an implicit local variable
141117
// declaration in a type switch.
142-
if objs := typeSwitchVar(pkg.GetTypesInfo(), path); len(objs) > 0 {
118+
if objs := typeSwitchImplicits(pkg, path); len(objs) > 0 {
143119
// There is no types.Object for the declaration of an implicit local variable,
144120
// but all of the types.Objects associated with the usages of this variable can be
145121
// used to connect it back to the declaration.
@@ -202,18 +178,6 @@ func identifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File, po
202178
return result, nil
203179
}
204180

205-
func searchForIdent(n ast.Node) *ast.Ident {
206-
switch node := n.(type) {
207-
case *ast.Ident:
208-
return node
209-
case *ast.SelectorExpr:
210-
return node.Sel
211-
case *ast.StarExpr:
212-
return searchForIdent(node.X)
213-
}
214-
return nil
215-
}
216-
217181
func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
218182
for _, n := range path {
219183
switch n := n.(type) {
@@ -326,31 +290,69 @@ func importSpec(s Snapshot, pkg Package, file *ast.File, pos token.Pos) (*Identi
326290
return result, nil
327291
}
328292

329-
// typeSwitchVar handles the special case of a local variable implicitly defined in a type switch.
330-
// In such cases, the definition of the implicit variable will not be recorded in the *types.Info.Defs map,
331-
// but rather in the *types.Info.Implicits map.
332-
func typeSwitchVar(info *types.Info, path []ast.Node) []types.Object {
333-
if len(path) < 3 {
334-
return nil
335-
}
336-
// Check for [Ident AssignStmt TypeSwitchStmt...]
337-
if _, ok := path[0].(*ast.Ident); !ok {
293+
// typeSwitchImplicits returns all the implicit type switch objects
294+
// that correspond to the leaf *ast.Ident.
295+
func typeSwitchImplicits(pkg Package, path []ast.Node) []types.Object {
296+
ident, _ := path[0].(*ast.Ident)
297+
if ident == nil {
338298
return nil
339299
}
340-
if _, ok := path[1].(*ast.AssignStmt); !ok {
341-
return nil
300+
301+
var (
302+
ts *ast.TypeSwitchStmt
303+
assign *ast.AssignStmt
304+
cc *ast.CaseClause
305+
obj = pkg.GetTypesInfo().ObjectOf(ident)
306+
)
307+
308+
// Walk our ancestors to determine if our leaf ident refers to a
309+
// type switch variable, e.g. the "a" from "switch a := b.(type)".
310+
Outer:
311+
for i := 1; i < len(path); i++ {
312+
switch n := path[i].(type) {
313+
case *ast.AssignStmt:
314+
// Check if ident is the "a" in "a := foo.(type)". The "a" in
315+
// this case has no types.Object, so check for ident equality.
316+
if len(n.Lhs) == 1 && n.Lhs[0] == ident {
317+
assign = n
318+
}
319+
case *ast.CaseClause:
320+
// Check if ident is a use of "a" within a case clause. Each
321+
// case clause implicitly maps "a" to a different types.Object,
322+
// so check if ident's object is the case clause's implicit
323+
// object.
324+
if obj != nil && pkg.GetTypesInfo().Implicits[n] == obj {
325+
cc = n
326+
}
327+
case *ast.TypeSwitchStmt:
328+
// Look for the type switch that owns our previously found
329+
// *ast.AssignStmt or *ast.CaseClause.
330+
331+
if n.Assign == assign {
332+
ts = n
333+
break Outer
334+
}
335+
336+
for _, stmt := range n.Body.List {
337+
if stmt == cc {
338+
ts = n
339+
break Outer
340+
}
341+
}
342+
}
342343
}
343-
sw, ok := path[2].(*ast.TypeSwitchStmt)
344-
if !ok {
344+
345+
if ts == nil {
345346
return nil
346347
}
347348

348-
var res []types.Object
349-
for _, stmt := range sw.Body.List {
350-
obj := info.Implicits[stmt.(*ast.CaseClause)]
351-
if obj != nil {
352-
res = append(res, obj)
349+
// Our leaf ident refers to a type switch variable. Fan out to the
350+
// type switch's implicit case clause objects.
351+
var objs []types.Object
352+
for _, cc := range ts.Body.List {
353+
if ccObj := pkg.GetTypesInfo().Implicits[cc]; ccObj != nil {
354+
objs = append(objs, ccObj)
353355
}
354356
}
355-
return res
357+
return objs
356358
}

0 commit comments

Comments
 (0)