internal/lsp/source: support typeDefinition for function/method's return values

Support typeDefinition for functions/methods that have only one return value
of a named type. The total number of return values doesn't matter.

Examples:

* func foo() X
* func foo() (X, bool, int)
* func foo() (*float64, *X, error)

Fixes golang/go#38589

Change-Id: I8840d667437300fd1250a13630e12a36601f0a60
GitHub-Last-Rev: 581d810af959f8b2c0bf62a22e5725f32947f5e4
GitHub-Pull-Request: golang/tools#311
Reviewed-on: https://go-review.googlesource.com/c/tools/+/313093
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Rebecca Stambler <rstambler@golang.org>
Trust: Peter Weinberger <pjw@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
This commit is contained in:
Shoshin Nikita 2021-04-30 20:27:34 +00:00 committed by Rebecca Stambler
parent 08a4f343fb
commit d1ea2c78a5
5 changed files with 130 additions and 4 deletions

View File

@ -179,3 +179,58 @@ func main() {}
})
}
}
func TestGoToTypeDefinition_Issue38589(t *testing.T) {
const mod = `
-- go.mod --
module mod.com
go 1.12
-- main.go --
package main
type Int int
type Struct struct{}
func F1() {}
func F2() (int, error) { return 0, nil }
func F3() (**Struct, bool, *Int, error) { return nil, false, nil, nil }
func F4() (**Struct, bool, *float64, error) { return nil, false, nil, nil }
func main() {}
`
for _, tt := range []struct {
re string
wantError bool
wantTypeRe string
}{
{re: `F1`, wantError: true},
{re: `F2`, wantError: true},
{re: `F3`, wantError: true},
{re: `F4`, wantError: false, wantTypeRe: `type (Struct)`},
} {
t.Run(tt.re, func(t *testing.T) {
Run(t, mod, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
_, pos, err := env.Editor.GoToTypeDefinition(env.Ctx, "main.go", env.RegexpSearch("main.go", tt.re))
if tt.wantError {
if err == nil {
t.Fatal("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("expected nil error, got %s", err)
}
typePos := env.RegexpSearch("main.go", tt.wantTypeRe)
if pos != typePos {
t.Errorf("invalid pos: want %+v, got %+v", typePos, pos)
}
})
})
}
}

View File

@ -707,11 +707,35 @@ func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (stri
if err != nil {
return "", Pos{}, errors.Errorf("definition: %w", err)
}
if len(resp) == 0 {
return e.extractFirstPathAndPos(ctx, resp)
}
// GoToTypeDefinition jumps to the type definition of the symbol at the given position
// in an open buffer.
func (e *Editor) GoToTypeDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
if err := e.checkBufferPosition(path, pos); err != nil {
return "", Pos{}, err
}
params := &protocol.TypeDefinitionParams{}
params.TextDocument.URI = e.sandbox.Workdir.URI(path)
params.Position = pos.ToProtocolPosition()
resp, err := e.Server.TypeDefinition(ctx, params)
if err != nil {
return "", Pos{}, errors.Errorf("type definition: %w", err)
}
return e.extractFirstPathAndPos(ctx, resp)
}
// extractFirstPathAndPos returns the path and the position of the first location.
// It opens the file if needed.
func (e *Editor) extractFirstPathAndPos(ctx context.Context, locs []protocol.Location) (string, Pos, error) {
if len(locs) == 0 {
return "", Pos{}, nil
}
newPath := e.sandbox.Workdir.URIToPath(resp[0].URI)
newPos := fromProtocolPosition(resp[0].Range.Start)
newPath := e.sandbox.Workdir.URIToPath(locs[0].URI)
newPos := fromProtocolPosition(locs[0].Range.Start)
if !e.HasBuffer(newPath) {
if err := e.OpenFile(ctx, newPath); err != nil {
return "", Pos{}, errors.Errorf("OpenFile: %w", err)

View File

@ -329,6 +329,26 @@ func typeToObject(typ types.Type) types.Object {
return typeToObject(typ.Elem())
case *types.Chan:
return typeToObject(typ.Elem())
case *types.Signature:
// Try to find a return value of a named type. If there's only one
// such value, jump to its type definition.
var res types.Object
results := typ.Results()
for i := 0; i < results.Len(); i++ {
obj := typeToObject(results.At(i).Type())
if obj == nil || hasErrorType(obj) {
// Skip builtins.
continue
}
if res != nil {
// The function/method must have only one return value of a named type.
return nil
}
res = obj
}
return res
default:
return nil
}

View File

@ -16,7 +16,7 @@ SemanticTokenCount = 3
SuggestedFixCount = 40
FunctionExtractionCount = 18
DefinitionsCount = 95
TypeDefinitionsCount = 10
TypeDefinitionsCount = 18
HighlightsCount = 69
ReferencesCount = 25
RenamesCount = 33

View File

@ -36,3 +36,30 @@ func _() {
s.x.xx.field1 //@typdef("field1", Struct)
s.x.xx.field2 //@typdef("field2", Int)
}
func F1() Int { return 0 }
func F2() (Int, float64) { return 0, 0 }
func F3() (Struct, int, bool, error) { return Struct{}, 0, false, nil }
func F4() (**int, Int, bool, *error) { return nil, Struct{}, false, nil }
func F5() (int, float64, error, Struct) { return 0, 0, nil, Struct{} }
func F6() (int, float64, ***Struct, error) { return 0, 0, nil, nil }
func _() {
F1() //@typdef("F1", Int)
F2() //@typdef("F2", Int)
F3() //@typdef("F3", Struct)
F4() //@typdef("F4", Int)
F5() //@typdef("F5", Struct)
F6() //@typdef("F6", Struct)
f := func() Int { return 0 }
f() //@typdef("f", Int)
}
// https://github.com/golang/go/issues/38589#issuecomment-620350922
func _() {
type myFunc func(int) Int //@item(myFunc, "myFunc", "func", "type")
var foo myFunc
bar := foo() //@typdef("foo", myFunc)
}