check-miscaptures.py: Python doesn't really have declarations; report the topmost assignment. refs #1555

This commit is contained in:
david-sarah 2011-10-09 04:48:00 +00:00
parent 1c6fe1d230
commit 5359c24e99

View File

@ -41,21 +41,22 @@ def check_loop(ast, results):
# ), and that case they are not nested in the AST. But these
# warts (nonobviously) happen not to matter for our analysis.
declared = {} # maps name to lineno of declaration
assigned = {} # maps name to lineno of topmost assignment
nested = set()
collect_declared_and_nested(ast, declared, nested)
collect_assigned_and_nested(ast, assigned, nested)
# For each nested function...
for funcnode in nested:
# Check for captured variables in this function.
captured = set()
collect_captured(funcnode, declared, captured)
collect_captured(funcnode, assigned, captured)
for name in captured:
# We want to report the outermost capturing function
# (since that is where the workaround will need to be
# added), and the variable declaration. Just one report
# per capturing function per variable will do.
results.append(make_result(funcnode, name, declared[name]))
# added), and the topmost assignment to the variable.
# Just one report per capturing function per variable
# will do.
results.append(make_result(funcnode, name, assigned[name]))
# Check each node in the function body in case it
# contains another 'for' loop.
@ -63,14 +64,15 @@ def check_loop(ast, results):
for child in childnodes:
check_ast(funcnode, results)
def collect_declared_and_nested(ast, declared, nested):
def collect_assigned_and_nested(ast, assigned, nested):
"""
Collect the names declared in this 'for' loop, not including
names declared in nested functions. Also collect the nodes of
functions that are nested one level deep.
Collect the names assigned in this loop, not including names
assigned in nested functions. Also collect the nodes of functions
that are nested one level deep.
"""
if isinstance(ast, AssName):
declared[ast.name] = ast.lineno
if ast.name not in assigned or assigned[ast.name] > ast.lineno:
assigned[ast.name] = ast.lineno
else:
childnodes = ast.getChildNodes()
if isinstance(ast, (Lambda, Function)):
@ -83,24 +85,23 @@ def collect_declared_and_nested(ast, declared, nested):
for child in childnodes:
if isinstance(ast, Node):
collect_declared_and_nested(child, declared, nested)
collect_assigned_and_nested(child, assigned, nested)
def collect_captured(ast, declared, captured):
"""Collect any captured variables that are also in declared."""
def collect_captured(ast, assigned, captured):
"""Collect any captured variables that are also in assigned."""
if isinstance(ast, Name):
if ast.name in declared:
if ast.name in assigned:
captured.add(ast.name)
else:
childnodes = ast.getChildNodes()
if isinstance(ast, (Lambda, Function)):
# Formal parameters of the function are excluded from
# captures we care about in subnodes of the function body.
new_declared = declared.copy()
remove_argnames(ast.argnames, new_declared)
new_assigned = assigned.copy()
remove_argnames(ast.argnames, new_assigned)
for child in childnodes[len(ast.defaults):]:
collect_captured(child, declared, captured)
collect_captured(child, assigned, captured)
# The default argument expressions are "outside" the
# function, even though they are children of the
@ -109,7 +110,7 @@ def collect_captured(ast, declared, captured):
for child in childnodes:
if isinstance(ast, Node):
collect_captured(child, declared, captured)
collect_captured(child, assigned, captured)
def remove_argnames(names, fromset):
@ -132,7 +133,7 @@ def report(out, path, results):
if isinstance(r, SyntaxError):
print >>out, path + (" NOT ANALYSED due to syntax error: %s" % r)
else:
print >>out, path + (":%r %s captures %r declared at line %d" % r)
print >>out, path + (":%r %s captures %r assigned at line %d" % r)
def check(sources, out):
class Counts: