Skip to content

Commit c24121c

Browse files
committed
go/analysis/passes/modernize: stditerators: even better name heuristic
This CL causes stditerators to generate a fresh name only when the preferred name is already declared _and_ is free in (referenced within) the loop body. This eliminates most cases where a fresh (ugly) name is needed. (Most places that use FreshName should do something similar, though some care will be required to figure out the correct abstraction.) Fixes golang/go#76240 Updates golang/go#76241 Change-Id: Ia7a3332ef49e3d4f2934e9f85215b9373226084a Reviewed-on: https://go-review.googlesource.com/c/tools/+/719321 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent edb9587 commit c24121c

File tree

7 files changed

+73
-4
lines changed

7 files changed

+73
-4
lines changed

go/analysis/passes/modernize/errorsastype.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ func errorsastype(pass *analysis.Pass) (any, error) {
125125
errtype := types.TypeString(v.Type(), qual)
126126

127127
// Choose a name for the "ok" variable.
128+
// TODO(adonovan): this pattern also appears in stditerators,
129+
// and is wanted elsewhere; factor.
128130
okName := "ok"
129131
if okVar := lookup(info, curCall, "ok"); okVar != nil {
130132
// The name 'ok' is already declared, but

go/analysis/passes/modernize/stditerators.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,23 @@ func stditerators(pass *analysis.Pass) (any, error) {
163163
}
164164

165165
loop := curBody.Parent().Node()
166-
return refactor.FreshName(info.Scopes[loop], loop.Pos(), row.elemname), nil
166+
167+
// Choose a fresh name only if
168+
// (a) the preferred name is already declared here, and
169+
// (b) there are references to it from the loop body.
170+
// TODO(adonovan): this pattern also appears in errorsastype,
171+
// and is wanted elsewhere; factor.
172+
name := row.elemname
173+
if v := lookup(info, curBody, name); v != nil {
174+
// is it free in body?
175+
for curUse := range index.Uses(v) {
176+
if curBody.Contains(curUse) {
177+
name = refactor.FreshName(info.Scopes[loop], loop.Pos(), name)
178+
break
179+
}
180+
}
181+
}
182+
return name, nil
167183
}
168184

169185
// Process each call of x.Len().

go/analysis/passes/modernize/stringscut.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ func stringscut(pass *analysis.Pass) (any, error) {
191191

192192
scope := iObj.Parent()
193193
var (
194+
// TODO(adonovan): avoid FreshName when not needed; see errorsastype.
194195
okVarName = refactor.FreshName(scope, iIdent.Pos(), "ok")
195196
beforeVarName = refactor.FreshName(scope, iIdent.Pos(), "before")
196197
afterVarName = refactor.FreshName(scope, iIdent.Pos(), "after")

go/analysis/passes/modernize/stringscutprefix.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
201201

202202
if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
203203
(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
204+
// TODO(adonovan): avoid FreshName when not needed; see errorsastype.
204205
okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
205206
// Have one of:
206207
// if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ func _(scope *types.Scope) {
1313
print(scope.Child(i))
1414
}
1515
{
16-
const child = 0 // shadowing of preferred name at def
16+
// tests of shadowing of preferred name at def
17+
const child = 0
1718
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
1819
print(scope.Child(i))
1920
}
21+
for i := 0; i < scope.NumChildren(); i++ { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
22+
print(scope.Child(i), child)
23+
}
2024
}
2125
{
2226
for i := 0; i < scope.NumChildren(); i++ {
@@ -65,4 +69,22 @@ func _(tuple *types.Tuple) {
6569
bar := tuple.At(i)
6670
print(bar)
6771
}
72+
{
73+
// The name v is already declared, but not
74+
// used in the loop, so we can use it again.
75+
v := 1
76+
print(v)
77+
78+
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
79+
print(tuple.At(i))
80+
}
81+
}
82+
{
83+
// The name v is used from the loop, so
84+
// we must choose a fresh name.
85+
v := 1
86+
for i := 0; i < tuple.Len(); i++ { // want "Len/At loop can simplified using Tuple.Variables iteration"
87+
print(tuple.At(i), v)
88+
}
89+
}
6890
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ func _(scope *types.Scope) {
1313
print(child)
1414
}
1515
{
16-
const child = 0 // shadowing of preferred name at def
16+
// tests of shadowing of preferred name at def
17+
const child = 0
18+
for child := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
19+
print(child)
20+
}
1721
for child0 := range scope.Children() { // want "NumChildren/Child loop can simplified using Scope.Children iteration"
18-
print(child0)
22+
print(child0, child)
1923
}
2024
}
2125
{
@@ -65,4 +69,22 @@ func _(tuple *types.Tuple) {
6569
bar := foo
6670
print(bar)
6771
}
72+
{
73+
// The name v is already declared, but not
74+
// used in the loop, so we can use it again.
75+
v := 1
76+
print(v)
77+
78+
for v := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
79+
print(v)
80+
}
81+
}
82+
{
83+
// The name v is used from the loop, so
84+
// we must choose a fresh name.
85+
v := 1
86+
for v0 := range tuple.Variables() { // want "Len/At loop can simplified using Tuple.Variables iteration"
87+
print(v0, v)
88+
}
89+
}
6890
}

internal/refactor/refactor.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import (
1717

1818
// FreshName returns the name of an identifier that is undefined
1919
// at the specified position, based on the preferred name.
20+
//
21+
// TODO(adonovan): refine this to choose a fresh name only when there
22+
// would be a conflict with the existing declaration: it's fine to
23+
// redeclare a name in a narrower scope so long as there are no free
24+
// references to the outer name from within the narrower scope.
2025
func FreshName(scope *types.Scope, pos token.Pos, preferred string) string {
2126
newName := preferred
2227
for i := 0; ; i++ {

0 commit comments

Comments
 (0)