Skip to content

Commit 2f6a4f9

Browse files
committed
go/analysis/passes/modernize: forvar: handle "if v := v; cond {"
Some analyzers' (especially stditerator's) fixes result in redundant v := v assignments, often as the "init" part of an if statement. This change causes the forvar analyzer to clean them up when the if statement is the sole statement of the loop body. + test For golang/go#76241 For golang/go#76240 Change-Id: I327d8726b0d2439a5cc55765acefdfa463282728 Reviewed-on: https://go-review.googlesource.com/c/tools/+/719360 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent efd8c43 commit 2f6a4f9

File tree

3 files changed

+74
-24
lines changed

3 files changed

+74
-24
lines changed

go/analysis/passes/modernize/forvar.go

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,48 +38,74 @@ var ForVarAnalyzer = &analysis.Analyzer{
3838
// because the Go specfilesUsingGoVersionsays "The variable used by each subsequent iteration
3939
// is declared implicitly before executing the post statement and initialized to the
4040
// value of the previous iteration's variable at that moment.")
41+
//
42+
// Variant: same thing in an IfStmt.Init, when the IfStmt is the sole
43+
// loop body statement:
44+
//
45+
// for _, x := range foo {
46+
// if x := x; cond { ... }
47+
// }
48+
//
49+
// (The restriction is necessary to avoid potential problems arising
50+
// from merging two distinct variables.)
51+
//
52+
// This analyzer is synergistic with stditerators,
53+
// which may create redundant "x := x" statements.
4154
func forvar(pass *analysis.Pass) (any, error) {
4255
for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
4356
for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
4457
loop := curLoop.Node().(*ast.RangeStmt)
4558
if loop.Tok != token.DEFINE {
4659
continue
4760
}
48-
isLoopVarRedecl := func(assign *ast.AssignStmt) bool {
49-
for i, lhs := range assign.Lhs {
50-
if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
51-
(astutil.EqualSyntax(lhs, loop.Key) || astutil.EqualSyntax(lhs, loop.Value))) {
52-
return false
61+
isLoopVarRedecl := func(stmt ast.Stmt) bool {
62+
if assign, ok := stmt.(*ast.AssignStmt); ok &&
63+
assign.Tok == token.DEFINE &&
64+
len(assign.Lhs) == len(assign.Rhs) {
65+
66+
for i, lhs := range assign.Lhs {
67+
if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
68+
(astutil.EqualSyntax(lhs, loop.Key) ||
69+
astutil.EqualSyntax(lhs, loop.Value))) {
70+
return false
71+
}
5372
}
73+
return true
5474
}
55-
return true
75+
return false
5676
}
5777
// Have: for k, v := range x { stmts }
5878
//
5979
// Delete the prefix of stmts that are
6080
// of the form k := k; v := v; k, v := k, v; v, k := v, k.
6181
for _, stmt := range loop.Body.List {
62-
if assign, ok := stmt.(*ast.AssignStmt); ok &&
63-
assign.Tok == token.DEFINE &&
64-
len(assign.Lhs) == len(assign.Rhs) &&
65-
isLoopVarRedecl(assign) {
66-
67-
curStmt, _ := curLoop.FindNode(stmt)
68-
edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
69-
if len(edits) > 0 {
70-
pass.Report(analysis.Diagnostic{
71-
Pos: stmt.Pos(),
72-
End: stmt.End(),
73-
Message: "copying variable is unneeded",
74-
SuggestedFixes: []analysis.SuggestedFix{{
75-
Message: "Remove unneeded redeclaration",
76-
TextEdits: edits,
77-
}},
78-
})
79-
}
82+
if isLoopVarRedecl(stmt) {
83+
// { x := x; ... }
84+
// ------
85+
} else if ifstmt, ok := stmt.(*ast.IfStmt); ok &&
86+
ifstmt.Init != nil &&
87+
len(loop.Body.List) == 1 && // must be sole statement in loop body
88+
isLoopVarRedecl(ifstmt.Init) {
89+
// if x := x; cond {
90+
// ------
91+
stmt = ifstmt.Init
8092
} else {
8193
break // stop at first other statement
8294
}
95+
96+
curStmt, _ := curLoop.FindNode(stmt)
97+
edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
98+
if len(edits) > 0 {
99+
pass.Report(analysis.Diagnostic{
100+
Pos: stmt.Pos(),
101+
End: stmt.End(),
102+
Message: "copying variable is unneeded",
103+
SuggestedFixes: []analysis.SuggestedFix{{
104+
Message: "Remove unneeded redeclaration",
105+
TextEdits: edits,
106+
}},
107+
})
108+
}
83109
}
84110
}
85111
}

go/analysis/passes/modernize/testdata/src/forvar/forvar.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ func _(m map[int]int, s []int) {
3636
/* hi */ i := i // want "copying variable is unneeded"
3737
go f(i)
3838
}
39+
for v := range m {
40+
if v := v; true { // want "copying variable is unneeded"
41+
print(v)
42+
}
43+
}
44+
3945
// nope
4046
var i, k, v int
4147

@@ -84,6 +90,12 @@ func _(m map[int]int, s []int) {
8490
i := i + 1
8591
go f(i)
8692
}
93+
for v := range m {
94+
if v := v; true { // nope, would merge distinct outer and inner variables v
95+
print(v)
96+
}
97+
print(v)
98+
}
8799
}
88100

89101
func f(n int) {}

go/analysis/passes/modernize/testdata/src/forvar/forvar.go.golden

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ func _(m map[int]int, s []int) {
2727
for i := range s {
2828
go f(i)
2929
}
30+
for v := range m {
31+
if true { // want "copying variable is unneeded"
32+
print(v)
33+
}
34+
}
35+
3036
// nope
3137
var i, k, v int
3238

@@ -75,6 +81,12 @@ func _(m map[int]int, s []int) {
7581
i := i + 1
7682
go f(i)
7783
}
84+
for v := range m {
85+
if v := v; true { // nope, would merge distinct outer and inner variables v
86+
print(v)
87+
}
88+
print(v)
89+
}
7890
}
7991

8092
func f(n int) {}

0 commit comments

Comments
 (0)