Skip to content

Commit e879524

Browse files
oreflowTim Malmström
andauthored
Bugfix false positive for "unused" variable for nested defs (#1376)
When detecting whether symbols are used or not, variables referenced as argument defaults of the inner def would count as "use" in the inner scope, and hence falsely trigger "unused" warning. Example: ``` def outer_def(name, foo = "foo"): def inner_def(foo = foo): print(foo) # buildifier: disable=print inner_def() ``` Unused variable check for the above function would 1. Check outer_def for variable usage 2. Recursively check inner_def for variable usage - Detect that `foo` is used - Omit `foo` from returned usedSymbolsFromOuterScope since `foo` is a local variable 3. Receive no "usedSymbol" for `foo` 4. Output "unused-variable" warning If there are RHS idents in a def statement, these idents are always from an outer scope, so these can check the later inner-scope definedSymbols check. Co-authored-by: Tim Malmström <oreflow@google.com>
1 parent 61dad1f commit e879524

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

warn/warn_control_flow.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ func unusedVariableCheck(f *build.File, root build.Expr) (map[string]bool, []*Li
410410
// Symbols for which the warning should be suppressed
411411
suppressedWarnings := make(map[string]bool)
412412

413+
// Symbols from outer scopes that are used in the current scope
414+
usedSymbolsFromOuterScope := make(map[string]bool)
415+
413416
build.WalkStatements(root, func(expr build.Expr, stack []build.Expr) (err error) {
414417
switch expr := expr.(type) {
415418
case *build.File:
@@ -463,7 +466,8 @@ func unusedVariableCheck(f *build.File, root build.Expr) (map[string]bool, []*Li
463466
// a function call with a keyword parameter.
464467
_, used := extractIdentsFromStmt(assign.RHS)
465468
for ident := range used {
466-
usedSymbols[ident.Name] = true
469+
// RHS idents in the def statement contains direct references to the outer scope.
470+
usedSymbolsFromOuterScope[ident.Name] = true
467471
}
468472
}
469473

@@ -497,7 +501,6 @@ func unusedVariableCheck(f *build.File, root build.Expr) (map[string]bool, []*Li
497501
// make the variable with the same name from an outer scope also used.
498502
// Collect variables that are used in the current or inner scopes but are not
499503
// defined in the current scope.
500-
usedSymbolsFromOuterScope := make(map[string]bool)
501504
for symbol := range usedSymbols {
502505
if _, ok := definedSymbols[symbol]; ok {
503506
continue

warn/warn_control_flow_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,35 @@ bar()
668668
":9: Variable \"y\" is unused.",
669669
},
670670
scopeEverywhere)
671+
672+
// foo is unused in the outer macro, since the symbol is overloaded, and only
673+
// the inner_def "foo" is actually used.
674+
checkFindings(t, "unused-variable", `
675+
def sample_macro_with_unused_foo(name, foo = "foo"):
676+
def inner_def(foo = "bar"):
677+
print(foo)
678+
679+
inner_def()
680+
681+
sample_macro_with_unused_foo()
682+
`,
683+
[]string{
684+
":1: Variable \"foo\" is unused.",
685+
},
686+
scopeEverywhere)
687+
688+
// Foo is referenced as the default value for foo in inner_def, hence it is not unused.
689+
checkFindings(t, "unused-variable", `
690+
def sample_macro_with_used_foo(name, foo = "foo"):
691+
def inner_def(bar = foo):
692+
print(bar)
693+
694+
inner_def()
695+
696+
sample_macro_with_used_foo()
697+
`,
698+
[]string{},
699+
scopeEverywhere)
671700
}
672701

673702
func TestRedefinedVariable(t *testing.T) {

0 commit comments

Comments
 (0)