@@ -16,6 +16,7 @@ import (
16
16
"golang.org/x/tools/go/analysis/passes/inspect"
17
17
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
18
18
"golang.org/x/tools/go/ast/inspector"
19
+ "golang.org/x/tools/internal/typesinternal"
19
20
)
20
21
21
22
// NOTE: Experimental. Not part of the vet suite.
@@ -42,6 +43,10 @@ func run(pass *analysis.Pass) (any, error) {
42
43
inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
43
44
44
45
spans := make (map [types.Object ]span )
46
+ // usedPkgVars contains all package-level objects of the current package
47
+ // that are actually referenced.
48
+ usedPkgVars := make (map [types.Object ]struct {})
49
+
45
50
for id , obj := range pass .TypesInfo .Defs {
46
51
// Ignore identifiers that don't denote objects
47
52
// (package names, symbolic variables such as t
@@ -52,6 +57,10 @@ func run(pass *analysis.Pass) (any, error) {
52
57
}
53
58
for id , obj := range pass .TypesInfo .Uses {
54
59
growSpan (spans , obj , id .Pos (), id .End ())
60
+ // Track usage for package-level variables in the current package.
61
+ if typesinternal .IsPackageLevel (obj ) && obj .Pkg () == pass .Pkg {
62
+ usedPkgVars [obj ] = struct {}{}
63
+ }
55
64
}
56
65
for node , obj := range pass .TypesInfo .Implicits {
57
66
// A type switch with a short variable declaration
@@ -74,9 +83,9 @@ func run(pass *analysis.Pass) (any, error) {
74
83
inspect .Preorder (nodeFilter , func (n ast.Node ) {
75
84
switch n := n .(type ) {
76
85
case * ast.AssignStmt :
77
- checkShadowAssignment (pass , spans , n )
86
+ checkShadowAssignment (pass , spans , usedPkgVars , n )
78
87
case * ast.GenDecl :
79
- checkShadowDecl (pass , spans , n )
88
+ checkShadowDecl (pass , spans , usedPkgVars , n )
80
89
}
81
90
})
82
91
return nil , nil
@@ -131,7 +140,7 @@ func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos)
131
140
}
132
141
133
142
// checkShadowAssignment checks for shadowing in a short variable declaration.
134
- func checkShadowAssignment (pass * analysis.Pass , spans map [types.Object ]span , a * ast.AssignStmt ) {
143
+ func checkShadowAssignment (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, a * ast.AssignStmt ) {
135
144
if a .Tok != token .DEFINE {
136
145
return
137
146
}
@@ -144,7 +153,7 @@ func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *
144
153
pass .ReportRangef (expr , "invalid AST: short variable declaration of non-identifier" )
145
154
return
146
155
}
147
- checkShadowing (pass , spans , ident )
156
+ checkShadowing (pass , spans , usedPkgVars , ident )
148
157
}
149
158
}
150
159
@@ -204,7 +213,7 @@ func idiomaticRedecl(d *ast.ValueSpec) bool {
204
213
}
205
214
206
215
// checkShadowDecl checks for shadowing in a general variable declaration.
207
- func checkShadowDecl (pass * analysis.Pass , spans map [types.Object ]span , d * ast.GenDecl ) {
216
+ func checkShadowDecl (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, d * ast.GenDecl ) {
208
217
if d .Tok != token .VAR {
209
218
return
210
219
}
@@ -220,13 +229,13 @@ func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.Ge
220
229
return
221
230
}
222
231
for _ , ident := range valueSpec .Names {
223
- checkShadowing (pass , spans , ident )
232
+ checkShadowing (pass , spans , usedPkgVars , ident )
224
233
}
225
234
}
226
235
}
227
236
228
237
// 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 ) {
238
+ func checkShadowing (pass * analysis.Pass , spans map [types.Object ]span , usedPkgVars map [types. Object ] struct {}, ident * ast.Ident ) {
230
239
if ident .Name == "_" {
231
240
// Can't shadow the blank identifier.
232
241
return
@@ -241,50 +250,52 @@ func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast
241
250
if shadowed == nil {
242
251
return
243
252
}
253
+ // Don't complain if the types differ: that implies the programmer really wants two different things.
254
+ if ! types .Identical (obj .Type (), shadowed .Type ()) {
255
+ return
256
+ }
244
257
// Don't complain if it's shadowing a universe-declared identifier; that's fine.
245
258
if shadowed .Parent () == types .Universe {
246
259
return
247
260
}
248
-
249
- shadowedPos := pass . Fset . Position ( shadowed . Pos ())
250
- identPos := pass . Fset . Position ( ident . Pos ())
251
-
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
261
+ if typesinternal . IsPackageLevel ( shadowed ) {
262
+ if ! strict {
263
+ // Don't complain if the shadowed package variable is unused.
264
+ // Since package-level variables are always in scope, we skip
265
+ // reporting only when they're not used at all.
266
+ if _ , ok := usedPkgVars [ shadowed ]; ! ok {
267
+ return
268
+ }
256
269
}
257
270
} 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
271
+ if strict {
272
+ // The shadowed identifier must appear before this one to be an instance of shadowing.
273
+ if shadowed .Pos () > ident .Pos () {
274
+ return
275
+ }
276
+ } else {
277
+ // Don't complain if the span of validity of the shadowed identifier doesn't include
278
+ // the shadowing identifier.
279
+ span , ok := spans [shadowed ]
280
+ if ! ok || ! span .contains (ident .Pos ()) {
281
+ return
282
+ }
269
283
}
270
284
}
271
- // Don't complain if the types differ: that implies the programmer really wants two different things.
272
- if types .Identical (obj .Type (), shadowed .Type ()) {
273
- // Build the message, adding filename only if in a different file
274
- message := fmt .Sprintf ("declaration of %q shadows declaration at line %d" , obj .Name (), shadowedPos .Line )
275
- if shadowedPos .Filename != identPos .Filename {
276
- message += fmt .Sprintf (" in %s" , filepath .Base (shadowedPos .Filename ))
277
- }
278
-
279
- pass .Report (analysis.Diagnostic {
280
- Pos : ident .Pos (),
281
- End : ident .End (),
282
- Message : message ,
283
- Related : []analysis.RelatedInformation {{
284
- Pos : shadowed .Pos (),
285
- End : shadowed .Pos () + token .Pos (len (shadowed .Name ())),
286
- Message : fmt .Sprintf ("shadowed symbol %q declared here" , obj .Name ()),
287
- }},
288
- })
285
+ shadowedPos := pass .Fset .Position (shadowed .Pos ())
286
+ message := fmt .Sprintf ("declaration of %q shadows declaration at line %d" , obj .Name (), shadowedPos .Line )
287
+ currentFile := pass .Fset .Position (ident .Pos ()).Filename
288
+ if shadowedPos .Filename != currentFile {
289
+ message += fmt .Sprintf (" in %s" , filepath .Base (shadowedPos .Filename ))
289
290
}
291
+ pass .Report (analysis.Diagnostic {
292
+ Pos : ident .Pos (),
293
+ End : ident .End (),
294
+ Message : message ,
295
+ Related : []analysis.RelatedInformation {{
296
+ Pos : shadowed .Pos (),
297
+ End : shadowed .Pos () + token .Pos (len (shadowed .Name ())),
298
+ Message : fmt .Sprintf ("shadowed symbol %q declared here" , obj .Name ()),
299
+ }},
300
+ })
290
301
}
0 commit comments