Skip to content

Commit 2fa621c

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/golang: fix resolution of in-package implementations
A rather egregious oversight in CL 518896 broke the query for implementations in declaring packages, when the query originates from a different package: the object was resolved *before* computing local packages. Fix this by restoring the logic for finding the query type in the same realm as the local packages. Fixes golang/go#67041 Change-Id: I5a111153154d66798e9ba87be9f0b3d0c792f973 Reviewed-on: https://go-review.googlesource.com/c/tools/+/581875 Reviewed-by: Alan Donovan <adonovan@google.com> Auto-Submit: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent bcec099 commit 2fa621c

File tree

2 files changed

+98
-15
lines changed

2 files changed

+98
-15
lines changed

gopls/internal/golang/implementation.go

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,29 @@ func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle
7373
}
7474

7575
func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) {
76+
// First, find the object referenced at the cursor by type checking the
77+
// current package.
7678
obj, pkg, err := implementsObj(ctx, snapshot, fh.URI(), pp)
7779
if err != nil {
7880
return nil, err
7981
}
8082

81-
var localPkgs []*cache.Package
83+
// If the resulting object has a position, we can expand the search to types
84+
// in the declaring package(s). In this case, we must re-type check these
85+
// packages in the same realm.
86+
var (
87+
declOffset int
88+
declURI protocol.DocumentURI
89+
localPkgs []*cache.Package
90+
)
8291
if obj.Pos().IsValid() { // no local package for error or error.Error
8392
declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
93+
declOffset = declPosn.Offset
8494
// Type-check the declaring package (incl. variants) for use
8595
// by the "local" search, which uses type information to
8696
// enumerate all types within the package that satisfy the
8797
// query type, even those defined local to a function.
88-
declURI := protocol.URIFromPath(declPosn.Filename)
98+
declURI = protocol.URIFromPath(declPosn.Filename)
8999
declMPs, err := snapshot.MetadataForFile(ctx, declURI)
90100
if err != nil {
91101
return nil, err
@@ -104,20 +114,25 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
104114
}
105115
}
106116

117+
pkg = nil // no longer used
118+
107119
// Is the selected identifier a type name or method?
108120
// (For methods, report the corresponding method names.)
109-
var queryType types.Type
110-
var queryMethodID string
111-
switch obj := obj.(type) {
112-
case *types.TypeName:
113-
queryType = obj.Type()
114-
case *types.Func:
115-
// For methods, use the receiver type, which may be anonymous.
116-
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
117-
queryType = recv.Type()
118-
queryMethodID = obj.Id()
121+
//
122+
// This logic is reused for local queries.
123+
typeOrMethod := func(obj types.Object) (types.Type, string) {
124+
switch obj := obj.(type) {
125+
case *types.TypeName:
126+
return obj.Type(), ""
127+
case *types.Func:
128+
// For methods, use the receiver type, which may be anonymous.
129+
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
130+
return recv.Type(), obj.Id()
131+
}
119132
}
133+
return nil, ""
120134
}
135+
queryType, queryMethodID := typeOrMethod(obj)
121136
if queryType == nil {
122137
return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
123138
}
@@ -169,11 +184,42 @@ func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Hand
169184
)
170185
// local search
171186
for _, localPkg := range localPkgs {
172-
localPkg := localPkg
187+
// The localImplementations algorithm assumes needle and haystack
188+
// belong to a single package (="realm" of types symbol identities),
189+
// so we need to recompute obj for each local package.
190+
// (By contrast the global algorithm is name-based.)
191+
declPkg := localPkg
173192
group.Go(func() error {
174-
localLocs, err := localImplementations(ctx, snapshot, localPkg, queryType, queryMethodID)
193+
pkgID := declPkg.Metadata().ID
194+
declFile, err := declPkg.File(declURI)
195+
if err != nil {
196+
return err // "can't happen"
197+
}
198+
199+
// Find declaration of corresponding object
200+
// in this package based on (URI, offset).
201+
pos, err := safetoken.Pos(declFile.Tok, declOffset)
202+
if err != nil {
203+
return err // also "can't happen"
204+
}
205+
// TODO(adonovan): simplify: use objectsAt?
206+
path := pathEnclosingObjNode(declFile.File, pos)
207+
if path == nil {
208+
return ErrNoIdentFound // checked earlier
209+
}
210+
id, ok := path[0].(*ast.Ident)
211+
if !ok {
212+
return ErrNoIdentFound // checked earlier
213+
}
214+
// Shadow obj, queryType, and queryMethodID in this package.
215+
obj := declPkg.TypesInfo().ObjectOf(id) // may be nil
216+
queryType, queryMethodID := typeOrMethod(obj)
217+
if queryType == nil {
218+
return fmt.Errorf("querying method sets in package %q: %v", pkgID, err)
219+
}
220+
localLocs, err := localImplementations(ctx, snapshot, declPkg, queryType, queryMethodID)
175221
if err != nil {
176-
return err
222+
return fmt.Errorf("querying local implementations %q: %v", pkgID, err)
177223
}
178224
locsMu.Lock()
179225
locs = append(locs, localLocs...)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
This test verifies that implementations uses the correct object when querying
2+
local implementations . As described in golang/go#67041), a bug led to it
3+
comparing types from different realms.
4+
5+
-- go.mod --
6+
module example.com
7+
8+
go 1.18
9+
10+
-- a/a.go --
11+
package a
12+
13+
type A struct{}
14+
15+
type Aer interface { //@loc(Aer, "Aer")
16+
GetA() A
17+
}
18+
19+
type X struct{} //@loc(X, "X")
20+
21+
func (X) GetA() A
22+
23+
-- a/a_test.go --
24+
package a
25+
26+
// Verify that we also find implementations in a test variant.
27+
type Y struct{} //@loc(Y, "Y")
28+
29+
func (Y) GetA() A
30+
-- b/b.go --
31+
package b
32+
33+
import "example.com/a"
34+
35+
var _ a.X //@implementation("X", Aer)
36+
37+
var _ a.Aer //@implementation("Aer", X, Y)

0 commit comments

Comments
 (0)