Skip to content

Commit 1daaaca

Browse files
adonovangopherbot
authored andcommitted
go/analysis/passes/modernize: reflecttypefor: remove orphaned var
This CL fixes a bug in reflecttypefor that caused it to leave a declaration for a local variable that was no longer used, which is not legal. Now, it deletes the variable: - var zero T - reflect.TypeOf(zero) + reflect.TypeFor[T]() The bookkeeping is done by the new refactor.DeleteUnusedVars function. Fixes golang/go#75767 Change-Id: If51af1500a63c968098b4a518b941315e826fcbf Reviewed-on: https://go-review.googlesource.com/c/tools/+/710656 Reviewed-by: Madeline Kalil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: shuang cui <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent f8608b7 commit 1daaaca

File tree

5 files changed

+70
-9
lines changed

5 files changed

+70
-9
lines changed

go/analysis/passes/modernize/modernize_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
. "golang.org/x/tools/go/analysis/analysistest"
1111
"golang.org/x/tools/go/analysis/passes/modernize"
1212
"golang.org/x/tools/internal/goplsexport"
13+
"golang.org/x/tools/internal/testenv"
1314
)
1415

1516
func TestAppendClipped(t *testing.T) {
@@ -61,6 +62,7 @@ func TestRangeInt(t *testing.T) {
6162
}
6263

6364
func TestReflectTypeFor(t *testing.T) {
65+
testenv.NeedsGo1Point(t, 25) // requires go1.25 types.Var.Kind
6466
RunWithSuggestedFixes(t, TestData(), modernize.ReflectTypeForAnalyzer, "reflecttypefor")
6567
}
6668

go/analysis/passes/modernize/reflect.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"golang.org/x/tools/internal/analysisinternal/generated"
1919
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
2020
"golang.org/x/tools/internal/astutil"
21+
"golang.org/x/tools/internal/refactor"
2122
"golang.org/x/tools/internal/typesinternal"
2223
"golang.org/x/tools/internal/typesinternal/typeindex"
2324
"golang.org/x/tools/internal/versions"
@@ -66,16 +67,15 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
6667
obj := typeutil.Callee(info, call2)
6768
if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
6869
if ptr, ok := t.(*types.Pointer); ok {
69-
// Have: reflect.TypeOf(...*T value...).Elem()
70-
// => reflect.TypeFor[T]()
70+
// Have: TypeOf(expr).Elem() where expr : *T
7171
t = ptr.Elem()
72-
edits = []analysis.TextEdit{
73-
{
74-
// delete .Elem()
75-
Pos: call.End(),
76-
End: call2.End(),
77-
},
78-
}
72+
// reflect.TypeOf(expr).Elem()
73+
// -------
74+
// reflect.TypeOf(expr)
75+
edits = []analysis.TextEdit{{
76+
Pos: call.End(),
77+
End: call2.End(),
78+
}}
7979
}
8080
}
8181
}
@@ -92,6 +92,7 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
9292
if versions.Before(info.FileVersions[file], "go1.22") {
9393
continue // TypeFor requires go1.22
9494
}
95+
tokFile := pass.Fset.File(file.Pos())
9596

9697
// Format the type as valid Go syntax.
9798
// TODO(adonovan): FileQualifier needs to respect
@@ -105,6 +106,14 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
105106
continue // e.g. reflect was dot-imported
106107
}
107108

109+
// If the call argument contains the last use
110+
// of a variable, as in:
111+
// var zero T
112+
// reflect.TypeOf(zero)
113+
// remove the declaration of that variable.
114+
curArg0 := curCall.ChildAt(edge.CallExpr_Args, 0)
115+
edits = append(edits, refactor.DeleteUnusedVars(index, info, tokFile, curArg0)...)
116+
108117
pass.Report(analysis.Diagnostic{
109118
Pos: call.Fun.Pos(),
110119
End: call.Fun.End(),

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ var (
1919
_ = reflect.TypeOf(time.Time{}) // want "reflect.TypeOf call can be simplified using TypeFor"
2020
_ = reflect.TypeOf(time.Duration(0)) // want "reflect.TypeOf call can be simplified using TypeFor"
2121
)
22+
23+
// Eliminate local var if we deleted its last use.
24+
func _() {
25+
var zero string
26+
_ = reflect.TypeOf(zero) // want "reflect.TypeOf call can be simplified using TypeFor"
27+
28+
var z2 string
29+
_ = reflect.TypeOf(z2) // want "reflect.TypeOf call can be simplified using TypeFor"
30+
_ = z2 // z2 has multiple uses
31+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ var (
2020
_ = reflect.TypeFor[time.Duration]() // want "reflect.TypeOf call can be simplified using TypeFor"
2121
)
2222

23+
// Eliminate local var if we deleted its last use.
24+
func _() {
25+
_ = reflect.TypeFor[string]() // want "reflect.TypeOf call can be simplified using TypeFor"
26+
27+
var z2 string
28+
_ = reflect.TypeFor[string]() // want "reflect.TypeOf call can be simplified using TypeFor"
29+
_ = z2 // z2 has multiple uses
30+
}

internal/refactor/delete.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"golang.org/x/tools/go/ast/inspector"
1919
"golang.org/x/tools/internal/astutil"
2020
"golang.org/x/tools/internal/typesinternal"
21+
"golang.org/x/tools/internal/typesinternal/typeindex"
2122
)
2223

2324
// DeleteVar returns edits to delete the declaration of a variable
@@ -431,3 +432,34 @@ Outer:
431432
}
432433
return []analysis.TextEdit{edit}
433434
}
435+
436+
// DeleteUnusedVars computes the edits required to delete the
437+
// declarations of any local variables whose last uses are in the
438+
// curDelend subtree, which is about to be deleted.
439+
func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.File, curDelend inspector.Cursor) []analysis.TextEdit {
440+
// TODO(adonovan): we might want to generalize this by
441+
// splitting the two phases below, so that we can gather
442+
// across a whole sequence of deletions then finally compute the
443+
// set of variables that are no longer wanted.
444+
445+
// Count number of deletions of each var.
446+
delcount := make(map[*types.Var]int)
447+
for curId := range curDelend.Preorder((*ast.Ident)(nil)) {
448+
id := curId.Node().(*ast.Ident)
449+
if v, ok := info.Uses[id].(*types.Var); ok &&
450+
typesinternal.GetVarKind(v) == typesinternal.LocalVar { // always false before go1.25
451+
delcount[v]++
452+
}
453+
}
454+
455+
// Delete declaration of each var that became unused.
456+
var edits []analysis.TextEdit
457+
for v, count := range delcount {
458+
if len(slices.Collect(index.Uses(v))) == count {
459+
if curDefId, ok := index.Def(v); ok {
460+
edits = append(edits, DeleteVar(tokFile, info, curDefId)...)
461+
}
462+
}
463+
}
464+
return edits
465+
}

0 commit comments

Comments
 (0)