@@ -17,6 +17,7 @@ import (
1717 "go/types"
1818 "io/fs"
1919 "path/filepath"
20+ "sort"
2021 "strconv"
2122 "strings"
2223 "text/tabwriter"
@@ -39,6 +40,7 @@ import (
3940 "golang.org/x/tools/internal/aliases"
4041 "golang.org/x/tools/internal/event"
4142 "golang.org/x/tools/internal/tokeninternal"
43+ "golang.org/x/tools/internal/typeparams"
4244 "golang.org/x/tools/internal/typesinternal"
4345)
4446
@@ -247,6 +249,9 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
247249 // Compute size information for types,
248250 // and (size, offset) for struct fields.
249251 //
252+ // Also, if a struct type's field ordering is significantly
253+ // wasteful of space, report its optimal size.
254+ //
250255 // This information is useful when debugging crashes or
251256 // optimizing layout. To reduce distraction, we show it only
252257 // when hovering over the declaring identifier,
@@ -272,50 +277,24 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
272277 return fmt .Sprintf ("%[1]d (%#[1]x)" , x )
273278 }
274279
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 )
285281
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 )
291289 }
292290 }
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 (", " )
315294 }
295+ fmt .Fprintf (& buf , "offset=%s" , format (offset ))
316296 }
317-
318- sizeOffset = strings .Join (data , ", " )
297+ sizeOffset = buf .String ()
319298 }
320299
321300 var typeDecl , methods , fields string
@@ -1361,3 +1340,72 @@ func promotedFields(t types.Type, from *types.Package) []promotedField {
13611340func accessibleTo (obj types.Object , pkg * types.Package ) bool {
13621341 return obj .Exported () || obj .Pkg () == pkg
13631342}
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