Skip to content

Commit 3821a14

Browse files
S1011: flag index-based loops
Closes gh-881 Closes gh-978 Co-authored-by: Dominik Honnef <[email protected]>
1 parent b4a6f75 commit 3821a14

File tree

6 files changed

+103
-9
lines changed

6 files changed

+103
-9
lines changed

simple/analysis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ var Analyzers = lint.InitializeAnalyzers(Docs, map[string]*analysis.Analyzer{
5555
},
5656
"S1011": {
5757
Run: CheckLoopAppend,
58-
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated},
58+
Requires: []*analysis.Analyzer{inspect.Analyzer, facts.Generated, facts.Purity},
5959
},
6060
"S1012": {
6161
Run: CheckTimeSince,

simple/doc.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,21 @@ making \'s[n:len(s)]\' and \'s[n:]\' equivalent.`,
137137
Before: `
138138
for _, e := range y {
139139
x = append(x, e)
140+
}
141+
142+
for i := range y {
143+
x = append(x, y[i])
144+
}
145+
146+
for i := range y {
147+
v := y[i]
148+
x = append(x, v)
140149
}`,
141-
After: `x = append(x, y...)`,
150+
151+
After: `
152+
x = append(x, y...)
153+
x = append(x, y...)
154+
x = append(x, y...)`,
142155
Since: "2017.1",
143156
// MergeIfAll because y might not be a slice under all build tags.
144157
MergeIf: lint.MergeIfAll,
@@ -267,9 +280,9 @@ Given the following shared definitions
267280
268281
type T1 string
269282
type T2 int
270-
283+
271284
func (T2) String() string { return "Hello, world" }
272-
285+
273286
var x string
274287
var y T1
275288
var z T2

simple/lint.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"honnef.co/go/tools/analysis/code"
1515
"honnef.co/go/tools/analysis/edit"
16+
"honnef.co/go/tools/analysis/facts"
1617
"honnef.co/go/tools/analysis/lint"
1718
"honnef.co/go/tools/analysis/report"
1819
"honnef.co/go/tools/go/ast/astutil"
@@ -767,22 +768,44 @@ func refersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
767768
}
768769

769770
var checkLoopAppendQ = pattern.MustParse(`
771+
(Or
770772
(RangeStmt
771773
(Ident "_")
772774
val@(Object _)
773775
_
774776
x
775-
[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]) `)
777+
[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])])
778+
(RangeStmt
779+
idx@(Ident _)
780+
nil
781+
_
782+
x
783+
[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])])
784+
(RangeStmt
785+
idx@(Ident _)
786+
nil
787+
_
788+
x
789+
[(AssignStmt val@(Object _) ":=" (IndexExpr x idx))
790+
(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]))`)
776791

777792
func CheckLoopAppend(pass *analysis.Pass) (interface{}, error) {
793+
pure := pass.ResultOf[facts.Purity].(facts.PurityResult)
794+
778795
fn := func(node ast.Node) {
779796
m, ok := code.Match(pass, checkLoopAppendQ, node)
780797
if !ok {
781798
return
782799
}
783800

784-
val := m.State["val"].(types.Object)
785-
if refersTo(pass, m.State["lhs"].(ast.Expr), val) {
801+
val, ok := m.State["val"].(types.Object)
802+
if ok && refersTo(pass, m.State["lhs"].(ast.Expr), val) {
803+
return
804+
}
805+
806+
if m.State["idx"] != nil && code.MayHaveSideEffects(pass, m.State["x"].(ast.Expr), pure) {
807+
// When using an index-based loop, x gets evaluated repeatedly and thus should be pure.
808+
// This doesn't matter for value-based loops, because x only gets evaluated once.
786809
return
787810
}
788811

simple/testdata/src/loop-append/loop-append.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ func fn1() {
1717
b = append(b, v)
1818
}
1919

20+
var a2, b2 []int
21+
for i := range a2 { // want `should replace loop`
22+
b2 = append(b2, a2[i])
23+
}
24+
25+
var a3, b3 []int
26+
for i := range a3 { // want `should replace loop`
27+
v := a3[i]
28+
b3 = append(b3, v)
29+
}
30+
31+
var a4 []int
32+
for i := range fn6() {
33+
a4 = append(a4, fn6()[i])
34+
}
35+
2036
var m map[string]int
2137
var c []int
2238
for _, v := range m {
@@ -76,3 +92,20 @@ func fn5() {
7692
}
7793
_ = out
7894
}
95+
96+
func fn6() []int {
97+
return []int{1, 2, 3}
98+
}
99+
100+
func fn7() {
101+
var x []int
102+
for _, v := range fn6() { // want `should replace loop`
103+
// Purity doesn't matter here
104+
x = append(x, v)
105+
}
106+
107+
for i := range fn6() {
108+
// Purity does matter here
109+
x = append(x, fn6()[i])
110+
}
111+
}

simple/testdata/src/loop-append/loop-append.go.golden

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ func fn1() {
1515
var a, b []int
1616
b = append(b, a...)
1717

18+
var a2, b2 []int
19+
b2 = append(b2, a2...)
20+
21+
var a3, b3 []int
22+
b3 = append(b3, a3...)
23+
24+
var a4 []int
25+
for i := range fn6() {
26+
a4 = append(a4, fn6()[i])
27+
}
28+
1829
var m map[string]int
1930
var c []int
2031
for _, v := range m {
@@ -74,3 +85,17 @@ func fn5() {
7485
}
7586
_ = out
7687
}
88+
89+
func fn6() []int {
90+
return []int{1, 2, 3}
91+
}
92+
93+
func fn7() {
94+
var x []int
95+
x = append(x, fn6()...)
96+
97+
for i := range fn6() {
98+
// Purity does matter here
99+
x = append(x, fn6()[i])
100+
}
101+
}

website/data/checks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@
304304
"Text": "",
305305
"TitleMarkdown": "Use a single `append` to concatenate two slices",
306306
"TextMarkdown": "",
307-
"Before": "for _, e := range y {\n x = append(x, e)\n}",
308-
"After": "x = append(x, y...)",
307+
"Before": "for _, e := range y {\n x = append(x, e)\n}\n\nfor i := range y {\n x = append(x, y[i])\n}\n\nfor i := range y {\n v := y[i]\n x = append(x, v)\n}",
308+
"After": "x = append(x, y...)\nx = append(x, y...)\nx = append(x, y...)",
309309
"Since": "2017.1",
310310
"NonDefault": false,
311311
"Options": null,

0 commit comments

Comments
 (0)