Skip to content

Commit aae3642

Browse files
committed
gopls/internal/lsp/source: referencesV2: support unexported methods
This change adds support for unexported methods to the incremental implementation of the 'references' operation, which previously had to fall back to a type-check-the-world strategy for methods. (Exported methods are still to do.) The implementation strategy is to record the receiver type R of the target method M, and search types.Info.Uses for all objects that are methods of the same name as M, whose receiver is mutually assignable with R. Change-Id: I4121f34df822662ccbaac5d53f3e2a5578be5431 Reviewed-on: https://go-review.googlesource.com/c/tools/+/462915 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Alan Donovan <adonovan@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Robert Findley <rfindley@google.com>
1 parent b5d65e0 commit aae3642

File tree

3 files changed

+67
-15
lines changed

3 files changed

+67
-15
lines changed

gopls/internal/lsp/lsp_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,8 @@ func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
867867
want := make(map[protocol.Location]bool)
868868
for i, pos := range itemList {
869869
// We don't want the first result if we aren't including the declaration.
870+
// TODO(adonovan): don't assume a single declaration:
871+
// there may be >1 if corresponding methods are considered.
870872
if i == 0 && !includeDeclaration {
871873
continue
872874
}

gopls/internal/lsp/source/references2.go

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"golang.org/x/tools/go/types/objectpath"
3131
"golang.org/x/tools/gopls/internal/lsp/protocol"
3232
"golang.org/x/tools/gopls/internal/lsp/safetoken"
33+
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
3334
"golang.org/x/tools/gopls/internal/span"
3435
"golang.org/x/tools/internal/event"
3536
)
@@ -248,18 +249,7 @@ func ordinaryReferences(ctx context.Context, snapshot Snapshot, uri span.URI, pp
248249
break
249250
}
250251
if obj == nil {
251-
return nil, ErrNoIdentFound
252-
}
253-
254-
// If the object is a method, we need to search for all
255-
// matching implementations and/or interfaces, without
256-
// type-checking everything.
257-
//
258-
// That will require an approach such as the one sketched in
259-
// go.dev/cl/452060. Until then, we simply fall back to the
260-
// old implementation for now. TODO(adonovan): fix.
261-
if fn, ok := obj.(*types.Func); ok && fn.Type().(*types.Signature).Recv() != nil {
262-
return nil, ErrFallback
252+
return nil, ErrNoIdentFound // can't happen
263253
}
264254

265255
// nil, error, iota, or other built-in?
@@ -303,6 +293,18 @@ func ordinaryReferences(ctx context.Context, snapshot Snapshot, uri span.URI, pp
303293
var exportedObjectPath objectpath.Path
304294
if path, err := objectpath.For(obj); err == nil && obj.Exported() {
305295
exportedObjectPath = path
296+
297+
// If the object is an exported method, we need to search for
298+
// all matching implementations (using the incremental
299+
// implementation of 'implementations') and then search for
300+
// the set of corresponding methods (requiring the incremental
301+
// implementation of 'references' to be generalized to a set
302+
// of search objects).
303+
// Until then, we simply fall back to the old implementation for now.
304+
// TODO(adonovan): fix.
305+
if fn, ok := obj.(*types.Func); ok && fn.Type().(*types.Signature).Recv() != nil {
306+
return nil, ErrFallback
307+
}
306308
}
307309

308310
// If it is exported, how far need we search?
@@ -368,7 +370,7 @@ func ordinaryReferences(ctx context.Context, snapshot Snapshot, uri span.URI, pp
368370
return refs, nil
369371
}
370372

371-
// localReferences reports (concurrently) each reference to the object
373+
// localReferences reports each reference to the object
372374
// declared at the specified URI/offset within its enclosing package m.
373375
func localReferences(ctx context.Context, snapshot Snapshot, declURI span.URI, declOffset int, m *Metadata, report func(loc protocol.Location, isDecl bool)) error {
374376
pkgs, err := snapshot.TypeCheck(ctx, TypecheckFull, m.ID)
@@ -393,15 +395,57 @@ func localReferences(ctx context.Context, snapshot Snapshot, declURI span.URI, d
393395
}
394396

395397
// Report the locations of the declaration(s).
398+
// TODO(adonovan): what about for corresponding methods? Add tests.
396399
for _, node := range targets {
397400
report(mustLocation(pgf, node), true)
398401
}
399402

403+
// receiver returns the effective receiver type for method-set
404+
// comparisons for obj, if it is a method, or nil otherwise.
405+
receiver := func(obj types.Object) types.Type {
406+
if fn, ok := obj.(*types.Func); ok {
407+
if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
408+
return methodsets.EnsurePointer(recv.Type())
409+
}
410+
}
411+
return nil
412+
}
413+
414+
// If we're searching for references to a method, broaden the
415+
// search to include references to corresponding methods of
416+
// mutually assignable receiver types.
417+
// (We use a slice, but objectsAt never returns >1 methods.)
418+
var methodRecvs []types.Type
419+
var methodName string // name of an arbitrary target, iff a method
420+
for obj := range targets {
421+
if t := receiver(obj); t != nil {
422+
methodRecvs = append(methodRecvs, t)
423+
methodName = obj.Name()
424+
}
425+
}
426+
427+
// matches reports whether obj either is or corresponds to a target.
428+
// (Correspondence is defined as usual for interface methods.)
429+
matches := func(obj types.Object) bool {
430+
if targets[obj] != nil {
431+
return true
432+
} else if methodRecvs != nil && obj.Name() == methodName {
433+
if orecv := receiver(obj); orecv != nil {
434+
for _, mrecv := range methodRecvs {
435+
if concreteImplementsIntf(orecv, mrecv) {
436+
return true
437+
}
438+
}
439+
}
440+
}
441+
return false
442+
}
443+
400444
// Scan through syntax looking for uses of one of the target objects.
401445
for _, pgf := range pkg.CompiledGoFiles() {
402446
ast.Inspect(pgf.File, func(n ast.Node) bool {
403447
if id, ok := n.(*ast.Ident); ok {
404-
if used, ok := pkg.GetTypesInfo().Uses[id]; ok && targets[used] != nil {
448+
if obj, ok := pkg.GetTypesInfo().Uses[id]; ok && matches(obj) {
405449
report(mustLocation(pgf, id), false)
406450
}
407451
}
@@ -453,10 +497,14 @@ func objectsAt(info *types.Info, file *ast.File, pos token.Pos) (map[types.Objec
453497
}
454498
targets[obj] = leaf
455499
}
500+
501+
if len(targets) == 0 {
502+
return nil, fmt.Errorf("objectAt: internal error: no targets") // can't happen
503+
}
456504
return targets, nil
457505
}
458506

459-
// globalReferences reports (concurrently) each cross-package
507+
// globalReferences reports each cross-package
460508
// reference to the object identified by (pkgPath, objectPath).
461509
func globalReferences(ctx context.Context, snapshot Snapshot, m *Metadata, pkgPath PackagePath, objectPath objectpath.Path, report func(loc protocol.Location, isDecl bool)) error {
462510
// TODO(adonovan): opt: don't actually type-check here,

gopls/internal/regtest/misc/references_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ func main() {
5252

5353
// This reproduces and tests golang/go#48400.
5454
func TestReferencesPanicOnError(t *testing.T) {
55+
// Ideally this would actually return the correct answer,
56+
// instead of merely failing gracefully.
5557
const files = `
5658
-- go.mod --
5759
module mod.com

0 commit comments

Comments
 (0)