@@ -19,6 +19,7 @@ import (
19
19
"path/filepath"
20
20
"strconv"
21
21
"strings"
22
+ "text/tabwriter"
22
23
"time"
23
24
"unicode/utf8"
24
25
@@ -76,13 +77,19 @@ type hoverJSON struct {
76
77
// interface might be nice, but it needs a design and a
77
78
// precise specification.
78
79
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.
80
82
typeDecl string
81
83
82
84
// 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 .
84
86
// It is "" for a non-type.
85
87
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
86
93
}
87
94
88
95
// 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
234
241
}
235
242
}
236
243
237
- var typeDecl , methods string
244
+ var typeDecl , methods , fields string
238
245
239
246
// For "objects defined by a type spec", the signature produced by
240
247
// objectString is insufficient:
@@ -276,11 +283,33 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
276
283
typeDecl = b .String ()
277
284
}
278
285
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
+ }
280
311
281
- // TODO(adonovan): compute a similar list of
282
- // accessible fields, reflecting embedding
283
- // (e.g. "T.Embed.Y int").
312
+ // -- methods --
284
313
285
314
// For an interface type, explicit methods will have
286
315
// 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
305
334
// embedded interfaces.
306
335
var b strings.Builder
307
336
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 ()) {
309
338
continue // inaccessible
310
339
}
311
340
if skip [m .Obj ().Name ()] {
@@ -433,6 +462,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
433
462
LinkAnchor : anchor ,
434
463
typeDecl : typeDecl ,
435
464
methods : methods ,
465
+ promotedFields : fields ,
436
466
}, nil
437
467
}
438
468
@@ -989,6 +1019,7 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
989
1019
maybeMarkdown (h .Signature ),
990
1020
maybeMarkdown (h .typeDecl ),
991
1021
formatDoc (h , options ),
1022
+ maybeMarkdown (h .promotedFields ),
992
1023
maybeMarkdown (h .methods ),
993
1024
formatLink (h , options ),
994
1025
}
@@ -1170,3 +1201,74 @@ func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spe
1170
1201
1171
1202
return nil , nil , nil
1172
1203
}
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
+ }
0 commit comments