Skip to content

Commit

Permalink
internal/lsp/source: support typeDefinition for function/method's ret…
Browse files Browse the repository at this point in the history
…urn 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: 581d810
GitHub-Pull-Request: golang#311
Reviewed-on: https://go-review.googlesource.com/c/tools/+/313093
Reviewed-by: Rebecca Stambler <[email protected]>
Trust: Rebecca Stambler <[email protected]>
Trust: Peter Weinberger <[email protected]>
Run-TryBot: Rebecca Stambler <[email protected]>
gopls-CI: kokoro <[email protected]>
TryBot-Result: Go Bot <[email protected]>
  • Loading branch information
ShoshinNikita authored and stamblerre committed May 6, 2021
1 parent 08a4f34 commit d1ea2c7
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 4 deletions.
55 changes: 55 additions & 0 deletions gopls/internal/regtest/misc/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
})
}
}
30 changes: 27 additions & 3 deletions internal/lsp/fake/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions internal/lsp/source/identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/testdata/summary.txt.golden
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ SemanticTokenCount = 3
SuggestedFixCount = 40
FunctionExtractionCount = 18
DefinitionsCount = 95
TypeDefinitionsCount = 10
TypeDefinitionsCount = 18
HighlightsCount = 69
ReferencesCount = 25
RenamesCount = 33
Expand Down
27 changes: 27 additions & 0 deletions internal/lsp/testdata/typdef/typdef.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit d1ea2c7

Please sign in to comment.