@@ -17,6 +17,7 @@ import (
17
17
"go/types"
18
18
"io/fs"
19
19
"path/filepath"
20
+ "sort"
20
21
"strconv"
21
22
"strings"
22
23
"text/tabwriter"
@@ -39,6 +40,7 @@ import (
39
40
"golang.org/x/tools/internal/aliases"
40
41
"golang.org/x/tools/internal/event"
41
42
"golang.org/x/tools/internal/tokeninternal"
43
+ "golang.org/x/tools/internal/typeparams"
42
44
"golang.org/x/tools/internal/typesinternal"
43
45
)
44
46
@@ -247,6 +249,9 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
247
249
// Compute size information for types,
248
250
// and (size, offset) for struct fields.
249
251
//
252
+ // Also, if a struct type's field ordering is significantly
253
+ // wasteful of space, report its optimal size.
254
+ //
250
255
// This information is useful when debugging crashes or
251
256
// optimizing layout. To reduce distraction, we show it only
252
257
// when hovering over the declaring identifier,
@@ -272,50 +277,24 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
272
277
return fmt .Sprintf ("%[1]d (%#[1]x)" , x )
273
278
}
274
279
275
- var data []string // {size, offset}, both optional
276
-
277
- // If the type has free type parameters, its size cannot be
278
- // computed. For now, we capture panics from go/types.Sizes.
279
- // TODO(adonovan): use newly factored typeparams.Free.
280
- try := func (f func ()) bool {
281
- defer func () { recover () }()
282
- f ()
283
- return true
284
- }
280
+ path := pathEnclosingObjNode (pgf .File , pos )
285
281
286
- // size (types and fields)
287
- if v , ok := obj .(* types.Var ); ok && v .IsField () || is [* types.TypeName ](obj ) {
288
- var sz int64
289
- if try (func () { sz = pkg .TypesSizes ().Sizeof (obj .Type ()) }) {
290
- data = append (data , "size=" + format (sz ))
282
+ // Build string of form "size=... (X% wasted), offset=...".
283
+ size , wasted , offset := computeSizeOffsetInfo (pkg , path , obj )
284
+ var buf strings.Builder
285
+ if size >= 0 {
286
+ fmt .Fprintf (& buf , "size=%s" , format (size ))
287
+ if wasted >= 20 { // >=20% wasted
288
+ fmt .Fprintf (& buf , " (%d%% wasted)" , wasted )
291
289
}
292
290
}
293
-
294
- // offset (fields)
295
- if v , ok := obj .(* types.Var ); ok && v .IsField () {
296
- for _ , n := range pathEnclosingObjNode (pgf .File , pos ) {
297
- if n , ok := n .(* ast.StructType ); ok {
298
- t := pkg .TypesInfo ().TypeOf (n ).(* types.Struct )
299
- var fields []* types.Var
300
- for i := 0 ; i < t .NumFields (); i ++ {
301
- f := t .Field (i )
302
- fields = append (fields , f )
303
- if f == v {
304
- var offsets []int64
305
- if try (func () { offsets = pkg .TypesSizes ().Offsetsof (fields ) }) {
306
- if n := len (offsets ); n > 0 {
307
- data = append (data , "offset=" + format (offsets [n - 1 ]))
308
- }
309
- }
310
- break
311
- }
312
- }
313
- break
314
- }
291
+ if offset >= 0 {
292
+ if buf .Len () > 0 {
293
+ buf .WriteString (", " )
315
294
}
295
+ fmt .Fprintf (& buf , "offset=%s" , format (offset ))
316
296
}
317
-
318
- sizeOffset = strings .Join (data , ", " )
297
+ sizeOffset = buf .String ()
319
298
}
320
299
321
300
var typeDecl , methods , fields string
@@ -1361,3 +1340,72 @@ func promotedFields(t types.Type, from *types.Package) []promotedField {
1361
1340
func accessibleTo (obj types.Object , pkg * types.Package ) bool {
1362
1341
return obj .Exported () || obj .Pkg () == pkg
1363
1342
}
1343
+
1344
+ // computeSizeOffsetInfo reports the size of obj (if a type or struct
1345
+ // field), its wasted space percentage (if a struct type), and its
1346
+ // offset (if a struct field). It returns -1 for undefined components.
1347
+ func computeSizeOffsetInfo (pkg * cache.Package , path []ast.Node , obj types.Object ) (size , wasted , offset int64 ) {
1348
+ size , wasted , offset = - 1 , - 1 , - 1
1349
+
1350
+ var free typeparams.Free
1351
+ sizes := pkg .TypesSizes ()
1352
+
1353
+ // size (types and fields)
1354
+ if v , ok := obj .(* types.Var ); ok && v .IsField () || is [* types.TypeName ](obj ) {
1355
+ // If the field's type has free type parameters,
1356
+ // its size cannot be computed.
1357
+ if ! free .Has (obj .Type ()) {
1358
+ size = sizes .Sizeof (obj .Type ())
1359
+ }
1360
+
1361
+ // wasted space (struct types)
1362
+ if tStruct , ok := obj .Type ().Underlying ().(* types.Struct ); ok && is [* types.TypeName ](obj ) && size > 0 {
1363
+ var fields []* types.Var
1364
+ for i := 0 ; i < tStruct .NumFields (); i ++ {
1365
+ fields = append (fields , tStruct .Field (i ))
1366
+ }
1367
+ if len (fields ) > 0 {
1368
+ // Sort into descending (most compact) order
1369
+ // and recompute size of entire struct.
1370
+ sort .Slice (fields , func (i , j int ) bool {
1371
+ return sizes .Sizeof (fields [i ].Type ()) >
1372
+ sizes .Sizeof (fields [j ].Type ())
1373
+ })
1374
+ offsets := sizes .Offsetsof (fields )
1375
+ compactSize := offsets [len (offsets )- 1 ] + sizes .Sizeof (fields [len (fields )- 1 ].Type ())
1376
+ wasted = 100 * (size - compactSize ) / size
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ // offset (fields)
1382
+ if v , ok := obj .(* types.Var ); ok && v .IsField () {
1383
+ // Find enclosing struct type.
1384
+ var tStruct * types.Struct
1385
+ for _ , n := range path {
1386
+ if n , ok := n .(* ast.StructType ); ok {
1387
+ tStruct = pkg .TypesInfo ().TypeOf (n ).(* types.Struct )
1388
+ break
1389
+ }
1390
+ }
1391
+ if tStruct != nil {
1392
+ var fields []* types.Var
1393
+ for i := 0 ; i < tStruct .NumFields (); i ++ {
1394
+ f := tStruct .Field (i )
1395
+ // If any preceding field's type has free type parameters,
1396
+ // its offset cannot be computed.
1397
+ if free .Has (f .Type ()) {
1398
+ break
1399
+ }
1400
+ fields = append (fields , f )
1401
+ if f == v {
1402
+ offsets := sizes .Offsetsof (fields )
1403
+ offset = offsets [len (offsets )- 1 ]
1404
+ break
1405
+ }
1406
+ }
1407
+ }
1408
+ }
1409
+
1410
+ return
1411
+ }
0 commit comments