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