Skip to content

Commit 87d00e6

Browse files
committed
gopls/internal/lsp: separate some requests from source.Identifier
Start to unwind source.Identifier by unrolling definition, type definition, and call hierarchy handlers. Along the way, introduce a couple primitive helper functions, which may be made obsolete in the future but allowed preserving source.Identifier behavior: - referencedObject returns the object referenced by the cursor position, as defined by source.Identifier. - mapPosition is a helper to map token.Pos to MappedRange in the narrow context of a package fileset. After this change, the only remaining use of source.Identifier is for Hover, but that is a sizeable refactoring and therefore left to a subsequent CL. Updates golang/go#57987 Change-Id: Iba4b0a574e6a6d3d54253f3b4bff8fe6e13a1b15 Reviewed-on: https://go-review.googlesource.com/c/tools/+/463955 gopls-CI: kokoro <[email protected]> Run-TryBot: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent ae242ec commit 87d00e6

File tree

10 files changed

+447
-155
lines changed

10 files changed

+447
-155
lines changed

gopls/internal/lsp/definition.go

+13-21
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,33 @@ import (
1414
)
1515

1616
func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
17+
// TODO(rfindley): definition requests should be multiplexed across all views.
1718
snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
1819
defer release()
1920
if !ok {
2021
return nil, err
2122
}
22-
if snapshot.View().FileKind(fh) == source.Tmpl {
23+
switch kind := snapshot.View().FileKind(fh); kind {
24+
case source.Tmpl:
2325
return template.Definition(snapshot, fh, params.Position)
26+
case source.Go:
27+
return source.Definition(ctx, snapshot, fh, params.Position)
28+
default:
29+
return nil, fmt.Errorf("can't find definitions for file type %s", kind)
2430
}
25-
ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
26-
if err != nil {
27-
return nil, err
28-
}
29-
if ident.IsImport() && !snapshot.View().Options().ImportShortcut.ShowDefinition() {
30-
return nil, nil
31-
}
32-
var locations []protocol.Location
33-
for _, ref := range ident.Declaration.MappedRange {
34-
locations = append(locations, ref.Location())
35-
}
36-
37-
return locations, nil
3831
}
3932

4033
func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
34+
// TODO(rfindley): type definition requests should be multiplexed across all views.
4135
snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go)
4236
defer release()
4337
if !ok {
4438
return nil, err
4539
}
46-
ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
47-
if err != nil {
48-
return nil, err
49-
}
50-
if ident.Type.Object == nil {
51-
return nil, fmt.Errorf("no type definition for %s", ident.Name)
40+
switch kind := snapshot.View().FileKind(fh); kind {
41+
case source.Go:
42+
return source.TypeDefinition(ctx, snapshot, fh, params.Position)
43+
default:
44+
return nil, fmt.Errorf("can't find type definitions for file type %s", kind)
5245
}
53-
return []protocol.Location{ident.Type.MappedRange.Location()}, nil
5446
}

gopls/internal/lsp/lsp_test.go

+22-9
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@ func (r *runner) MethodExtraction(t *testing.T, start span.Span, end span.Span)
666666
}
667667
}
668668

