Skip to content

Commit c3f60b7

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: hover: show embedded fields
This change causes the hover information for a type to display the set of fields that are accessible due to promotion through one or more embedded fields. Change-Id: I7795daaeef6d5e910ae7917aa44d3012f4c016b7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/559499 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 85146f5 commit c3f60b7

File tree

5 files changed

+188
-8
lines changed

5 files changed

+188
-8
lines changed

gopls/internal/golang/hover.go

+110-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"path/filepath"
2020
"strconv"
2121
"strings"
22+
"text/tabwriter"
2223
"time"
2324
"unicode/utf8"
2425

@@ -76,13 +77,19 @@ type hoverJSON struct {
7677
// interface might be nice, but it needs a design and a
7778
// precise specification.
7879

79-
// typeDecl is the declaration syntax, or "" for a non-type.
80+
// typeDecl is the declaration syntax for a type,
81+
// or "" for a non-type.
8082
typeDecl string
8183

8284
// methods is the list of descriptions of methods of a type,
83-
// omitting any that are obvious from TypeDecl.
85+
// omitting any that are obvious from typeDecl.
8486
// It is "" for a non-type.
8587
methods string
88+
89+
// promotedFields is the list of descriptions of accessible
90+
// fields of a (struct) type that were promoted through an
91+
// embedded field.
92+
promotedFields string
8693
}
8794

8895
// Hover implements the "textDocument/hover" RPC for Go files.
@@ -234,7 +241,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
234241
}
235242
}
236243

237-
var typeDecl, methods string
244+
var typeDecl, methods, fields string
238245

239246
// For "objects defined by a type spec", the signature produced by
240247
// objectString is insufficient:
@@ -276,11 +283,33 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
276283
typeDecl = b.String()
277284
}
278285

279-
// -- methods --
286+
// Promoted fields
287+
//
288+
// Show a table of accessible fields of the (struct)
289+
// type that may not be visible in the syntax (above)
290+
// due to promotion through embedded fields.
291+
//
292+
// Example:
293+
//
294+
// // Embedded fields:
295+
// foo int // through x.y
296+
// z string // through x.y
297+
if prom := promotedFields(obj.Type(), pkg.GetTypes()); len(prom) > 0 {
298+
var b strings.Builder
299+
b.WriteString("// Embedded fields:\n")
300+
w := tabwriter.NewWriter(&b, 0, 8, 1, ' ', 0)
301+
for _, f := range prom {
302+
fmt.Fprintf(w, "%s\t%s\t// through %s\t\n",
303+
f.field.Name(),
304+
types.TypeString(f.field.Type(), qf),
305+
f.path)
306+
}
307+
w.Flush()
308+
b.WriteByte('\n')
309+
fields = b.String()
310+
}
280311

281-
// TODO(adonovan): compute a similar list of
282-
// accessible fields, reflecting embedding
283-
// (e.g. "T.Embed.Y int").
312+
// -- methods --
284313

285314
// For an interface type, explicit methods will have
286315
// already been displayed when the node was formatted
@@ -305,7 +334,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
305334
// embedded interfaces.
306335
var b strings.Builder
307336
for _, m := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
308-
if m.Obj().Pkg() != pkg.GetTypes() && !m.Obj().Exported() {
337+
if !accessibleTo(m.Obj(), pkg.GetTypes()) {
309338
continue // inaccessible
310339
}
311340
if skip[m.Obj().Name()] {
@@ -433,6 +462,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
433462
LinkAnchor: anchor,
434463
typeDecl: typeDecl,
435464
methods: methods,
465+
promotedFields: fields,
436466
}, nil
437467
}
438468

@@ -989,6 +1019,7 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
9891019
maybeMarkdown(h.Signature),
9901020
maybeMarkdown(h.typeDecl),
9911021
formatDoc(h, options),
1022+
maybeMarkdown(h.promotedFields),
9921023
maybeMarkdown(h.methods),
9931024
formatLink(h, options),
9941025
}
@@ -1170,3 +1201,74 @@ func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spe
11701201

