@@ -16,6 +16,7 @@ import (
1616 "golang.org/x/tools/go/analysis/passes/inspect"
1717 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
1818 "golang.org/x/tools/go/ast/inspector"
19+ "golang.org/x/tools/internal/typesinternal"
1920)
2021
2122// NOTE: Experimental. Not part of the vet suite.
@@ -42,6 +43,10 @@ func run(pass *analysis.Pass) (any, error) {
4243 inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
4344
4445 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+
4550 for id , obj := range pass .TypesInfo .Defs {
4651 // Ignore identifiers that don't denote objects
4752 // (package names, symbolic variables such as t
@@ -52,6 +57,10 @@ func run(pass *analysis.Pass) (any, error) {
5257 }
5358 for id , obj := range pass .TypesInfo .Uses {
5459 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+ }
5564 }
5665 for node , obj := range pass .TypesInfo .Implicits {
5766 // A type switch with a short variable declaration
@@ -74,9 +83,9 @@ func run(pass *analysis.Pass) (any, error) {
7483 inspect .Preorder (nodeFilter , func (n ast.Node ) {
7584 switch n := n .(type ) {
7685 case * ast.AssignStmt :
77- checkShadowAssignment (pass , spans , n )
86+ checkShadowAssignment (pass , spans , usedPkgVars , n )
7887 case * ast.GenDecl :
79- checkShadowDecl (pass , spans , n )
88+ checkShadowDecl (pass , spans , usedPkgVars , n )
8089 }
8190 })
8291 return nil , nil
@@ -131,7 +140,7 @@ func growSpan(spans map[types.Object]span, obj types.Object, pos, end token.Pos)
131140}
132141
133142// 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 ) {
135144 if a .Tok != token .DEFINE {
136145 return
137146 }
@@ -144,7 +153,7 @@ func checkShadowAssignment(pass *analysis.Pass, spans map[types.Object]span, a *
144153 pass .ReportRangef (expr , "invalid AST: short variable declaration of non-identifier" )
145154 return
146155 }
147- checkShadowing (pass , spans , ident )
156+ checkShadowing (pass , spans , usedPkgVars , ident )
148157 }
149158}
150159
@@ -204,7 +213,7 @@ func idiomaticRedecl(d *ast.ValueSpec) bool {
204213}
205214
206215// 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 ) {
208217 if d .Tok != token .VAR {
209218 return
210219 }
@@ -220,13 +229,13 @@ func checkShadowDecl(pass *analysis.Pass, spans map[types.Object]span, d *ast.Ge
220229 return
221230 }
222231 for _ , ident := range valueSpec .Names {
223- checkShadowing (pass , spans , ident )
232+ checkShadowing (pass , spans , usedPkgVars , ident )
224233 }
225234 }
226235}
227236
228237// 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 ) {
230239 if ident .Name == "_" {
231240 // Can't shadow the blank identifier.
232241 return
@@ -241,50 +250,52 @@ func checkShadowing(pass *analysis.Pass, spans map[types.Object]span, ident *ast
241250 if shadowed == nil {
242251 return
243252 }
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+ }
244257 // Don't complain if it's shadowing a universe-declared identifier; that's fine.
245258 if shadowed .Parent () == types .Universe {
246259 return
247260 }
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+ }
256269 }
257270 } 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+ }
269283 }
270284 }
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 ))
289290 }
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+ })
290301}
0 commit comments