diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 648a2240..c5492611 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -409,7 +409,6 @@ class FunctionScope(Scope): @ivar globals: Names declared 'global' in this function. """ - usesLocals = False alwaysUsed = {'__tracebackhide__', '__traceback_info__', '__traceback_supplement__'} @@ -428,7 +427,6 @@ def unusedAssignments(self): if (not binding.used and name != '_' # see issue #202 and name not in self.globals - and not self.usesLocals and isinstance(binding, Assignment)): yield name, binding @@ -710,6 +708,15 @@ def handleNodeLoad(self, node): in_generators = None importStarred = None + if node.id == 'locals' and isinstance(node.parent, ast.Call): + # we are doing locals() call, which marks names currently + # in scope as used. + scope = self.scope + if isinstance(scope, GeneratorScope): + scope = self.scopeStack[-2] + for binding in scope.values(): + binding.used = (self.scope, node) + # try enclosing function scopes and global scope for scope in self.scopeStack[-1::-1]: if isinstance(scope, ClassScope): @@ -1096,10 +1103,6 @@ def NAME(self, node): # Locate the name in locals / function / globals scopes. if isinstance(node.ctx, (ast.Load, ast.AugLoad)): self.handleNodeLoad(node) - if (node.id == 'locals' and isinstance(self.scope, FunctionScope) - and isinstance(node.parent, ast.Call)): - # we are doing locals() call in current scope - self.scope.usesLocals = True elif isinstance(node.ctx, (ast.Store, ast.AugStore)): self.handleNodeStore(node) elif isinstance(node.ctx, ast.Del): diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index 4dbaf236..db8ef234 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -1185,7 +1185,7 @@ def a(unused_param): _ = unused_param ''') - def test_unusedVariableAsLocals(self): + def test_unusedVariableWithLocals(self): """ Using locals() it is perfectly valid to have unused variables """ @@ -1195,6 +1195,29 @@ def a(): return locals() ''') + def test_unusedVariableWithLocalsInComprehension(self): + """ + Using locals() in comprehension it is perfectly valid + to have unused variables + """ + self.flakes(''' + def a(): + b = 1 + return (i for i in locals()) + ''') + + def test_unusedVariableAfterLocals(self): + """ + Warn when an unused variable appears after locals() + """ + self.flakes(''' + def a(): + b = 1 + c = locals() + d = 1 + return c + ''', m.UnusedVariable) + def test_unusedVariableNoLocals(self): """ Using locals() in wrong scope should not matter @@ -1660,6 +1683,15 @@ def foo(): except (tokenize.TokenError, IndentationError): pass ''') + def test_exceptUnusedAsLocals(self): + """ + Don't issue false warning when an exception is used by locals(). + """ + self.flakes(''' + try: raise ValueError() + except ValueError as e: locals() + ''') + def test_augmentedAssignmentImportedFunctionCall(self): """ Consider a function that is called on the right part of an