Skip to content

Commit 37c69d8

Browse files
committed
gopls/internal/lsp/source: references: support exported methods
This change extends referencesV2 to support exported methods, avoiding the need to fall back to the old type-check-the-world implementation. (The latter is not quite dead code yet.) A references query on a method (C).F will compute all methods I.F of interfaces that correspond to it, by doing a lookup in the method set index used by the global "implementations" operation. The found methods are then used to augment the set of targets in the global search. The methodsets index has been extended to save the (PkgPath, objectpath) pair for each method, since these are the needed inputs to the global references query. (The method's package is not necessarily the same as that of the enclosing type's package due to embedding.) The current encoding increases the index size by about 10%. I suspect we could do better by exploiting redundancy between the objectpath and fingerprint, but there's no urgency. Also: - typeDeclPosition no longer returns methodID, since it can be derived later. - ensure that pkg.xrefs is always set by doTypeCheck. Change-Id: I4037dcfc1a2a710f0cb7a2f5268527e77ebfcd79 Reviewed-on: https://go-review.googlesource.com/c/tools/+/463141 Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Alan Donovan <adonovan@google.com> gopls-CI: kokoro <noreply+kokoro@google.com>
1 parent f3c36a2 commit 37c69d8

File tree

7 files changed

+280
-171
lines changed

7 files changed

+280
-171
lines changed

gopls/internal/lsp/cache/check.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,6 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
432432
}
433433
pkg.diagnostics = append(pkg.diagnostics, depsErrors...)
434434

435-
// Build index of outbound cross-references.
436-
pkg.xrefs = xrefs.Index(pkg)
437-
438435
return pkg, nil
439436
}
440437

@@ -488,8 +485,10 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
488485
if m.PkgPath == "unsafe" {
489486
// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
490487
// race to Unsafe.completed.
488+
// TODO(adonovan): factor (tail-merge) with the normal control path.
491489
pkg.types = types.Unsafe
492490
pkg.methodsets = methodsets.NewIndex(pkg.fset, pkg.types)
491+
pkg.xrefs = xrefs.Index(pkg)
493492
return pkg, nil
494493
}
495494

@@ -574,6 +573,9 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
574573
// Build global index of method sets for 'implementations' queries.
575574
pkg.methodsets = methodsets.NewIndex(pkg.fset, pkg.types)
576575