11711202
return nil, nil, nil
11721203
}
1204+
1205+
type promotedField struct {
1206+
path string // path (e.g. "x.y" through embedded fields)
1207+
field *types.Var
1208+
}
1209+
1210+
// promotedFields returns the list of accessible promoted fields of a struct type t.
1211+
// (Logic plundered from x/tools/cmd/guru/describe.go.)
1212+
func promotedFields(t types.Type, from *types.Package) []promotedField {
1213+
wantField := func(f *types.Var) bool {
1214+
if !accessibleTo(f, from) {
1215+
return false
1216+
}
1217+
// Check that the field is not shadowed.
1218+
obj, _, _ := types.LookupFieldOrMethod(t, true, f.Pkg(), f.Name())
1219+
return obj == f
1220+
}
1221+
1222+
var fields []promotedField
1223+
var visit func(t types.Type, stack []*types.Named)
1224+
visit = func(t types.Type, stack []*types.Named) {
1225+
tStruct, ok := Deref(t).Underlying().(*types.Struct)
1226+
if !ok {
1227+
return
1228+
}
1229+
fieldloop:
1230+
for i := 0; i < tStruct.NumFields(); i++ {
1231+
f := tStruct.Field(i)
1232+
1233+
// Handle recursion through anonymous fields.
1234+
if f.Anonymous() {
1235+
tf := f.Type()
1236+
if ptr, ok := tf.(*types.Pointer); ok {
1237+
tf = ptr.Elem()
1238+
}
1239+
if named, ok := tf.(*types.Named); ok { // (be defensive)
1240+
// If we've already visited this named type
1241+
// on this path, break the cycle.
1242+
for _, x := range stack {
1243+
if x.Origin() == named.Origin() {
1244+
continue fieldloop
1245+
}
1246+
}
1247+
visit(f.Type(), append(stack, named))
1248+
}
1249+
}
1250+
1251+
// Save accessible promoted fields.
1252+
if len(stack) > 0 && wantField(f) {
1253+
var path strings.Builder
1254+
for i, t := range stack {
1255+
if i > 0 {
1256+
path.WriteByte('.')
1257+
}
1258+
path.WriteString(t.Obj().Name())
1259+
}
1260+
fields = append(fields, promotedField{
1261+
path: path.String(),
1262+
field: f,
1263+
})
1264+
}
1265+
}
1266+
}
1267+
visit(t, nil)
1268+
1269+
return fields
1270+
}
1271+
1272+
func accessibleTo(obj types.Object, pkg *types.Package) bool {
1273+
return obj.Exported() || obj.Pkg() == pkg
1274+
}

gopls/internal/test/marker/testdata/definition/embed.txt

+5
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ type S1 struct {
182182
}
183183
```
184184

185+
```go
186+
// Embedded fields:
187+
F2 int // through S2
188+
```
189+
185190
[`b.S1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1)
186191
-- @S1F1 --
187192
```go
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
This test checks that hover reports accessible embedded fields
2+
(after the doc comment and before the accessible methods).
3+
4+
-- go.mod --
5+
module example.com
6+
7+
go 1.18
8+
9+
-- q/q.go --
10+
package q
11+
12+
type Q struct {
13+
One int
14+
two string
15+
q2[chan int]
16+
}
17+
18+
type q2[T any] struct {
19+
Three *T
20+
four string
21+
}
22+
23+
-- p.go --
24+
package p
25+
26+
import "example.com/q"
27+
28+
// doc
29+
type P struct {
30+
q.Q
31+
}
32+
33+
func (P) m() {}
34+
35+
var p P //@hover("P", "P", P)
36+
37+
-- @P --
38+
```go
39+
type P struct {
40+
q.Q
41+
}
42+
```
43+
44+
doc
45+
46+
47+
```go
48+
// Embedded fields:
49+
One int // through Q
50+
Three *chan int // through Q.q2
51+
```
52+
53+
```go
54+
func (P) m()
55+
```
56+
57+
[`p.P` on pkg.go.dev](https://pkg.go.dev/example.com#P)

gopls/internal/test/marker/testdata/hover/godef.txt

+5
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ type NextThing struct {
132132
}
133133
```
134134

135+
```go
136+
// Embedded fields:
137+
Member string // through Thing
138+
```
139+
135140
```go
136141
func (t Thing) Method(i int) string
137142
func (t *Thing) Method2(i int, j int) (error, string)

gopls/internal/test/marker/testdata/hover/linkable.txt

+11
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ type Local struct {
8383
```
8484

8585
Local types should not be linkable, even if they are capitalized.
86+
87+
88+
```go
89+
// Embedded fields:
90+
Embed int // through E
91+
```
8692
-- @Nested --
8793
```go
8894
field Nested int
@@ -108,6 +114,11 @@ type T struct {
108114
T is in the package scope, and so should be linkable.
109115

110116

117+
```go
118+
// Embedded fields:
119+
Embed int // through E
120+
```
121+
111122
```go
112123
func (T) M()
113124
func (T) m()

0 commit comments

Comments
 (0)