Skip to content

Commit 49368ab

Browse files
kwjwadonovan
authored andcommitted
go/analysis/passes/assign: correctly handle parallel assignments
Fixes golang/go#74013 Change-Id: I40a4832ccb180f0f72ae9a63e6638d81a1446c7e Reviewed-on: https://go-review.googlesource.com/c/tools/+/679435 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 06818cb commit 49368ab

File tree

6 files changed

+112
-35
lines changed

6 files changed

+112
-35
lines changed

go/analysis/passes/assign/assign.go

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ package assign
99

1010
import (
1111
_ "embed"
12-
"fmt"
1312
"go/ast"
1413
"go/token"
1514
"go/types"
1615
"reflect"
16+
"strings"
1717

1818
"golang.org/x/tools/go/analysis"
1919
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -48,31 +48,84 @@ func run(pass *analysis.Pass) (any, error) {
4848
// If LHS and RHS have different cardinality, they can't be the same.
4949
return
5050
}
51+
52+
// Delete redundant LHS, RHS pairs, taking care
53+
// to include intervening commas.
54+
var (
55+
exprs []string // expressions appearing on both sides (x = x)
56+
edits []analysis.TextEdit
57+
runStartLHS, runStartRHS token.Pos // non-zero => within a run
58+
)
5159
for i, lhs := range stmt.Lhs {
5260
rhs := stmt.Rhs[i]
53-
if analysisutil.HasSideEffects(pass.TypesInfo, lhs) ||
54-
analysisutil.HasSideEffects(pass.TypesInfo, rhs) ||
55-
isMapIndex(pass.TypesInfo, lhs) {
56-
continue // expressions may not be equal
57-
}
58-
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
59-
continue // short-circuit the heavy-weight gofmt check
61+
isSelfAssign := false
62+
var le string
63+
64+
if !analysisutil.HasSideEffects(pass.TypesInfo, lhs) &&
65+
!analysisutil.HasSideEffects(pass.TypesInfo, rhs) &&
66+
!isMapIndex(pass.TypesInfo, lhs) &&
67+
reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check
68+
69+
le = analysisinternal.Format(pass.Fset, lhs)
70+
re := analysisinternal.Format(pass.Fset, rhs)
71+
if le == re {
72+
isSelfAssign = true
73+
}
6074
}
61-
le := analysisinternal.Format(pass.Fset, lhs)
62-
re := analysisinternal.Format(pass.Fset, rhs)
63-
if le == re {
64-
pass.Report(analysis.Diagnostic{
65-
Pos: stmt.Pos(), Message: fmt.Sprintf("self-assignment of %s to %s", re, le),
66-
SuggestedFixes: []analysis.SuggestedFix{{
67-
Message: "Remove self-assignment",
68-
TextEdits: []analysis.TextEdit{{
69-
Pos: stmt.Pos(),
70-
End: stmt.End(),
71-
}}},
72-
},
73-
})
75+
76+
if isSelfAssign {
77+
exprs = append(exprs, le)
78+
if !runStartLHS.IsValid() {
79+
// Start of a new run of self-assignments.
80+
if i > 0 {
81+
runStartLHS = stmt.Lhs[i-1].End()
82+
runStartRHS = stmt.Rhs[i-1].End()
83+
} else {
84+
runStartLHS = lhs.Pos()
85+
runStartRHS = rhs.Pos()
86+
}
87+
}
88+
} else if runStartLHS.IsValid() {
89+
// End of a run of self-assignments.
90+
endLHS, endRHS := stmt.Lhs[i-1].End(), stmt.Rhs[i-1].End()
91+
if runStartLHS == stmt.Lhs[0].Pos() {
92+
endLHS, endRHS = lhs.Pos(), rhs.Pos()
93+
}
94+
edits = append(edits,
95+
analysis.TextEdit{Pos: runStartLHS, End: endLHS},
96+
analysis.TextEdit{Pos: runStartRHS, End: endRHS},
97+
)
98+
runStartLHS, runStartRHS = 0, 0
7499
}
75100
}
101+
102+
// If a run of self-assignments continues to the end of the statement, close it.
103+
if runStartLHS.IsValid() {
104+
last := len(stmt.Lhs) - 1
105+
edits = append(edits,
106+
analysis.TextEdit{Pos: runStartLHS, End: stmt.Lhs[last].End()},
107+
analysis.TextEdit{Pos: runStartRHS, End: stmt.Rhs[last].End()},
108+
)
109+
}
110+
111+
if len(exprs) == 0 {
112+
return
113+
}
114+
115+
if len(exprs) == len(stmt.Lhs) {
116+
// If every part of the statement is a self-assignment,
117+
// remove the whole statement.
118+
edits = []analysis.TextEdit{{Pos: stmt.Pos(), End: stmt.End()}}
119+
}
120+
121+
pass.Report(analysis.Diagnostic{
122+
Pos: stmt.Pos(),
123+
Message: "self-assignment of " + strings.Join(exprs, ", "),
124+
SuggestedFixes: []analysis.SuggestedFix{{
125+
Message: "Remove self-assignment",
126+
TextEdits: edits,
127+
}},
128+
})
76129
})
77130

78131
return nil, nil

