1
0
mirror of https://github.com/golang/go synced 2024-09-30 16:18:35 -06:00

internal/lsp: fix hover link for embedded fields and methods

Our logic to generate documentation links did not account for embedded
fields and methods. The types.Info.ObjectOf an embedded field returns
the *types.Var created for the field, not its types.TypeName, so we have
to navigate back to the actual definition of the field. This requires
traversing through all of the named types in the top-level type.

Fixes golang/go#40294

Change-Id: Ia6573aebe66b7f60e2d6861a381cd7b07e7d7eaa
Reviewed-on: https://go-review.googlesource.com/c/tools/+/244178
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Rebecca Stambler 2020-07-22 01:29:34 -04:00
parent 102e7d3570
commit eaaaedc6af
8 changed files with 220 additions and 98 deletions

View File

@ -98,11 +98,13 @@ func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverInformation,
case types.Object:
// If the variable is implicitly declared in a type switch, we need to
// manually generate its object string.
if v, ok := x.(*types.Var); ok && i.Declaration.typeSwitchImplicit {
h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(i.enclosing, i.qf))
} else {
h.Signature = objectString(x, i.qf)
if typ := i.Declaration.typeSwitchImplicit; typ != nil {
if v, ok := x.(*types.Var); ok {
h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf))
break
}
}
h.Signature = objectString(x, i.qf)
}
if obj := i.Declaration.obj; obj != nil {
h.SingleLine = objectString(obj, i.qf)
@ -143,12 +145,11 @@ func pathLinkAndSymbolName(i *IdentifierInfo) (string, string, string) {
var rTypeName string
switch obj := obj.(type) {
case *types.Var:
// If the object is a field, and we have an associated selector
// composite literal, or struct, we can determine the link.
if obj.IsField() {
// If the object is a field, and we have an associated selector
// composite literal, or struct, we can determine the link.
switch typ := i.enclosing.(type) {
case *types.Named:
rTypeName = typ.Obj().Name()
if named, ok := i.enclosing.(*types.Named); ok {
rTypeName = named.Obj().Name()
}
}
case *types.Func:
@ -161,10 +162,15 @@ func pathLinkAndSymbolName(i *IdentifierInfo) (string, string, string) {
case *types.Struct:
rTypeName = r.Name()
case *types.Named:
if named, ok := i.enclosing.(*types.Named); ok {
rTypeName = named.Obj().Name()
} else if !rtyp.Obj().Exported() {
return "", "", ""
// If we have an unexported type, see if the enclosing type is
// exported (we may have an interface or struct we can link
// to). If not, don't show any link.
if !rtyp.Obj().Exported() {
if named := i.enclosing.(*types.Named); ok && named.Obj().Exported() {
rTypeName = named.Obj().Name()
} else {
return "", "", ""
}
} else {
rTypeName = rtyp.Obj().Name()
}

View File

@ -32,7 +32,8 @@ type IdentifierInfo struct {
ident *ast.Ident
// enclosing is an expression used to determine the link anchor for an identifier.
// enclosing is an expression used to determine the link anchor for an
// identifier. If it's a named type, it should be exported.
enclosing types.Type
pkg Package
@ -45,8 +46,9 @@ type Declaration struct {
obj types.Object
// typeSwitchImplicit indicates that the declaration is in an implicit
// type switch.
typeSwitchImplicit bool
// type switch. Its type is the type of the variable on the right-hand
// side of the type switch.
typeSwitchImplicit types.Type
}
// Identifier returns identifier information for a position
@ -158,13 +160,13 @@ func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File
if result.Declaration.obj == nil {
// If there was no types.Object for the declaration, there might be an
// implicit local variable declaration in a type switch.
if objs := typeSwitchImplicits(pkg, path); len(objs) > 0 {
result.Declaration.typeSwitchImplicit = true
if objs, typ := typeSwitchImplicits(pkg, path); len(objs) > 0 {
// There is no types.Object for the declaration of an implicit local variable,
// but all of the types.Objects associated with the usages of this variable can be
// used to connect it back to the declaration.
// Preserve the first of these objects and treat it as if it were the declaring object.
result.Declaration.obj = objs[0]
result.Declaration.typeSwitchImplicit = typ
} else {
// Probably a type error.
return nil, errors.Errorf("no object for ident %v", result.Name)
@ -236,8 +238,25 @@ func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
for _, n := range path {
switch n := n.(type) {
case *ast.SelectorExpr:
if selection, ok := pkg.GetTypesInfo().Selections[n]; ok {
return deref(selection.Recv())
if sel, ok := pkg.GetTypesInfo().Selections[n]; ok {
recv := deref(sel.Recv())
// Keep track of the last exported type seen.
var exported *types.Named
if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
exported = named
}
// We don't want the last element, as that's the field or
// method itself.
for _, index := range sel.Index()[:len(sel.Index())-1] {
if r, ok := recv.Underlying().(*types.Struct); ok {
recv = deref(r.Field(index).Type())
if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
exported = named
}
}
}
return exported
}
case *ast.CompositeLit:
if t, ok := pkg.GetTypesInfo().Types[n]; ok {
@ -249,15 +268,6 @@ func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
return t.Type()
}
}
case *ast.TypeSwitchStmt:
// The right-hand side of a type switch should only have one
// element, and we need to track its type in order to generate
// hover information for implicit type switch variables.
if assign, ok := n.Assign.(*ast.AssignStmt); ok && len(assign.Rhs) == 1 {
if rhs := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
return pkg.GetTypesInfo().TypeOf(rhs.X)
}
}
}
}
return nil
@ -339,12 +349,13 @@ func importSpec(s Snapshot, pkg Package, file *ast.File, pos token.Pos) (*Identi
return result, nil
}
// typeSwitchImplicits returns all the implicit type switch objects
// that correspond to the leaf *ast.Ident.
func typeSwitchImplicits(pkg Package, path []ast.Node) []types.Object {
// typeSwitchImplicits returns all the implicit type switch objects that
// correspond to the leaf *ast.Ident. It also returns the original type
// associated with the identifier (outside of a case clause).
func typeSwitchImplicits(pkg Package, path []ast.Node) ([]types.Object, types.Type) {
ident, _ := path[0].(*ast.Ident)
if ident == nil {
return nil
return nil, nil
}
var (
@ -376,7 +387,6 @@ Outer:
case *ast.TypeSwitchStmt:
// Look for the type switch that owns our previously found
// *ast.AssignStmt or *ast.CaseClause.
if n.Assign == assign {
ts = n
break Outer
@ -390,11 +400,9 @@ Outer:
}
}
}
if ts == nil {
return nil
return nil, nil
}
// Our leaf ident refers to a type switch variable. Fan out to the
// type switch's implicit case clause objects.
var objs []types.Object
@ -403,5 +411,14 @@ Outer:
objs = append(objs, ccObj)
}
}
return objs
// The right-hand side of a type switch should only have one
// element, and we need to track its type in order to generate
// hover information for implicit type switch variables.
var typ types.Type
if assign, ok := ts.Assign.(*ast.AssignStmt); ok && len(assign.Rhs) == 1 {
if rhs := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
typ = pkg.GetTypesInfo().TypeOf(rhs.X)
}
}
return objs, typ
}

View File

@ -230,7 +230,7 @@ func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, fh FileHandle,
// If leaf represents an implicit type switch object or the type
// switch "assign" variable, expand to all of the type switch's
// implicit objects.
if implicits := typeSwitchImplicits(searchpkg, path); len(implicits) > 0 {
if implicits, _ := typeSwitchImplicits(searchpkg, path); len(implicits) > 0 {
objs = append(objs, implicits...)
} else {
obj := searchpkg.GetTypesInfo().ObjectOf(leaf)

View File

@ -42,3 +42,33 @@ func AStuff() { //@AStuff
var typ *types.Named //@mark(typesImport, "types"),hover("types", typesImport)
typ.Obj().Name() //@Name,hover("Name", Name)
}
type A struct {
}
func (_ A) Hi() {} //@mark(AHi, "Hi")
type S struct {
Field int //@mark(AField, "Field")
R // embed a struct
H // embed an interface
}
type R struct {
Field2 int //@mark(AField2, "Field2")
}
func (_ R) Hey() {} //@mark(AHey, "Hey")
type H interface {
Goodbye() //@mark(AGoodbye, "Goodbye")
}
type I interface {
B() //@mark(AB, "B")
J
}
type J interface {
Hello() //@mark(AHello, "Hello")
}

View File

@ -5,9 +5,26 @@ import (
"golang.org/x/tools/internal/lsp/godef/a" //@mark(AImport, re"\".*\"")
)
type Embed struct {
*a.A
a.I
a.S
}
func _() {
e := Embed{}
e.Hi() //@hover("Hi", AHi)
e.B() //@hover("B", AB)
e.Field //@hover("Field", AField)
e.Field2 //@hover("Field2", AField2)
e.Hello() //@hover("Hello", AHello)
e.Hey() //@hover("Hey", AHey)
e.Goodbye() //@hover("Goodbye", AGoodbye)
}
type S1 struct { //@S1
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
S2 //@godef("S2", S2),mark(S1S2, "S2")
a.A //@godef("A", AString)
}

View File

@ -1,3 +1,55 @@
-- AB-hover --
```go
func (a.I).B()
```
[`(a.I).B` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#I.B)
\@mark\(AB, \"B\"\)
-- AField-hover --
```go
field Field int
```
[`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#S.Field)
\@mark\(AField, \"Field\"\)
-- AField2-hover --
```go
field Field2 int
```
[`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#R.Field2)
\@mark\(AField2, \"Field2\"\)
-- AGoodbye-hover --
```go
func (a.H).Goodbye()
```
[`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#H.Goodbye)
\@mark\(AGoodbye, \"Goodbye\"\)
-- AHello-hover --
```go
func (a.J).Hello()
```
[`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#J.Hello)
\@mark\(AHello, \"Hello\"\)
-- AHey-hover --
```go
func (a.R).Hey()
```
[`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#R.Hey)
-- AHi-hover --
```go
func (a.A).Hi()
```
[`(a.A).Hi` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A.Hi)
-- AImport-definition --
godef/b/b.go:5:2-43: defined here as ```go
package a ("golang.org/x/tools/internal/lsp/godef/a")
@ -91,10 +143,10 @@ func a.AStuff()
[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
-- S1-definition --
godef/b/b.go:8:6-8: defined here as ```go
godef/b/b.go:25:6-8: defined here as ```go
S1 struct {
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
S2 //@godef("S2", S2),mark(S1S2, "S2")
a.A //@godef("A", AString)
}
```
@ -105,31 +157,31 @@ S1 struct {
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 8,
"line": 25,
"column": 6,
"offset": 193
"offset": 521
},
"end": {
"line": 8,
"line": 25,
"column": 8,
"offset": 195
"offset": 523
}
},
"description": "```go\nS1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
"description": "```go\nS1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
}
-- S1-hover --
```go
S1 struct {
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
S2 //@godef("S2", S2),mark(S1S2, "S2")
a.A //@godef("A", AString)
}
```
[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-- S1F1-definition --
godef/b/b.go:9:2-4: defined here as ```go
godef/b/b.go:26:2-4: defined here as ```go
field F1 int
```
@ -141,14 +193,14 @@ field F1 int
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 9,
"line": 26,
"column": 2,
"offset": 212
"offset": 540
},
"end": {
"line": 9,
"line": 26,
"column": 4,
"offset": 214
"offset": 542
}
},
"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"
@ -163,29 +215,29 @@ field F1 int
\@mark\(S1F1, \"F1\"\)
-- S1S2-definition --
godef/b/b.go:10:2-4: defined here as ```go
godef/b/b.go:27:2-4: defined here as ```go
field S2 S2
```
[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
\@godef\(\"S2\", S2\),mark\(S1S2, \"S2\"\)
-- S1S2-definition-json --
{
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 10,
"line": 27,
"column": 2,
"offset": 241
"offset": 569
},
"end": {
"line": 10,
"line": 27,
"column": 4,
"offset": 243
"offset": 571
}
},
"description": "```go\nfield S2 S2\n```\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)\n\n\\@godef\\(\\\"S2\\\", S2\\), mark\\(S1S2, \\\"S2\\\"\\)"
"description": "```go\nfield S2 S2\n```\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)\n\n\\@godef\\(\\\"S2\\\", S2\\),mark\\(S1S2, \\\"S2\\\"\\)"
}
-- S1S2-hover --
@ -195,9 +247,9 @@ field S2 S2
[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
\@godef\(\"S2\", S2\),mark\(S1S2, \"S2\"\)
-- S2-definition --
godef/b/b.go:14:6-8: defined here as ```go
godef/b/b.go:31:6-8: defined here as ```go
S2 struct {
F1 string //@mark(S2F1, "F1")
F2 int //@mark(S2F2, "F2")
@ -211,14 +263,14 @@ S2 struct {
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 14,
"line": 31,
"column": 6,
"offset": 326
"offset": 653
},
"end": {
"line": 14,
"line": 31,
"column": 8,
"offset": 328
"offset": 655
}
},
"description": "```go\nS2 struct {\n\tF1 string //@mark(S2F1, \"F1\")\n\tF2 int //@mark(S2F2, \"F2\")\n\t*a.A //@godef(\"A\", AString),godef(\"a\",AImport)\n}\n```\n\n[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)"
@ -235,7 +287,7 @@ S2 struct {
[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
-- S2F1-definition --
godef/b/b.go:15:2-4: defined here as ```go
godef/b/b.go:32:2-4: defined here as ```go
field F1 string
```
@ -247,14 +299,14 @@ field F1 string
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 15,
"line": 32,
"column": 2,
"offset": 345
"offset": 672
},
"end": {
"line": 15,
"line": 32,
"column": 4,
"offset": 347
"offset": 674
}
},
"description": "```go\nfield F1 string\n```\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)\n\n\\@mark\\(S2F1, \\\"F1\\\"\\)"
@ -269,11 +321,11 @@ field F1 string
\@mark\(S2F1, \"F1\"\)
-- S2F2-definition --
godef/b/b.go:16:2-4: defined here as ```go
godef/b/b.go:33:2-4: defined here as ```go
field F2 int
```
[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F2)
\@mark\(S2F2, \"F2\"\)
-- S2F2-definition-json --
@ -281,17 +333,17 @@ field F2 int
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 16,
"line": 33,
"column": 2,
"offset": 378
"offset": 705
},
"end": {
"line": 16,
"line": 33,
"column": 4,
"offset": 380
"offset": 707
}
},
"description": "```go\nfield F2 int\n```\n\n[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)\n\n\\@mark\\(S2F2, \\\"F2\\\"\\)"
"description": "```go\nfield F2 int\n```\n\n[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F2)\n\n\\@mark\\(S2F2, \\\"F2\\\"\\)"
}
-- S2F2-hover --
@ -299,11 +351,11 @@ field F2 int
field F2 int
```
[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F2)
\@mark\(S2F2, \"F2\"\)
-- bX-definition --
godef/b/b.go:37:7-8: defined here as ```go
godef/b/b.go:54:7-8: defined here as ```go
const X untyped int = 0
```
@ -315,14 +367,14 @@ const X untyped int = 0
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 37,
"line": 54,
"column": 7,
"offset": 813
"offset": 1140
},
"end": {
"line": 37,
"line": 54,
"column": 8,
"offset": 814
"offset": 1141
}
},
"description": "```go\nconst X untyped int = 0\n```\n\n[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)\n\n\\@mark\\(bX, \\\"X\\\"\\),godef\\(\\\"X\\\", bX\\)"

View File

@ -1,8 +1,8 @@
-- S1-definition --
godef/b/b.go:8:6-8: defined here as ```go
godef/b/b.go:25:6-8: defined here as ```go
S1 struct {
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
S2 //@godef("S2", S2),mark(S1S2, "S2")
a.A //@godef("A", AString)
}
```
@ -13,31 +13,31 @@ S1 struct {
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 8,
"line": 25,
"column": 6,
"offset": 193
"offset": 521
},
"end": {
"line": 8,
"line": 25,
"column": 8,
"offset": 195
"offset": 523
}
},
"description": "```go\nS1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
"description": "```go\nS1 struct {\n\tF1 int //@mark(S1F1, \"F1\")\n\tS2 //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
}
-- S1-hover --
```go
S1 struct {
F1 int //@mark(S1F1, "F1")
S2 //@godef("S2", S2), mark(S1S2, "S2")
S2 //@godef("S2", S2),mark(S1S2, "S2")
a.A //@godef("A", AString)
}
```
[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-- S1F1-definition --
godef/b/b.go:9:2-4: defined here as ```go
godef/b/b.go:26:2-4: defined here as ```go
field F1 int
```
@ -49,14 +49,14 @@ field F1 int
"span": {
"uri": "file://godef/b/b.go",
"start": {
"line": 9,
"line": 26,
"column": 2,
"offset": 212
"offset": 540
},
"end": {
"line": 9,
"line": 26,
"column": 4,
"offset": 214
"offset": 542
}
},
"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"

View File

@ -13,7 +13,7 @@ FormatCount = 6
ImportCount = 8
SuggestedFixCount = 31
FunctionExtractionCount = 5
DefinitionsCount = 56
DefinitionsCount = 63
TypeDefinitionsCount = 2
HighlightsCount = 69
ReferencesCount = 11