s1001.go raw
1 package s1001
2
3 import (
4 "fmt"
5 "go/ast"
6 "go/token"
7 "go/types"
8
9 "honnef.co/go/tools/analysis/code"
10 "honnef.co/go/tools/analysis/edit"
11 "honnef.co/go/tools/analysis/facts/generated"
12 "honnef.co/go/tools/analysis/lint"
13 "honnef.co/go/tools/analysis/report"
14 "honnef.co/go/tools/pattern"
15
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/analysis/passes/inspect"
18 )
19
20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
21 Analyzer: &analysis.Analyzer{
22 Name: "S1001",
23 Run: run,
24 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
25 },
26 Doc: &lint.RawDocumentation{
27 Title: `Replace for loop with call to copy`,
28 Text: `
29 Use \'copy()\' for copying elements from one slice to another. For
30 arrays of identical size, you can use simple assignment.`,
31 Before: `
32 for i, x := range src {
33 dst[i] = x
34 }`,
35 After: `copy(dst, src)`,
36 Since: "2017.1",
37 // MergeIfAll because the types of src and dst might be different under different build tags.
38 // You shouldn't write code like that…
39 MergeIf: lint.MergeIfAll,
40 },
41 })
42
43 var Analyzer = SCAnalyzer.Analyzer
44
45 var (
46 checkLoopCopyQ = pattern.MustParse(`
47 (Or
48 (RangeStmt
49 key@(Ident _) value@(Ident _) ":=" src
50 [(AssignStmt (IndexExpr dst key) "=" value)])
51 (RangeStmt
52 key@(Ident _) nil ":=" src
53 [(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))])
54 (ForStmt
55 (AssignStmt key@(Ident _) ":=" (IntegerLiteral "0"))
56 (BinaryExpr key "<" (CallExpr (Symbol "len") [src]))
57 (IncDecStmt key "++")
58 [(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]))`)
59 )
60
61 func run(pass *analysis.Pass) (interface{}, error) {
62 // TODO revisit once range doesn't require a structural type
63
64 isInvariant := func(k, v types.Object, node ast.Expr) bool {
65 if code.MayHaveSideEffects(pass, node, nil) {
66 return false
67 }
68 invariant := true
69 ast.Inspect(node, func(node ast.Node) bool {
70 if node, ok := node.(*ast.Ident); ok {
71 obj := pass.TypesInfo.ObjectOf(node)
72 if obj == k || obj == v {
73 // don't allow loop bodies like 'a[i][i] = v'
74 invariant = false
75 return false
76 }
77 }
78 return true
79 })
80 return invariant
81 }
82
83 var elType func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool)
84 elType = func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) {
85 switch typ := T.Underlying().(type) {
86 case *types.Slice:
87 return typ.Elem(), false, false, true
88 case *types.Array:
89 return typ.Elem(), true, false, true
90 case *types.Pointer:
91 el, isArray, _, ok = elType(typ.Elem())
92 return el, isArray, true, ok
93 default:
94 return nil, false, false, false
95 }
96 }
97
98 fn := func(node ast.Node) {
99 m, ok := code.Match(pass, checkLoopCopyQ, node)
100 if !ok {
101 return
102 }
103
104 src := m.State["src"].(ast.Expr)
105 dst := m.State["dst"].(ast.Expr)
106
107 k := pass.TypesInfo.ObjectOf(m.State["key"].(*ast.Ident))
108 var v types.Object
109 if value, ok := m.State["value"]; ok {
110 v = pass.TypesInfo.ObjectOf(value.(*ast.Ident))
111 }
112 if !isInvariant(k, v, dst) {
113 return
114 }
115 if !isInvariant(k, v, src) {
116 // For example: 'for i := range foo()'
117 return
118 }
119
120 Tsrc := pass.TypesInfo.TypeOf(src)
121 Tdst := pass.TypesInfo.TypeOf(dst)
122 TsrcElem, TsrcArray, TsrcPointer, ok := elType(Tsrc)
123 if !ok {
124 return
125 }
126 if TsrcPointer {
127 Tsrc = Tsrc.Underlying().(*types.Pointer).Elem()
128 }
129 TdstElem, TdstArray, TdstPointer, ok := elType(Tdst)
130 if !ok {
131 return
132 }
133 if TdstPointer {
134 Tdst = Tdst.Underlying().(*types.Pointer).Elem()
135 }
136
137 if !types.Identical(TsrcElem, TdstElem) {
138 return
139 }
140
141 if TsrcArray && TdstArray && types.Identical(Tsrc, Tdst) {
142 if TsrcPointer {
143 src = &ast.StarExpr{
144 X: src,
145 }
146 }
147 if TdstPointer {
148 dst = &ast.StarExpr{
149 X: dst,
150 }
151 }
152 r := &ast.AssignStmt{
153 Lhs: []ast.Expr{dst},
154 Rhs: []ast.Expr{src},
155 Tok: token.ASSIGN,
156 }
157
158 report.Report(pass, node, "should copy arrays using assignment instead of using a loop",
159 report.FilterGenerated(),
160 report.ShortRange(),
161 report.Fixes(edit.Fix("replace loop with assignment", edit.ReplaceWithNode(pass.Fset, node, r))))
162 } else {
163 tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
164 if err == nil && tv.IsBuiltin() {
165 to := "to"
166 from := "from"
167 src := m.State["src"].(ast.Expr)
168 if TsrcArray {
169 from = "from[:]"
170 src = &ast.SliceExpr{
171 X: src,
172 }
173 }
174 dst := m.State["dst"].(ast.Expr)
175 if TdstArray {
176 to = "to[:]"
177 dst = &ast.SliceExpr{
178 X: dst,
179 }
180 }
181
182 r := &ast.CallExpr{
183 Fun: &ast.Ident{Name: "copy"},
184 Args: []ast.Expr{dst, src},
185 }
186 opts := []report.Option{
187 report.ShortRange(),
188 report.FilterGenerated(),
189 report.Fixes(edit.Fix("replace loop with call to copy()", edit.ReplaceWithNode(pass.Fset, node, r))),
190 }
191 report.Report(pass, node, fmt.Sprintf("should use copy(%s, %s) instead of a loop", to, from), opts...)
192 }
193 }
194 }
195 code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil))
196 return nil, nil
197 }
198