Skip to content

Commit d7e5d6e

Browse files
committed
go/analysis/passes/shadow: separate package-level variables logic
1 parent 28b865a commit d7e5d6e

File tree

3 files changed

+62
-46
lines changed

3 files changed

+62
-46
lines changed

go/analysis/passes/shadow/shadow.go

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

go/analysis/passes/shadow/testdata/src/crossfile/crossfile.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ package crossfile
88

99
func ShadowGlobal() {
1010
{
11-
global := 1 // want "declaration of .global. shadows declaration at line 7 in other.go"
11+
global := 1 // want "declaration of .global. shadows declaration at line 8 in other.go"
1212
_ = global
13+
unusedGlobal := 2 // OK - shadowed package variable is never used
14+
_ = unusedGlobal
1315
}
1416
_ = global
1517
}

go/analysis/passes/shadow/testdata/src/crossfile/other.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44

55
package crossfile
66

7-
var global int
7+
var (
8+
global int
9+
unusedGlobal int
10+
)

0 commit comments

Comments
 (0)