Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track locals() #343

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions pyflakes/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@ class FunctionScope(Scope):

@ivar globals: Names declared 'global' in this function.
"""
usesLocals = False
alwaysUsed = {'__tracebackhide__', '__traceback_info__',
'__traceback_supplement__'}

Expand All @@ -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

Expand Down Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be changed to

if not binding.used:
    binding.used = (self.scope, node)

Then at the end of the scope, any bindings used only by locals() could be a different type of message.

See #333 (comment)


# try enclosing function scopes and global scope
for scope in self.scopeStack[-1::-1]:
if isinstance(scope, ClassScope):
Expand Down Expand Up @@ -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):
Expand Down
34 changes: 33 additions & 1 deletion pyflakes/test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down