576+
// Build global index of outbound cross-references.
577+
pkg.xrefs = xrefs.Index(pkg)
578+
577579
// If the context was cancelled, we may have returned a ton of transient
578580
// errors to the type checker. Swallow them.
579581
if ctx.Err() != nil {

gopls/internal/lsp/source/implementation2.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ func Implementation(ctx context.Context, snapshot Snapshot, f FileHandle, pp pro
7272
func implementations2(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Position) ([]protocol.Location, error) {
7373

7474
// Type-check the query package, find the query identifier,
75-
// and locate the type/method declaration it refers to.
76-
declPosn, methodID, err := typeDeclPosition(ctx, snapshot, fh.URI(), pp)
75+
// and locate the type or method declaration it refers to.
76+
declPosn, err := typeDeclPosition(ctx, snapshot, fh.URI(), pp)
7777
if err != nil {
7878
return nil, err
7979
}
@@ -127,13 +127,15 @@ func implementations2(ctx context.Context, snapshot Snapshot, fh FileHandle, pp
127127
// Is the selected identifier a type name or method?
128128
// (For methods, report the corresponding method names.)
129129
var queryType types.Type
130+
var queryMethodID string
130131
switch obj := obj.(type) {
131132
case *types.TypeName:
132133
queryType = obj.Type()
133134
case *types.Func:
134135
// For methods, use the receiver type, which may be anonymous.
135136
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
136137
queryType = recv.Type()
138+
queryMethodID = obj.Id()
137139
}
138140
default:
139141
return nil, fmt.Errorf("%s is not a type or method", id.Name)
@@ -184,7 +186,7 @@ func implementations2(ctx context.Context, snapshot Snapshot, fh FileHandle, pp
184186
for _, localPkg := range localPkgs {
185187
localPkg := localPkg
186188
group.Go(func() error {
187-
localLocs, err := localImplementations(ctx, snapshot, localPkg, queryType, methodID)
189+
localLocs, err := localImplementations(ctx, snapshot, localPkg, queryType, queryMethodID)
188190
if err != nil {
189191
return err
190192
}
@@ -198,8 +200,8 @@ func implementations2(ctx context.Context, snapshot Snapshot, fh FileHandle, pp
198200
for _, globalPkg := range globalPkgs {
199201
globalPkg := globalPkg
200202
group.Go(func() error {
201-
for _, loc := range globalPkg.MethodSetsIndex().Search(key, methodID) {
202-
loc := loc
203+
for _, res := range globalPkg.MethodSetsIndex().Search(key, queryMethodID) {
204+
loc := res.Location
203205
// Map offsets to protocol.Locations in parallel (may involve I/O).
204206
group.Go(func() error {
205207
ploc, err := offsetToLocation(ctx, snapshot, loc.Filename, loc.Start, loc.End)
@@ -239,18 +241,17 @@ func offsetToLocation(ctx context.Context, snapshot Snapshot, filename string, s
239241
}
240242

241243
// typeDeclPosition returns the position of the declaration of the
242-
// type referred to at (uri, ppos). If it refers to a method, the
243-
// function returns the method's receiver type and ID.
244-
func typeDeclPosition(ctx context.Context, snapshot Snapshot, uri span.URI, ppos protocol.Position) (token.Position, string, error) {
244+
// type (or one of its methods) referred to at (uri, ppos).
245+
func typeDeclPosition(ctx context.Context, snapshot Snapshot, uri span.URI, ppos protocol.Position) (token.Position, error) {
245246
var noPosn token.Position
246247

247248
pkg, pgf, err := PackageForFile(ctx, snapshot, uri, TypecheckFull, WidestPackage)
248249
if err != nil {
249-
return noPosn, "", err
250+
return noPosn, err
250251
}
251252
pos, err := pgf.PositionPos(ppos)
252253
if err != nil {
253-
return noPosn, "", err
254+
return noPosn, err
254255
}
255256

256257
// This function inherits the limitation of its predecessor in
@@ -265,15 +266,14 @@ func typeDeclPosition(ctx context.Context, snapshot Snapshot, uri span.URI, ppos
265266
// TODO(adonovan): simplify: use objectsAt?
266267
path := pathEnclosingObjNode(pgf.File, pos)
267268
if path == nil {
268-
return noPosn, "", ErrNoIdentFound
269+
return noPosn, ErrNoIdentFound
269270
}
270271
id, ok := path[0].(*ast.Ident)
271272
if !ok {
272-
return noPosn, "", ErrNoIdentFound
273+
return noPosn, ErrNoIdentFound
273274
}
274275

275276
// Is the object a type or method? Reject other kinds.
276-
var methodID string
277277
obj := pkg.GetTypesInfo().Uses[id]
278278
if obj == nil {
279279
// Check uses first (unlike ObjectOf) so that T in
@@ -286,19 +286,18 @@ func typeDeclPosition(ctx context.Context, snapshot Snapshot, uri span.URI, ppos
286286
// ok
287287
case *types.Func:
288288
if obj.Type().(*types.Signature).Recv() == nil {
289-
return noPosn, "", fmt.Errorf("%s is a function, not a method", id.Name)
289+
return noPosn, fmt.Errorf("%s is a function, not a method", id.Name)
290290
}
291-
methodID = obj.Id()
292291
case nil:
293-
return noPosn, "", fmt.Errorf("%s denotes unknown object", id.Name)
292+
return noPosn, fmt.Errorf("%s denotes unknown object", id.Name)
294293
default:
295294
// e.g. *types.Var -> "var".
296295
kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
297-
return noPosn, "", fmt.Errorf("%s is a %s, not a type", id.Name, kind)
296+
return noPosn, fmt.Errorf("%s is a %s, not a type", id.Name, kind)
298297
}
299298

300299
declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
301-
return declPosn, methodID, nil
300+
return declPosn, nil
302301
}
303302

304303
// localImplementations searches within pkg for declarations of all

gopls/internal/lsp/source/methodsets/methodsets.go

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import (
5151
"strconv"
5252
"strings"
5353

54+
"golang.org/x/tools/go/types/objectpath"
5455
"golang.org/x/tools/gopls/internal/lsp/safetoken"
5556
"golang.org/x/tools/internal/typeparams"
5657
)
@@ -72,13 +73,6 @@ func NewIndex(fset *token.FileSet, pkg *types.Package) *Index {
7273
//
7374
// Conversion to protocol (UTF-16) form is done by the caller after a
7475
// search, not during index construction.
75-
// TODO(adonovan): opt: reconsider this choice, if FileHandles, not
76-
// ParsedGoFiles were to provide ColumnMapper-like functionality.
77-
// (Column mapping is currently associated with parsing,
78-
// but non-parsed and even non-Go files need it too.)
79-
// Since type checking requires reading (but not parsing) all
80-
// dependencies' Go files, we could do the conversion at type-checking
81-
// time at little extra cost in that case.
8276
type Location struct {
8377
Filename string
8478
Start, End int // byte offsets
@@ -93,19 +87,30 @@ type Key struct {
9387
// KeyOf returns the search key for the method sets of a given type.
9488
// It returns false if the type has no methods.
9589
func KeyOf(t types.Type) (Key, bool) {
96-
mset := methodSetInfo(func(types.Object) (_ gobPosition) { return }, t, gobPosition{})
90+
mset := methodSetInfo(t, nil)
9791
if mset.Mask == 0 {
9892
return Key{}, false // no methods
9993
}
10094
return Key{mset}, true
10195
}
10296

97+
// A Result reports a matching type or method in a method-set search.
98+
type Result struct {
99+
Location Location // location of the type or method
100+
101+
// methods only:
102+
PkgPath string // path of declaring package (may differ due to embedding)
103+
ObjectPath objectpath.Path // path of method within declaring package
104+
}
105+
103106
// Search reports each type that implements (or is implemented by) the
104107
// type that produced the search key. If methodID is nonempty, only
105108
// that method of each type is reported.
106-
// The result is the location of each type or method.
107-
func (index *Index) Search(key Key, methodID string) []Location {
108-
var locs []Location
109+
//
110+
// The result does not include the error.Error method.
111+
// TODO(adonovan): give this special case a more systematic treatment.
112+
func (index *Index) Search(key Key, methodID string) []Result {
113+
var results []Result
109114
for _, candidate := range index.pkg.MethodSets {
110115
// Traditionally this feature doesn't report
111116
// interface/interface elements of the relation.
@@ -125,19 +130,34 @@ func (index *Index) Search(key Key, methodID string) []Location {
125130
}
126131

127132
if methodID == "" {
128-
locs = append(locs, index.location(candidate.Posn))
133+
results = append(results, Result{Location: index.location(candidate.Posn)})
129134
} else {
130135
for _, m := range candidate.Methods {
131136
// Here we exploit knowledge of the shape of the fingerprint string.
132137
if strings.HasPrefix(m.Fingerprint, methodID) &&
133138
m.Fingerprint[len(methodID)] == '(' {
134-
locs = append(locs, index.location(m.Posn))
139+
140+
// Don't report error.Error among the results:
141+
// it has no true source location, no package,
142+
// and is excluded from the xrefs index.
143+
if m.PkgPath == 0 || m.ObjectPath == 0 {
144+
if methodID != "Error" {
145+
panic("missing info for" + methodID)
146+
}
147+
continue
148+
}
149+
150+
results = append(results, Result{
151+
Location: index.location(m.Posn),
152+
PkgPath: index.pkg.Strings[m.PkgPath],
153+
ObjectPath: objectpath.Path(index.pkg.Strings[m.ObjectPath]),
154+
})
135155
break
136156
}
137157
}
138158
}
139159
}
140-
return locs
160+
return results
141161
}
142162

143163
// satisfies does a fast check for whether x satisfies y.
@@ -161,7 +181,7 @@ outer:
161181

162182
func (index *Index) location(posn gobPosition) Location {
163183
return Location{
164-
Filename: index.pkg.Filenames[posn.File],
184+
Filename: index.pkg.Strings[posn.File],
165185
Start: posn.Offset,
166186
End: posn.Offset + posn.Len,
167187
}
@@ -170,54 +190,73 @@ func (index *Index) location(posn gobPosition) Location {
170190
// An indexBuilder builds an index for a single package.
171191
type indexBuilder struct {
172192
gobPackage
173-
filenameIndex map[string]int
193+
stringIndex map[string]int
174194
}
175195

176196
// build adds to the index all package-level named types of the specified package.
177197
func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index {
198+
_ = b.string("") // 0 => ""
199+
200+
objectPos := func(obj types.Object) gobPosition {
201+
posn := safetoken.StartPosition(fset, obj.Pos())
202+
return gobPosition{b.string(posn.Filename), posn.Offset, len(obj.Name())}
203+
}
204+
205+
// setindexInfo sets the (Posn, PkgPath, ObjectPath) fields for each method declaration.
206+
setIndexInfo := func(m *gobMethod, method *types.Func) {
207+
// error.Error has empty Position, PkgPath, and ObjectPath.
208+
if method.Pkg() == nil {
209+
return
210+
}
211+
212+
m.Posn = objectPos(method)
213+
m.PkgPath = b.string(method.Pkg().Path())
214+
215+
// Instantiations of generic methods don't have an
216+
// object path, so we use the generic.
217+
if p, err := objectpath.For(typeparams.OriginMethod(method)); err != nil {
218+
panic(err) // can't happen for a method of a package-level type
219+
} else {
220+
m.ObjectPath = b.string(string(p))
221+
}
222+
}
223+
178224
// We ignore aliases, though in principle they could define a
179225
// struct{...} or interface{...} type, or an instantiation of
180226
// a generic, that has a novel method set.
181227
scope := pkg.Scope()
182228
for _, name := range scope.Names() {
183229
if tname, ok := scope.Lookup(name).(*types.TypeName); ok && !tname.IsAlias() {
184-
b.add(fset, tname)
230+
if mset := methodSetInfo(tname.Type(), setIndexInfo); mset.Mask != 0 {
231+
mset.Posn = objectPos(tname)
232+
// Only record types with non-trivial method sets.
233+
b.MethodSets = append(b.MethodSets, mset)
234+
}
185235
}
186236
}
187237

188238
return &Index{pkg: b.gobPackage}
189239
}
190240

191-
func (b *indexBuilder) add(fset *token.FileSet, tname *types.TypeName) {
192-
objectPos := func(obj types.Object) gobPosition {
193-
posn := safetoken.StartPosition(fset, obj.Pos())
194-
return gobPosition{b.fileIndex(posn.Filename), posn.Offset, len(obj.Name())}
195-
}
196-
if mset := methodSetInfo(objectPos, tname.Type(), objectPos(tname)); mset.Mask != 0 {
197-
// Only record types with non-trivial method sets.
198-
b.MethodSets = append(b.MethodSets, mset)
199-
}
200-
}
201-
202-
// fileIndex returns a small integer that encodes the file name.
203-
func (b *indexBuilder) fileIndex(filename string) int {
204-
i, ok := b.filenameIndex[filename]
241+
// string returns a small integer that encodes the string.
242+
func (b *indexBuilder) string(s string) int {
243+
i, ok := b.stringIndex[s]
205244
if !ok {
206-
i = len(b.Filenames)
207-
if b.filenameIndex == nil {
208-
b.filenameIndex = make(map[string]int)
245+
i = len(b.Strings)
246+
if b.stringIndex == nil {
247+
b.stringIndex = make(map[string]int)
209248
}
210-
b.filenameIndex[filename] = i
211-
b.Filenames = append(b.Filenames, filename)
249+
b.stringIndex[s] = i
250+
b.Strings = append(b.Strings, s)
212251
}
213252
return i
214253
}
215254

216-
// methodSetInfo returns the method-set fingerprint
217-
// of a type and records its position (typePosn)
218-
// and the position of each of its methods m,
219-
// as provided by objectPos(m).
220-
func methodSetInfo(objectPos func(types.Object) gobPosition, t types.Type, typePosn gobPosition) gobMethodSet {
255+
// methodSetInfo returns the method-set fingerprint of a type.
256+
// It calls the optional setIndexInfo function for each gobMethod.
257+
// This is used during index construction, but not search (KeyOf),
258+
// to store extra information.
259+
func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) gobMethodSet {
221260
// For non-interface types, use *T
222261
// (if T is not already a pointer)
223262
// since it may have more methods.
@@ -234,10 +273,18 @@ func methodSetInfo(objectPos func(types.Object) gobPosition, t types.Type, typeP
234273
tricky = true
235274
}
236275
sum := crc32.ChecksumIEEE([]byte(fp))
237-
methods[i] = gobMethod{fp, sum, objectPos(m)}
276+
methods[i] = gobMethod{Fingerprint: fp, Sum: sum}
277+
if setIndexInfo != nil {
278+
setIndexInfo(&methods[i], m) // set Position, PkgPath, ObjectPath
279+
}
238280
mask |= 1 << uint64(((sum>>24)^(sum>>16)^(sum>>8)^sum)&0x3f)
239281
}
240-
return gobMethodSet{typePosn, types.IsInterface(t), tricky, mask, methods}
282+
return gobMethodSet{
283+
IsInterface: types.IsInterface(t),
284+
Tricky: tricky,
285+
Mask: mask,
286+
Methods: methods,
287+
}
241288
}
242289

243290
// EnsurePointer wraps T in a types.Pointer if T is a named, non-interface type.
@@ -398,7 +445,7 @@ func fingerprint(method *types.Func) (string, bool) {
398445

399446
// A gobPackage records the method set of each package-level type for a single package.
400447
type gobPackage struct {
401-
Filenames []string // see gobPosition.File
448+
Strings []string // index of strings used by gobPosition.File, gobMethod.{Pkg,Object}Path
402449
MethodSets []gobMethodSet
403450
}
404451

@@ -413,13 +460,17 @@ type gobMethodSet struct {
413460

414461
// A gobMethod records the name, type, and position of a single method.
415462
type gobMethod struct {
416-
Fingerprint string // string of form "methodID(params...)(results)"
417-
Sum uint32 // checksum of fingerprint
418-
Posn gobPosition // location of method declaration
463+
Fingerprint string // string of form "methodID(params...)(results)"
464+
Sum uint32 // checksum of fingerprint
465+
466+
// index records only (zero in KeyOf; also for index of error.Error).
467+
Posn gobPosition // location of method declaration
468+
PkgPath int // path of package containing method declaration
469+
ObjectPath int // object path of method relative to PkgPath
419470
}
420471

421472
// A gobPosition records the file, offset, and length of an identifier.
422473
type gobPosition struct {
423-
File int // index into Index.filenames
474+
File int // index into gopPackage.Strings
424475
Offset, Len int // in bytes
425476
}

0 commit comments

Comments
 (0)