Skip to content

Commit 4ec902d

Browse files
cuonglmCuong Manh Le
and
Cuong Manh Le
authored
Implement struct slop detection rules (#5)
Based on go/types.Sizes interface to re-arrange struct fields for optimal size. Fixes #3 Co-authored-by: Cuong Manh Le <cuong@orijtech.com>
1 parent 16eec7e commit 4ec902d

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

structslop.go

+88-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package structslop
22

33
import (
4+
"fmt"
45
"go/ast"
6+
"go/build"
7+
"go/types"
8+
"sort"
59

610
"golang.org/x/tools/go/analysis"
711
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -25,11 +29,92 @@ func run(pass *analysis.Pass) (interface{}, error) {
2529
(*ast.StructType)(nil),
2630
}
2731
inspect.Preorder(nodeFilter, func(n ast.Node) {
28-
s := n.(*ast.StructType)
29-
if s.Fields.NumFields() < 2 {
32+
atyp := n.(*ast.StructType)
33+
styp, ok := pass.TypesInfo.Types[atyp].Type.(*types.Struct)
34+
// Type information may be incomplete.
35+
if !ok {
3036
return
3137
}
32-
pass.Reportf(s.Pos(), "not implemented")
38+
39+
if styp.NumFields() < 2 {
40+
return
41+
}
42+
43+
r := checkSloppy(styp)
44+
if !r.sloppy() {
45+
return
46+
}
47+
48+
pass.Report(analysis.Diagnostic{
49+
Pos: n.Pos(),
50+
End: n.End(),
51+
Message: fmt.Sprintf("%v has size %d, could be %d, rearrange to %v for optimal size", styp, r.oldSize, r.newSize, r.suggestedStruct),
52+
SuggestedFixes: []analysis.SuggestedFix{{
53+
Message: fmt.Sprintf("Rearrange struct fields: %v", r.suggestedStruct),
54+
TextEdits: []analysis.TextEdit{
55+
{
56+
Pos: n.Pos(),
57+
End: n.End(),
58+
NewText: []byte(fmt.Sprintf("%v", r.suggestedStruct)),
59+
},
60+
},
61+
}},
62+
})
3363
})
3464
return nil, nil
3565
}
66+
67+
var sizes = types.SizesFor(build.Default.Compiler, build.Default.GOARCH)
68+
69+
type result struct {
70+
oldSize int64
71+
newSize int64
72+
suggestedStruct *types.Struct
73+
}
74+
75+
func (r result) sloppy() bool {
76+
return r.oldSize > r.newSize
77+
}
78+
79+
func checkSloppy(origStruct *types.Struct) result {
80+
optStruct := optimalStructArrangement(origStruct)
81+
return result{
82+
oldSize: sizes.Sizeof(origStruct),
83+
newSize: sizes.Sizeof(optStruct),
84+
suggestedStruct: optStruct,
85+
}
86+
}
87+
88+
func optimalStructArrangement(s *types.Struct) *types.Struct {
89+
nf := s.NumFields()
90+
// TODO (cuonglm): for struct has first field is "_ [0]func()", we should not move it.
91+
fields := make([]*types.Var, nf)
92+
for i := 0; i < nf; i++ {
93+
fields[i] = s.Field(i)
94+
}
95+
96+
sort.Slice(fields, func(i, j int) bool {
97+
ti, tj := fields[i].Type(), fields[j].Type()
98+
si, sj := sizes.Sizeof(ti), sizes.Sizeof(tj)
99+
100+
if si == 0 && sj != 0 {
101+
return true
102+
}
103+
if sj == 0 && si != 0 {
104+
return false
105+
}
106+
107+
ai, aj := sizes.Alignof(ti), sizes.Alignof(tj)
108+
if ai != aj {
109+
return ai > aj
110+
}
111+
112+
if si != sj {
113+
return si > sj
114+
}
115+
116+
return false
117+
})
118+
119+
return types.NewStruct(fields, nil)
120+
}

testdata/src/struct/p.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,21 @@ type s1 struct {
66
i int
77
}
88

9-
type s2 struct { // want "not implemented"
9+
type s2 struct {
1010
i int
1111
j int
1212
}
13+
14+
type s3 struct { // want "struct{.+} has size 20, could be 16, rearrange to struct{y uint64; x uint32; z uint32} for optimal size"
15+
x uint32
16+
y uint64
17+
z uint32
18+
}
19+
20+
type s4 struct { // want `struct{.+} has size 32, could be 20, rearrange to struct{_ \[0\]func\(\); i1 int; i2 int; a3 \[3\]bool; b bool} for optimal size`
21+
b bool
22+
i1 int
23+
i2 int
24+
a3 [3]bool
25+
_ [0]func()
26+
}

0 commit comments

Comments
 (0)