Skip to content

Commit 6520870

Browse files
committed
gopls/internal/lsp/source/typerefs: allow for duplicate decls
Allow for duplicate declarations in the typerefs.Refs algorithm. It ended up being simplest to treat duplicate declarations the same as methods. Also add a test covering some invalid declarations. Change-Id: Ib546ee39427245dbb42ec7618e877f18407b7075 Reviewed-on: https://go-review.googlesource.com/c/tools/+/480916 gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Auto-Submit: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent 902fdca commit 6520870

File tree

2 files changed

+76
-70
lines changed

2 files changed

+76
-70
lines changed

gopls/internal/lsp/source/typerefs/refs.go

Lines changed: 31 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ import (
1717

1818
// declInfo holds information about a single declaration.
1919
type declInfo struct {
20-
name string // declaration name, for debugging only
2120
node ast.Node // "declaring node" for this decl, to be traversed
2221
tparams map[string]bool // names declared by type parameters within this declaration
2322
file *ast.File // file containing this decl, for local imports
24-
methods []*declInfo // method declarations for type declarations
2523
}
2624

2725
// Refs analyzes local syntax of the provided ParsedGoFiles to extract
@@ -35,12 +33,17 @@ type declInfo struct {
3533
// does not) represent.
3634
func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.ImportPath]*source.Metadata, pkgIndex *PackageIndex) map[string][]Ref {
3735
var (
38-
// decls collects nodes that define top-level objects, to be walked
39-
// later.
36+
// decls collects declaration nodes that collectively define the type of
37+
// each name in the package scope.
4038
//
41-
// TODO(rfindley): we may have duplicate decls with the same name for
42-
// invalid code. We must handle this case by take the union of edges.
43-
decls = make(map[string]*declInfo)
39+
// - For valid code, there may be multiple declarations recorded when that
40+
// name is a type that has methods.
41+
// - For invalid code, there may also be multiple declarations recorded due
42+
// to duplicate declarations.
43+
//
44+
// In either case, the algorithm is the same: we walk all declarations for
45+
// each name to collect referring identifiers.
46+
decls = make(map[string][]*declInfo)
4447

4548
// localImports holds local import information, per file. The value is a
4649
// slice because multiple packages may be referenced by a given name in the
@@ -92,22 +95,11 @@ func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.I
9295
if name == "_" {
9396
continue
9497
}
95-
info := decls[name] // TODO(rfindley): handle the case of duplicate decls.
96-
if info == nil {
97-
info = &declInfo{}
98-
decls[name] = info
99-
}
100-
// Sanity check that the root node info has not been set.
101-
//
102-
// TODO(rfindley): this panic is incorrect in the presence of
103-
// invalid code with duplicate decls.
104-
if info.node != nil || info.tparams != nil {
105-
panic(fmt.Sprintf("root node already set for %s.%s", id, name))
106-
}
107-
info.name = name
108-
info.file = file
109-
info.node = spec
110-
info.tparams = tparamsMap(typeparams.ForTypeSpec(spec))
98+
decls[name] = append(decls[name], &declInfo{
99+
node: spec,
100+
tparams: tparamsMap(typeparams.ForTypeSpec(spec)),
101+
file: file,
102+
})
111103
}
112104

113105
case token.VAR, token.CONST:
@@ -117,8 +109,7 @@ func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.I
117109
if name.Name == "_" {
118110
continue
119111
}
120-
// TODO(rfindley): handle dupes here too.
121-
decls[name.Name] = &declInfo{node: spec, name: name.Name, file: file}
112+
decls[name.Name] = append(decls[name.Name], &declInfo{node: spec, file: file})
122113
}
123114
}
124115
}
@@ -127,40 +118,32 @@ func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.I
127118
if d.Name.Name == "_" {
128119
continue
129120
}
130-
// TODO(rfindley): handle dupes here too.
131121
// This check for NumFields() > 0 is consistent with go/types, which
132122
// reports an error but treats the declaration like a normal function
133123
// when Recv is non-nil but empty (as in func () f()).
134124
if d.Recv.NumFields() > 0 {
135125
// Method. Associate it with the receiver.
136126
_, id, tparams := unpackRecv(d.Recv.List[0].Type)
137-
recvInfo := decls[id.Name]
138-
if recvInfo == nil {
139-
recvInfo = &declInfo{}
140-
decls[id.Name] = recvInfo
141-
}
142127
methodInfo := &declInfo{
143-
name: d.Name.Name,
144128
node: d,
145129
file: file,
146130
}
147131
if len(tparams) > 0 {
148132
methodInfo.tparams = make(map[string]bool)
149-
for _, id := range tparams {
150-
if id.Name != "_" {
151-
methodInfo.tparams[id.Name] = true
133+
for _, tparam := range tparams {
134+
if tparam.Name != "_" {
135+
methodInfo.tparams[tparam.Name] = true
152136
}
153137
}
154138
}
155-
recvInfo.methods = append(recvInfo.methods, methodInfo)
139+
decls[id.Name] = append(decls[id.Name], methodInfo)
156140
} else {
157141
// Non-method.
158-
decls[d.Name.Name] = &declInfo{
159-
name: d.Name.Name,
142+
decls[d.Name.Name] = append(decls[d.Name.Name], &declInfo{
160143
node: d,
161-
file: file,
162144
tparams: tparamsMap(typeparams.ForFuncType(d.Type)),
163-
}
145+
file: file,
146+
})
164147
}
165148
}
166149
}
@@ -169,10 +152,7 @@ func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.I
169152
// mappedRefs maps each name in this package to the set
170153
// of (pkg, name) pairs it references.
171154
mappedRefs := make(map[string]map[source.PackageID]map[string]bool)
172-
for name, rootInfo := range decls {
173-
// Consider method declarations to be part of the type declaration.
174-
infos := append([]*declInfo{rootInfo}, rootInfo.methods...)
175-
155+
for name, infos := range decls {
176156
// recordEdge records the (id, name)->(id2, name) edge.
177157
recordEdge := func(id2 source.PackageID, name2 string) {
178158
pkgRefs, ok := mappedRefs[name]
@@ -206,32 +186,20 @@ func Refs(pgfs []*source.ParsedGoFile, id source.PackageID, imports map[source.I
206186
// var y = struct {F int}{}
207187
if _, ok := decls[name]; ok {
208188
recordEdge(id, name)
209-
return
210-
}
211-
212-
// name is not declared in the current package, so record edges to all
213-
// imported objects it could refer to.
214-
215-
// Conservatively, if name is exported we record an edge for every
216-
// dot-imported package.
217-
//
218-
// Even though it is an error for a local import name and
219-
// dot-imported name to collide, we don't handle this collision
220-
// here because it is so vanishingly rare to have a package
221-
// qualifier that starts with a capital letter at the same time as
222-
// having a dot import.
223-
//
224-
// Just record edges to both.
225-
if token.IsExported(name) {
189+
} else if token.IsExported(name) {
190+
// Only record an edge to dot-imported packages if there was no edge
191+
// to a local name. This assumes that there are no duplicate declarations.
226192
for _, depID := range fileImports["."] {
227193
// Conservatively, assume that this name comes from every
228194
// dot-imported package.
229195
recordEdge(depID, name)
230196
}
231197
}
232198

233-
// Similarly, if sel is exported we record an edge for every matching
234-
// import.
199+
// Record an edge to an import if it matches the name, even if that
200+
// name collides with a package level name. Unlike the case of dotted
201+
// imports, we know the package is invalid here, and choose to fail
202+
// conservatively.
235203
if sel != "" && token.IsExported(sel) {
236204
for _, depID := range fileImports[name] {
237205
recordEdge(depID, sel)

gopls/internal/lsp/source/typerefs/refs_test.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ func TestRefs(t *testing.T) {
2323
ctx := context.Background()
2424

2525
tests := []struct {
26-
label string
27-
srcs []string // source for the local package; package name must be p
28-
imports map[string]string // for simplicity: importPath -> pkgID/pkgName (we set pkgName == pkgID)
29-
want map[string][]string // decl name -> id.<decl name>
30-
go118 bool // test uses generics
26+
label string
27+
srcs []string // source for the local package; package name must be p
28+
imports map[string]string // for simplicity: importPath -> pkgID/pkgName (we set pkgName == pkgID)
29+
want map[string][]string // decl name -> id.<decl name>
30+
go118 bool // test uses generics
31+
allowErrs bool // whether we expect parsing errors
3132
}{
3233
{
3334
label: "empty package",
@@ -291,7 +292,7 @@ type z interface{}
291292
`},
292293
imports: map[string]string{"q": "q", "r": "r", "s": "t", "z": "z"},
293294
want: map[string][]string{
294-
"A": {"p.z", "q.Q", "r.R"},
295+
"A": {"p.z", "q.Q", "r.R", "z.Z"},
295296
"B": {"t.T"},
296297
"y": {"q.V"},
297298
},
@@ -383,6 +384,43 @@ type E int
383384
},
384385
go118: true,
385386
},
387+
{
388+
label: "duplicate decls",
389+
srcs: []string{`package p
390+
391+
import "a"
392+
393+
type a a.A
394+
type b int
395+
type C a.A
396+
func (C) Foo(x) {} // invalid parameter, but that does not matter
397+
type C b
398+
func (C) Bar(y) {} // invalid parameter, but that does not matter
399+
400+
var x, y int
401+
`},
402+
imports: map[string]string{"a": "a", "b": "b"}, // "b" import should not matter, since it isn't in this file
403+
want: map[string][]string{
404+
"a": {"a.A", "p.a"},
405+
"C": {"a.A", "p.a", "p.b", "p.x", "p.y"},
406+
},
407+
},
408+
{
409+
label: "invalid decls",
410+
srcs: []string{`package p
411+
412+
type A B
413+
414+
func () Foo(B){}
415+
416+
var B
417+
`},
418+
want: map[string][]string{
419+
"A": {"p.B"},
420+
"Foo": {"p.B"},
421+
},
422+
allowErrs: true,
423+
},
386424
}
387425

388426
for _, test := range tests {
@@ -395,7 +433,7 @@ type E int
395433
for i, src := range test.srcs {
396434
uri := span.URI(fmt.Sprintf("file:///%d.go", i))
397435
pgf, _ := cache.ParseGoSrc(ctx, token.NewFileSet(), uri, []byte(src), source.ParseFull)
398-
if pgf.ParseErr != nil {
436+
if !test.allowErrs && pgf.ParseErr != nil {
399437
t.Fatalf("ParseGoSrc(...) returned parse errors: %v", pgf.ParseErr)
400438
}
401439
pgfs = append(pgfs, pgf)

0 commit comments

Comments
 (0)