@@ -42,6 +42,8 @@ func run(pass *analysis.Pass) (any, error) {
42
42
inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
43
43
44
44
spans := make (map [types.Object ]span )
45
+ usedPkgVars := make (map [types.Object ]struct {})
46
+
45
47
for id , obj := range pass .TypesInfo .Defs {
46
48
// Ignore identifiers that don't denote objects
47
49
// (package names, symbolic variables such as t
@@ -52,6 +54,10 @@ func run(pass *analysis.Pass) (any, error) {
52
54
}
53
55
for id , obj := range pass .TypesInfo .Uses {
54
56
growSpan (spans , obj , id .Pos (), id .End ())
57
+ // Track usage for package-level variables.
58
+ if obj .Parent () == obj .Pkg ().Scope () {
59
+ usedPkgVars [obj ] = struct {}{}
60
+ }
55
61
}
56
62
for node , obj := range pass .TypesInfo .Implicits {
57
63
// A type switch with a short variable declaration
@@ -74,9 +80,9 @@ func run(pass *analysis.Pass) (any, error) {
74
80
inspect .Preorder (nodeFilter , func (n ast.Node ) {
75
81
switch n := n .(type ) {
76
82
case * ast.AssignStmt :
77
- checkShadowAssignment (pass , spans , n )
83
+ checkShadowAssignment (pass , spans , usedPkgVars , n )
78
84
case * ast.GenDecl :
79
- checkShadowDecl (pass , spans , n )
85
+ checkShadowDecl (pass , spans , usedPkgVars , n )
80
86
}
81
87
})
82
88
return nil , nil
@@ -131,7 +137,7 @@ func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos)
131
137
}
132
138
133
139
// checkShadowAssignment checks for shadowing in a short variable declaration.
134
- func checkShadowAssignment (pass * analysis.Pass , spans map [types.Object ]span , a * ast.AssignStmt ) {
140
+ func checkShadowAssignment (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, a * ast.AssignStmt ) {
135
141
if a .Tok != token .DEFINE {
136
142
return
137
143
}
@@ -144,7 +150,7 @@ func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *
144
150
pass .ReportRangef (expr , "invalid AST: short variable declaration of non-identifier" )
145
151
return
146
152
}
147
- checkShadowing (pass , spans , ident )
153
+ checkShadowing (pass , spans , usedPkgVars , ident )
148
154
}
149
155
}
150
156
@@ -204,7 +210,7 @@ func idiomaticRedecl(d *ast.ValueSpec) bool {
204
210
}
205
211
206
212
// checkShadowDecl checks for shadowing in a general variable declaration.
207
- func checkShadowDecl (pass * analysis.Pass , spans map [types.Object ]span , d * ast.GenDecl ) {
213
+ func checkShadowDecl (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, d * ast.GenDecl ) {
208
214
if d .Tok != token .VAR {
209
215
return
210
216
}
@@ -220,13 +226,13 @@ func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.Ge
220
226
return
221
227
}
222
228
for _ , ident := range valueSpec .Names {
223
- checkShadowing (pass , spans , ident )
229
+ checkShadowing (pass , spans , usedPkgVars , ident )
224
230
}
225
231
}
226
232
}
227
233
228
234
// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
229
- func checkShadowing (pass * analysis.Pass , spans map [types.Object ]span , ident * ast.Ident ) {
235
+ func checkShadowing (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, ident * ast.Ident ) {
230
236
if ident .Name == "_" {
231
237
// Can't shadow the blank identifier.
232
238
return
@@ -249,23 +255,31 @@ func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast
249
255
shadowedPos := pass .Fset .Position (shadowed .Pos ())
250
256
identPos := pass .Fset .Position (ident .Pos ())
251
257
252
- if strict {
253
- // The shadowed identifier must appear before this one to be an instance of shadowing.
254
- if shadowed .Pos () > ident .Pos () {
255
- return
258
+ // Check if the shadowed variable is at package level.
259
+ if shadowed .Parent () == shadowed .Pkg ().Scope () {
260
+ if ! strict {
261
+ // Don't complain if the package variable is unused.
262
+ if _ , ok := usedPkgVars [shadowed ]; ! ok {
263
+ return
264
+ }
256
265
}
257
266
} else {
258
- // Don't complain if the span of validity of the shadowed identifier doesn't include
259
- // the shadowing identifier, except for cross-file shadowing where file processing
260
- // order affects span checks.
261
- span , ok := spans [shadowed ]
262
- if ! ok {
263
- pass .ReportRangef (ident , "internal error: no range for %q" , ident .Name )
264
- return
265
- }
266
-
267
- if shadowedPos .Filename == identPos .Filename && ! span .contains (ident .Pos ()) {
268
- return
267
+ if strict {
268
+ // The shadowed identifier must appear before this one to be an instance of shadowing.
269
+ if shadowed .Pos () > ident .Pos () {
270
+ return
271
+ }
272
+ } else {
273
+ // Don't complain if the span of validity of the shadowed identifier doesn't include
274
+ // the shadowing identifier.
275
+ span , ok := spans [shadowed ]
276
+ if ! ok {
277
+ pass .ReportRangef (ident , "internal error: no range for %q" , ident .Name )
278
+ return
279
+ }
280
+ if ! span .contains (ident .Pos ()) {
281
+ return
282
+ }
269
283
}
270
284
}
271
285
// Don't complain if the types differ: that implies the programmer really wants two different things.
0 commit comments