669-
func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
669+
// TODO(rfindley): This handler needs more work. The output is still a bit hard
670+
// to read (range diffs do not format nicely), and it is too entangled with hover.
671+
func (r *runner) Definition(t *testing.T, _ span.Span, d tests.Definition) {
670672
sm, err := r.data.Mapper(d.Src.URI())
671673
if err != nil {
672674
t.Fatal(err)
@@ -676,18 +678,18 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
676678
t.Fatalf("failed for %v: %v", d.Src, err)
677679
}
678680
tdpp := protocol.LocationTextDocumentPositionParams(loc)
679-
var locs []protocol.Location
681+
var got []protocol.Location
680682
var hover *protocol.Hover
681683
if d.IsType {
682684
params := &protocol.TypeDefinitionParams{
683685
TextDocumentPositionParams: tdpp,
684686
}
685-
locs, err = r.server.TypeDefinition(r.ctx, params)
687+
got, err = r.server.TypeDefinition(r.ctx, params)
686688
} else {
687689
params := &protocol.DefinitionParams{
688690
TextDocumentPositionParams: tdpp,
689691
}
690-
locs, err = r.server.Definition(r.ctx, params)
692+
got, err = r.server.Definition(r.ctx, params)
691693
if err != nil {
692694
t.Fatalf("failed for %v: %+v", d.Src, err)
693695
}
@@ -699,8 +701,19 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
699701
if err != nil {
700702
t.Fatalf("failed for %v: %v", d.Src, err)
701703
}
702-
if len(locs) != 1 {
703-
t.Errorf("got %d locations for definition, expected 1", len(locs))
704+
dm, err := r.data.Mapper(d.Def.URI())
705+
if err != nil {
706+
t.Fatal(err)
707+
}
708+
def, err := dm.SpanLocation(d.Def)
709+
if err != nil {
710+
t.Fatal(err)
711+
}
712+
if !d.OnlyHover {
713+
want := []protocol.Location{def}
714+
if diff := cmp.Diff(want, got); diff != "" {
715+
t.Fatalf("Definition(%s) mismatch (-want +got):\n%s", d.Src, diff)
716+
}
704717
}
705718
didSomething := false
706719
if hover != nil {
@@ -717,13 +730,13 @@ func (r *runner) Definition(t *testing.T, spn span.Span, d tests.Definition) {
717730
}
718731
if !d.OnlyHover {
719732
didSomething = true
720-
locURI := locs[0].URI.SpanURI()
733+
locURI := got[0].URI.SpanURI()
721734
lm, err := r.data.Mapper(locURI)
722735
if err != nil {
723736
t.Fatal(err)
724737
}
725-
if def, err := lm.LocationSpan(locs[0]); err != nil {
726-
t.Fatalf("failed for %v: %v", locs[0], err)
738+
if def, err := lm.LocationSpan(got[0]); err != nil {
739+
t.Fatalf("failed for %v: %v", got[0], err)
727740
} else if def != d.Def {
728741
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
729742
}

gopls/internal/lsp/source/call_hierarchy.go

+94-82
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,48 @@ import (
1515

1616
"golang.org/x/tools/go/ast/astutil"
1717
"golang.org/x/tools/gopls/internal/lsp/protocol"
18+
"golang.org/x/tools/gopls/internal/lsp/safetoken"
19+
"golang.org/x/tools/gopls/internal/span"
20+
"golang.org/x/tools/internal/bug"
1821
"golang.org/x/tools/internal/event"
1922
"golang.org/x/tools/internal/event/tag"
2023
)
2124

2225
// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
23-
func PrepareCallHierarchy(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyItem, error) {
26+
func PrepareCallHierarchy(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Position) ([]protocol.CallHierarchyItem, error) {
2427
ctx, done := event.Start(ctx, "source.PrepareCallHierarchy")
2528
defer done()
2629

27-
identifier, err := Identifier(ctx, snapshot, fh, pos)
30+
pkg, pgf, err := PackageForFile(ctx, snapshot, fh.URI(), TypecheckFull, NarrowestPackage)
31+
if err != nil {
32+
return nil, err
33+
}
34+
pos, err := pgf.PositionPos(pp)
2835
if err != nil {
29-
if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
30-
return nil, nil
31-
}
3236
return nil, err
3337
}
3438

35-
// The identifier can be nil if it is an import spec.
36-
if identifier == nil || identifier.Declaration.obj == nil {
39+
obj := referencedObject(pkg, pgf, pos)
40+
if obj == nil {
3741
return nil, nil
3842
}
3943

40-
if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
44+
if _, ok := obj.Type().Underlying().(*types.Signature); !ok {
4145
return nil, nil
4246
}
4347

44-
if len(identifier.Declaration.MappedRange) == 0 {
45-
return nil, nil
48+
declLoc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
49+
if err != nil {
50+
return nil, err
4651
}
47-
declMappedRange := identifier.Declaration.MappedRange[0]
48-
rng := declMappedRange.Range()
52+
rng := declLoc.Range
4953

5054
callHierarchyItem := protocol.CallHierarchyItem{
51-
Name: identifier.Name,
55+
Name: obj.Name(),
5256
Kind: protocol.Function,
5357
Tags: []protocol.SymbolTag{},
54-
Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
55-
URI: protocol.DocumentURI(declMappedRange.URI()),
58+
Detail: fmt.Sprintf("%s • %s", obj.Pkg().Path(), filepath.Base(declLoc.URI.SpanURI().Filename())),
59+
URI: declLoc.URI,
5660
Range: rng,
5761
SelectionRange: rng,
5862
}
@@ -174,41 +178,71 @@ outer:
174178
}
175179

176180
// OutgoingCalls returns an array of CallHierarchyOutgoingCall for a file and the position within the file.
177-
func OutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
181+
func OutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, pp protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
178182
ctx, done := event.Start(ctx, "source.OutgoingCalls")
179183
defer done()
180184

181-
identifier, err := Identifier(ctx, snapshot, fh, pos)
185+
pkg, pgf, err := PackageForFile(ctx, snapshot, fh.URI(), TypecheckFull, NarrowestPackage)
186+
if err != nil {
187+
return nil, err
188+
}
189+
pos, err := pgf.PositionPos(pp)
182190
if err != nil {
183-
if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
184-
return nil, nil
185-
}
186191
return nil, err
187192
}
188193

189-
if _, ok := identifier.Declaration.obj.Type().Underlying().(*types.Signature); !ok {
194+
obj := referencedObject(pkg, pgf, pos)
195+
if obj == nil {
190196
return nil, nil
191197
}
192-
node := identifier.Declaration.node
193-
if node == nil {
198+
199+
if _, ok := obj.Type().Underlying().(*types.Signature); !ok {
194200
return nil, nil
195201
}
196-
callExprs, err := collectCallExpressions(identifier.Declaration.nodeFile, node)
202+
203+
// Skip builtins.
204+
if obj.Pkg() == nil {
205+
return nil, nil
206+
}
207+
208+
if !obj.Pos().IsValid() {
209+
return nil, bug.Errorf("internal error: object %s.%s missing position", obj.Pkg().Path(), obj.Name())
210+
}
211+
212+
declFile := pkg.FileSet().File(obj.Pos())
213+
if declFile == nil {
214+
return nil, bug.Errorf("file not found for %d", obj.Pos())
215+
}
216+
217+
uri := span.URIFromPath(declFile.Name())
218+
offset, err := safetoken.Offset(declFile, obj.Pos())
197219
if err != nil {
198220
return nil, err
199221
}
200222

201-
return toProtocolOutgoingCalls(ctx, snapshot, fh, callExprs)
202-
}
223+
// Use TypecheckFull as we want to inspect the body of the function declaration.
224+
declPkg, declPGF, err := PackageForFile(ctx, snapshot, uri, TypecheckFull, NarrowestPackage)
225+
if err != nil {
226+
return nil, err
227+
}
203228

204-
// collectCallExpressions collects call expression ranges inside a function.
205-
func collectCallExpressions(pgf *ParsedGoFile, node ast.Node) ([]protocol.Range, error) {
206-
type callPos struct {
207-
start, end token.Pos
229+
declPos, err := safetoken.Pos(declPGF.Tok, offset)
230+
if err != nil {
231+
return nil, err
208232
}
209-
callPositions := []callPos{}
210233

211-
ast.Inspect(node, func(n ast.Node) bool {
234+
declNode, _ := FindDeclAndField([]*ast.File{declPGF.File}, declPos)
235+
if declNode == nil {
236+
// TODO(rfindley): why don't we return an error here, or even bug.Errorf?
237+
return nil, nil
238+
// return nil, bug.Errorf("failed to find declaration for object %s.%s", obj.Pkg().Path(), obj.Name())
239+
}
240+
241+
type callRange struct {
242+
start, end token.Pos
243+
}
244+
callRanges := []callRange{}
245+
ast.Inspect(declNode, func(n ast.Node) bool {
212246
if call, ok := n.(*ast.CallExpr); ok {
213247
var start, end token.Pos
214248
switch n := call.Fun.(type) {
@@ -225,70 +259,48 @@ func collectCallExpressions(pgf *ParsedGoFile, node ast.Node) ([]protocol.Range,
225259
// for ex: direct function literal calls since that's not an 'outgoing' call
226260
return false
227261
}
228-
callPositions = append(callPositions, callPos{start: start, end: end})
262+
callRanges = append(callRanges, callRange{start: start, end: end})
229263
}
230264
return true
231265
})
232266

233-
callRanges := []protocol.Range{}
234-
for _, call := range callPositions {
235-
callRange, err := pgf.PosRange(call.start, call.end)
236-
if err != nil {
237-
return nil, err
238-
}
239-
callRanges = append(callRanges, callRange)
240-
}
241-
return callRanges, nil
242-
}
243-
244-
// toProtocolOutgoingCalls returns an array of protocol.CallHierarchyOutgoingCall for ast call expressions.
245-
// Calls to the same function are assigned to the same declaration.
246-
func toProtocolOutgoingCalls(ctx context.Context, snapshot Snapshot, fh FileHandle, callRanges []protocol.Range) ([]protocol.CallHierarchyOutgoingCall, error) {
247-
// Multiple calls could be made to the same function, defined by "same declaration
248-
// AST node & same identifier name" to provide a unique identifier key even when
249-
// the func is declared in a struct or interface.
250-
type key struct {
251-
decl ast.Node
252-
name string
253-
}
254-
outgoingCalls := map[key]*protocol.CallHierarchyOutgoingCall{}
267+
outgoingCalls := map[token.Pos]*protocol.CallHierarchyOutgoingCall{}
255268
for _, callRange := range callRanges {
256-
identifier, err := Identifier(ctx, snapshot, fh, callRange.Start)
257-
if err != nil {
258-
if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
259-
continue
260-
}
261-
return nil, err
269+
obj := referencedObject(declPkg, declPGF, callRange.start)
270+
if obj == nil {
271+
continue
262272
}
263273

264274
// ignore calls to builtin functions
265-
if identifier.Declaration.obj.Pkg() == nil {
275+
if obj.Pkg() == nil {
266276
continue
267277
}
268278

269-
if outgoingCall, ok := outgoingCalls[key{identifier.Declaration.node, identifier.Name}]; ok {
270-
outgoingCall.FromRanges = append(outgoingCall.FromRanges, callRange)
271-
continue
279+
outgoingCall, ok := outgoingCalls[obj.Pos()]
280+
if !ok {
281+
loc, err := mapPosition(ctx, declPkg.FileSet(), snapshot, obj.Pos(), obj.Pos()+token.Pos(len(obj.Name())))
282+
if err != nil {
283+
return nil, err
284+
}
285+
outgoingCall = &protocol.CallHierarchyOutgoingCall{
286+
To: protocol.CallHierarchyItem{
287+
Name: obj.Name(),
288+
Kind: protocol.Function,
289+
Tags: []protocol.SymbolTag{},
290+
Detail: fmt.Sprintf("%s • %s", obj.Pkg().Path(), filepath.Base(loc.URI.SpanURI().Filename())),
291+
URI: loc.URI,
292+
Range: loc.Range,
293+
SelectionRange: loc.Range,
294+
},
295+
}
296+
outgoingCalls[obj.Pos()] = outgoingCall
272297
}
273298

274-
if len(identifier.Declaration.MappedRange) == 0 {
275-
continue
276-
}
277-
declMappedRange := identifier.Declaration.MappedRange[0]
278-
rng := declMappedRange.Range()
279-
280-
outgoingCalls[key{identifier.Declaration.node, identifier.Name}] = &protocol.CallHierarchyOutgoingCall{
281-
To: protocol.CallHierarchyItem{
282-
Name: identifier.Name,
283-
Kind: protocol.Function,
284-
Tags: []protocol.SymbolTag{},
285-
Detail: fmt.Sprintf("%s • %s", identifier.Declaration.obj.Pkg().Path(), filepath.Base(declMappedRange.URI().Filename())),
286-
URI: protocol.DocumentURI(declMappedRange.URI()),
287-
Range: rng,
288-
SelectionRange: rng,
289-
},
290-
FromRanges: []protocol.Range{callRange},
299+
rng, err := declPGF.PosRange(callRange.start, callRange.end)
300+
if err != nil {
301+
return nil, err
291302
}
303+
outgoingCall.FromRanges = append(outgoingCall.FromRanges, rng)
292304
}
293305

294306
outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))

0 commit comments

Comments
 (0)