go/analysis/passes/assign/testdata/src/a/a.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,23 @@ type ST struct {
1515

1616
func (s *ST) SetX(x int, ch chan int) {
1717
// Accidental self-assignment; it should be "s.x = x"
18-
x = x // want "self-assignment of x to x"
18+
x = x // want "self-assignment of x"
1919
// Another mistake
20-
s.x = s.x // want "self-assignment of s.x to s.x"
20+
s.x = s.x // want "self-assignment of s.x"
2121

22-
s.l[0] = s.l[0] // want "self-assignment of s.l.0. to s.l.0."
22+
s.l[0] = s.l[0] // want "self-assignment of s.l.0."
23+
24+
// Report self-assignment to x but preserve the actual assignment to s.x
25+
x, s.x = x, 1 // want "self-assignment of x"
26+
s.x, x = 1, x // want "self-assignment of x"
27+
28+
// Delete multiple self-assignment
29+
x, s.x = x, s.x // want "self-assignment of x, s.x"
30+
s.l[0], x, s.x = 1, x, s.x // want "self-assignment of x, s.x"
31+
x, s.l[0], s.x = x, 1, s.x // want "self-assignment of x, s.x"
32+
x, s.x, s.l[0] = x, s.x, 1 // want "self-assignment of x, s.x"
33+
s.l[0], x, s.x, s.l[1] = 1, x, s.x, 1 // want "self-assignment of x, s.x"
34+
x, s.l[0], s.l[1], s.x = x, 1, 1, s.x // want "self-assignment of x, s.x"
2335

2436
// Bail on any potential side effects to avoid false positives
2537
s.l[num()] = s.l[num()]

go/analysis/passes/assign/testdata/src/a/a.go.golden

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,23 @@ type ST struct {
1515

1616
func (s *ST) SetX(x int, ch chan int) {
1717
// Accidental self-assignment; it should be "s.x = x"
18-
// want "self-assignment of x to x"
18+
// want "self-assignment of x"
1919
// Another mistake
20-
// want "self-assignment of s.x to s.x"
20+
// want "self-assignment of s.x"
2121

22-
// want "self-assignment of s.l.0. to s.l.0."
22+
// want "self-assignment of s.l.0."
23+
24+
// Report self-assignment to x but preserve the actual assignment to s.x
25+
s.x = 1 // want "self-assignment of x"
26+
s.x = 1 // want "self-assignment of x"
27+
28+
// Delete multiple self-assignment
29+
// want "self-assignment of x, s.x"
30+
s.l[0] = 1 // want "self-assignment of x, s.x"
31+
s.l[0] = 1 // want "self-assignment of x, s.x"
32+
s.l[0] = 1 // want "self-assignment of x, s.x"
33+
s.l[0], s.l[1] = 1, 1 // want "self-assignment of x, s.x"
34+
s.l[0], s.l[1] = 1, 1 // want "self-assignment of x, s.x"
2335

2436
// Bail on any potential side effects to avoid false positives
2537
s.l[num()] = s.l[num()]

go/analysis/passes/assign/testdata/src/typeparams/typeparams.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ type ST[T interface{ ~int }] struct {
1515

1616
func (s *ST[T]) SetX(x T, ch chan T) {
1717
// Accidental self-assignment; it should be "s.x = x"
18-
x = x // want "self-assignment of x to x"
18+
x = x // want "self-assignment of x"
1919
// Another mistake
20-
s.x = s.x // want "self-assignment of s.x to s.x"
20+
s.x = s.x // want "self-assignment of s.x"
2121

22-
s.l[0] = s.l[0] // want "self-assignment of s.l.0. to s.l.0."
22+
s.l[0] = s.l[0] // want "self-assignment of s.l.0."
2323

2424
// Bail on any potential side effects to avoid false positives
2525
s.l[num()] = s.l[num()]

go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ type ST[T interface{ ~int }] struct {
1515

1616
func (s *ST[T]) SetX(x T, ch chan T) {
1717
// Accidental self-assignment; it should be "s.x = x"
18-
// want "self-assignment of x to x"
18+
// want "self-assignment of x"
1919
// Another mistake
20-
// want "self-assignment of s.x to s.x"
20+
// want "self-assignment of s.x"
2121

22-
// want "self-assignment of s.l.0. to s.l.0."
22+
// want "self-assignment of s.l.0."
2323

2424
// Bail on any potential side effects to avoid false positives
2525
s.l[num()] = s.l[num()]

go/analysis/unitchecker/unitchecker_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func _() {
9797
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:7:11: call of MyFunc123\(...\)
9898
`
9999
const wantC = `# golang.org/fake/c
100-
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go:5:5: self-assignment of i to i
100+
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go:5:5: self-assignment of i
101101
`
102102
const wantAJSON = `# golang.org/fake/a
103103
\{
@@ -130,7 +130,7 @@ func _() {
130130
"assign": \[
131131
\{
132132
"posn": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?c/c.go:5:5",
133-
"message": "self-assignment of i to i",
133+
"message": "self-assignment of i",
134134
"suggested_fixes": \[
135135
\{
136136
"message": "Remove self-assignment",

0 commit comments

Comments
 (0)