Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(278)

Unified Diff: third_party/cython/src/Cython/Compiler/FlowControl.py

Issue 385073004: Adding cython v0.20.2 in third-party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Reference cython dev list thread. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/cython/src/Cython/Compiler/FlowControl.py
diff --git a/third_party/cython/src/Cython/Compiler/FlowControl.py b/third_party/cython/src/Cython/Compiler/FlowControl.py
new file mode 100644
index 0000000000000000000000000000000000000000..a36ffa467614567ef8a3dae747f71abe2331b594
--- /dev/null
+++ b/third_party/cython/src/Cython/Compiler/FlowControl.py
@@ -0,0 +1,1303 @@
+import cython
+cython.declare(PyrexTypes=object, ExprNodes=object, Nodes=object,
+ Builtin=object, InternalError=object,
+ error=object, warning=object,
+ py_object_type=object, unspecified_type=object,
+ object_expr=object, object_expr_not_none=object,
+ fake_rhs_expr=object, TypedExprNode=object)
+
+import Builtin
+import ExprNodes
+import Nodes
+import Options
+from PyrexTypes import py_object_type, unspecified_type
+import PyrexTypes
+
+from Visitor import TreeVisitor, CythonTransform
+from Errors import error, warning, InternalError
+
+class TypedExprNode(ExprNodes.ExprNode):
+ # Used for declaring assignments of a specified type without a known entry.
+ def __init__(self, type, may_be_none=None, pos=None):
+ super(TypedExprNode, self).__init__(pos)
+ self.type = type
+ self._may_be_none = may_be_none
+
+ def may_be_none(self):
+ return self._may_be_none != False
+
+object_expr = TypedExprNode(py_object_type, may_be_none=True)
+object_expr_not_none = TypedExprNode(py_object_type, may_be_none=False)
+# Fake rhs to silence "unused variable" warning
+fake_rhs_expr = TypedExprNode(unspecified_type)
+
+
+class ControlBlock(object):
+ """Control flow graph node. Sequence of assignments and name references.
+
+ children set of children nodes
+ parents set of parent nodes
+ positions set of position markers
+
+ stats list of block statements
+ gen dict of assignments generated by this block
+ bounded set of entries that are definitely bounded in this block
+
+ Example:
+
+ a = 1
+ b = a + c # 'c' is already bounded or exception here
+
+ stats = [Assignment(a), NameReference(a), NameReference(c),
+ Assignment(b)]
+ gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)}
+ bounded = set([Entry(a), Entry(c)])
+
+ """
+
+ def __init__(self):
+ self.children = set()
+ self.parents = set()
+ self.positions = set()
+
+ self.stats = []
+ self.gen = {}
+ self.bounded = set()
+
+ self.i_input = 0
+ self.i_output = 0
+ self.i_gen = 0
+ self.i_kill = 0
+ self.i_state = 0
+
+ def empty(self):
+ return (not self.stats and not self.positions)
+
+ def detach(self):
+ """Detach block from parents and children."""
+ for child in self.children:
+ child.parents.remove(self)
+ for parent in self.parents:
+ parent.children.remove(self)
+ self.parents.clear()
+ self.children.clear()
+
+ def add_child(self, block):
+ self.children.add(block)
+ block.parents.add(self)
+
+
+class ExitBlock(ControlBlock):
+ """Non-empty exit point block."""
+
+ def empty(self):
+ return False
+
+
+class AssignmentList(object):
+ def __init__(self):
+ self.stats = []
+
+
+class ControlFlow(object):
+ """Control-flow graph.
+
+ entry_point ControlBlock entry point for this graph
+ exit_point ControlBlock normal exit point
+ block ControlBlock current block
+ blocks set children nodes
+ entries set tracked entries
+ loops list stack for loop descriptors
+ exceptions list stack for exception descriptors
+ """
+
+ def __init__(self):
+ self.blocks = set()
+ self.entries = set()
+ self.loops = []
+ self.exceptions = []
+
+ self.entry_point = ControlBlock()
+ self.exit_point = ExitBlock()
+ self.blocks.add(self.exit_point)
+ self.block = self.entry_point
+
+ def newblock(self, parent=None):
+ """Create floating block linked to `parent` if given.
+
+ NOTE: Block is NOT added to self.blocks
+ """
+ block = ControlBlock()
+ self.blocks.add(block)
+ if parent:
+ parent.add_child(block)
+ return block
+
+ def nextblock(self, parent=None):
+ """Create block children block linked to current or `parent` if given.
+
+ NOTE: Block is added to self.blocks
+ """
+ block = ControlBlock()
+ self.blocks.add(block)
+ if parent:
+ parent.add_child(block)
+ elif self.block:
+ self.block.add_child(block)
+ self.block = block
+ return self.block
+
+ def is_tracked(self, entry):
+ if entry.is_anonymous:
+ return False
+ return (entry.is_local or entry.is_pyclass_attr or entry.is_arg or
+ entry.from_closure or entry.in_closure or
+ entry.error_on_uninitialized)
+
+ def is_statically_assigned(self, entry):
+ if (entry.is_local and entry.is_variable and
+ (entry.type.is_struct_or_union or
+ entry.type.is_complex or
+ entry.type.is_array or
+ entry.type.is_cpp_class)):
+ # stack allocated structured variable => never uninitialised
+ return True
+ return False
+
+ def mark_position(self, node):
+ """Mark position, will be used to draw graph nodes."""
+ if self.block:
+ self.block.positions.add(node.pos[:2])
+
+ def mark_assignment(self, lhs, rhs, entry):
+ if self.block and self.is_tracked(entry):
+ assignment = NameAssignment(lhs, rhs, entry)
+ self.block.stats.append(assignment)
+ self.block.gen[entry] = assignment
+ self.entries.add(entry)
+
+ def mark_argument(self, lhs, rhs, entry):
+ if self.block and self.is_tracked(entry):
+ assignment = Argument(lhs, rhs, entry)
+ self.block.stats.append(assignment)
+ self.block.gen[entry] = assignment
+ self.entries.add(entry)
+
+ def mark_deletion(self, node, entry):
+ if self.block and self.is_tracked(entry):
+ assignment = NameDeletion(node, entry)
+ self.block.stats.append(assignment)
+ self.block.gen[entry] = Uninitialized
+ self.entries.add(entry)
+
+ def mark_reference(self, node, entry):
+ if self.block and self.is_tracked(entry):
+ self.block.stats.append(NameReference(node, entry))
+ ## XXX: We don't track expression evaluation order so we can't use
+ ## XXX: successful reference as initialization sign.
+ ## # Local variable is definitely bound after this reference
+ ## if not node.allow_null:
+ ## self.block.bounded.add(entry)
+ self.entries.add(entry)
+
+ def normalize(self):
+ """Delete unreachable and orphan blocks."""
+ queue = set([self.entry_point])
+ visited = set()
+ while queue:
+ root = queue.pop()
+ visited.add(root)
+ for child in root.children:
+ if child not in visited:
+ queue.add(child)
+ unreachable = self.blocks - visited
+ for block in unreachable:
+ block.detach()
+ visited.remove(self.entry_point)
+ for block in visited:
+ if block.empty():
+ for parent in block.parents: # Re-parent
+ for child in block.children:
+ parent.add_child(child)
+ block.detach()
+ unreachable.add(block)
+ self.blocks -= unreachable
+
+ def initialize(self):
+ """Set initial state, map assignments to bits."""
+ self.assmts = {}
+
+ bit = 1
+ for entry in self.entries:
+ assmts = AssignmentList()
+ assmts.mask = assmts.bit = bit
+ self.assmts[entry] = assmts
+ bit <<= 1
+
+ for block in self.blocks:
+ for stat in block.stats:
+ if isinstance(stat, NameAssignment):
+ stat.bit = bit
+ assmts = self.assmts[stat.entry]
+ assmts.stats.append(stat)
+ assmts.mask |= bit
+ bit <<= 1
+
+ for block in self.blocks:
+ for entry, stat in block.gen.items():
+ assmts = self.assmts[entry]
+ if stat is Uninitialized:
+ block.i_gen |= assmts.bit
+ else:
+ block.i_gen |= stat.bit
+ block.i_kill |= assmts.mask
+ block.i_output = block.i_gen
+ for entry in block.bounded:
+ block.i_kill |= self.assmts[entry].bit
+
+ for assmts in self.assmts.itervalues():
+ self.entry_point.i_gen |= assmts.bit
+ self.entry_point.i_output = self.entry_point.i_gen
+
+ def map_one(self, istate, entry):
+ ret = set()
+ assmts = self.assmts[entry]
+ if istate & assmts.bit:
+ if self.is_statically_assigned(entry):
+ ret.add(StaticAssignment(entry))
+ elif entry.from_closure:
+ ret.add(Unknown)
+ else:
+ ret.add(Uninitialized)
+ for assmt in assmts.stats:
+ if istate & assmt.bit:
+ ret.add(assmt)
+ return ret
+
+ def reaching_definitions(self):
+ """Per-block reaching definitions analysis."""
+ dirty = True
+ while dirty:
+ dirty = False
+ for block in self.blocks:
+ i_input = 0
+ for parent in block.parents:
+ i_input |= parent.i_output
+ i_output = (i_input & ~block.i_kill) | block.i_gen
+ if i_output != block.i_output:
+ dirty = True
+ block.i_input = i_input
+ block.i_output = i_output
+
+
+class LoopDescr(object):
+ def __init__(self, next_block, loop_block):
+ self.next_block = next_block
+ self.loop_block = loop_block
+ self.exceptions = []
+
+
+class ExceptionDescr(object):
+ """Exception handling helper.
+
+ entry_point ControlBlock Exception handling entry point
+ finally_enter ControlBlock Normal finally clause entry point
+ finally_exit ControlBlock Normal finally clause exit point
+ """
+
+ def __init__(self, entry_point, finally_enter=None, finally_exit=None):
+ self.entry_point = entry_point
+ self.finally_enter = finally_enter
+ self.finally_exit = finally_exit
+
+
+class NameAssignment(object):
+ def __init__(self, lhs, rhs, entry):
+ if lhs.cf_state is None:
+ lhs.cf_state = set()
+ self.lhs = lhs
+ self.rhs = rhs
+ self.entry = entry
+ self.pos = lhs.pos
+ self.refs = set()
+ self.is_arg = False
+ self.is_deletion = False
+ self.inferred_type = None
+
+ def __repr__(self):
+ return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
+
+ def infer_type(self):
+ self.inferred_type = self.rhs.infer_type(self.entry.scope)
+ return self.inferred_type
+
+ def type_dependencies(self):
+ return self.rhs.type_dependencies(self.entry.scope)
+
+ @property
+ def type(self):
+ if not self.entry.type.is_unspecified:
+ return self.entry.type
+ return self.inferred_type
+
+
+class StaticAssignment(NameAssignment):
+ """Initialised at declaration time, e.g. stack allocation."""
+ def __init__(self, entry):
+ if not entry.type.is_pyobject:
+ may_be_none = False
+ else:
+ may_be_none = None # unknown
+ lhs = TypedExprNode(
+ entry.type, may_be_none=may_be_none, pos=entry.pos)
+ super(StaticAssignment, self).__init__(lhs, lhs, entry)
+
+ def infer_type(self):
+ return self.entry.type
+
+ def type_dependencies(self):
+ return ()
+
+
+class Argument(NameAssignment):
+ def __init__(self, lhs, rhs, entry):
+ NameAssignment.__init__(self, lhs, rhs, entry)
+ self.is_arg = True
+
+
+class NameDeletion(NameAssignment):
+ def __init__(self, lhs, entry):
+ NameAssignment.__init__(self, lhs, lhs, entry)
+ self.is_deletion = True
+
+ def infer_type(self):
+ inferred_type = self.rhs.infer_type(self.entry.scope)
+ if (not inferred_type.is_pyobject and
+ inferred_type.can_coerce_to_pyobject(self.entry.scope)):
+ return py_object_type
+ self.inferred_type = inferred_type
+ return inferred_type
+
+
+class Uninitialized(object):
+ """Definitely not initialised yet."""
+
+
+class Unknown(object):
+ """Coming from outer closure, might be initialised or not."""
+
+
+class NameReference(object):
+ def __init__(self, node, entry):
+ if node.cf_state is None:
+ node.cf_state = set()
+ self.node = node
+ self.entry = entry
+ self.pos = node.pos
+
+ def __repr__(self):
+ return '%s(entry=%r)' % (self.__class__.__name__, self.entry)
+
+
+class ControlFlowState(list):
+ # Keeps track of Node's entry assignments
+ #
+ # cf_is_null [boolean] It is uninitialized
+ # cf_maybe_null [boolean] May be uninitialized
+ # is_single [boolean] Has only one assignment at this point
+
+ cf_maybe_null = False
+ cf_is_null = False
+ is_single = False
+
+ def __init__(self, state):
+ if Uninitialized in state:
+ state.discard(Uninitialized)
+ self.cf_maybe_null = True
+ if not state:
+ self.cf_is_null = True
+ elif Unknown in state:
+ state.discard(Unknown)
+ self.cf_maybe_null = True
+ else:
+ if len(state) == 1:
+ self.is_single = True
+ # XXX: Remove fake_rhs_expr
+ super(ControlFlowState, self).__init__(
+ [i for i in state if i.rhs is not fake_rhs_expr])
+
+ def one(self):
+ return self[0]
+
+
+class GVContext(object):
+ """Graphviz subgraph object."""
+
+ def __init__(self):
+ self.blockids = {}
+ self.nextid = 0
+ self.children = []
+ self.sources = {}
+
+ def add(self, child):
+ self.children.append(child)
+
+ def nodeid(self, block):
+ if block not in self.blockids:
+ self.blockids[block] = 'block%d' % self.nextid
+ self.nextid += 1
+ return self.blockids[block]
+
+ def extract_sources(self, block):
+ if not block.positions:
+ return ''
+ start = min(block.positions)
+ stop = max(block.positions)
+ srcdescr = start[0]
+ if not srcdescr in self.sources:
+ self.sources[srcdescr] = list(srcdescr.get_lines())
+ lines = self.sources[srcdescr]
+ return '\\n'.join([l.strip() for l in lines[start[1] - 1:stop[1]]])
+
+ def render(self, fp, name, annotate_defs=False):
+ """Render graphviz dot graph"""
+ fp.write('digraph %s {\n' % name)
+ fp.write(' node [shape=box];\n')
+ for child in self.children:
+ child.render(fp, self, annotate_defs)
+ fp.write('}\n')
+
+ def escape(self, text):
+ return text.replace('"', '\\"').replace('\n', '\\n')
+
+
+class GV(object):
+ """Graphviz DOT renderer."""
+
+ def __init__(self, name, flow):
+ self.name = name
+ self.flow = flow
+
+ def render(self, fp, ctx, annotate_defs=False):
+ fp.write(' subgraph %s {\n' % self.name)
+ for block in self.flow.blocks:
+ label = ctx.extract_sources(block)
+ if annotate_defs:
+ for stat in block.stats:
+ if isinstance(stat, NameAssignment):
+ label += '\n %s [definition]' % stat.entry.name
+ elif isinstance(stat, NameReference):
+ if stat.entry:
+ label += '\n %s [reference]' % stat.entry.name
+ if not label:
+ label = 'empty'
+ pid = ctx.nodeid(block)
+ fp.write(' %s [label="%s"];\n' % (pid, ctx.escape(label)))
+ for block in self.flow.blocks:
+ pid = ctx.nodeid(block)
+ for child in block.children:
+ fp.write(' %s -> %s;\n' % (pid, ctx.nodeid(child)))
+ fp.write(' }\n')
+
+
+class MessageCollection(object):
+ """Collect error/warnings messages first then sort"""
+ def __init__(self):
+ self.messages = []
+
+ def error(self, pos, message):
+ self.messages.append((pos, True, message))
+
+ def warning(self, pos, message):
+ self.messages.append((pos, False, message))
+
+ def report(self):
+ self.messages.sort()
+ for pos, is_error, message in self.messages:
+ if is_error:
+ error(pos, message)
+ else:
+ warning(pos, message, 2)
+
+
+def check_definitions(flow, compiler_directives):
+ flow.initialize()
+ flow.reaching_definitions()
+
+ # Track down state
+ assignments = set()
+ # Node to entry map
+ references = {}
+ assmt_nodes = set()
+
+ for block in flow.blocks:
+ i_state = block.i_input
+ for stat in block.stats:
+ i_assmts = flow.assmts[stat.entry]
+ state = flow.map_one(i_state, stat.entry)
+ if isinstance(stat, NameAssignment):
+ stat.lhs.cf_state.update(state)
+ assmt_nodes.add(stat.lhs)
+ i_state = i_state & ~i_assmts.mask
+ if stat.is_deletion:
+ i_state |= i_assmts.bit
+ else:
+ i_state |= stat.bit
+ assignments.add(stat)
+ if stat.rhs is not fake_rhs_expr:
+ stat.entry.cf_assignments.append(stat)
+ elif isinstance(stat, NameReference):
+ references[stat.node] = stat.entry
+ stat.entry.cf_references.append(stat)
+ stat.node.cf_state.update(state)
+ ## if not stat.node.allow_null:
+ ## i_state &= ~i_assmts.bit
+ ## # after successful read, the state is known to be initialised
+ state.discard(Uninitialized)
+ state.discard(Unknown)
+ for assmt in state:
+ assmt.refs.add(stat)
+
+ # Check variable usage
+ warn_maybe_uninitialized = compiler_directives['warn.maybe_uninitialized']
+ warn_unused_result = compiler_directives['warn.unused_result']
+ warn_unused = compiler_directives['warn.unused']
+ warn_unused_arg = compiler_directives['warn.unused_arg']
+
+ messages = MessageCollection()
+
+ # assignment hints
+ for node in assmt_nodes:
+ if Uninitialized in node.cf_state:
+ node.cf_maybe_null = True
+ if len(node.cf_state) == 1:
+ node.cf_is_null = True
+ else:
+ node.cf_is_null = False
+ elif Unknown in node.cf_state:
+ node.cf_maybe_null = True
+ else:
+ node.cf_is_null = False
+ node.cf_maybe_null = False
+
+ # Find uninitialized references and cf-hints
+ for node, entry in references.iteritems():
+ if Uninitialized in node.cf_state:
+ node.cf_maybe_null = True
+ if not entry.from_closure and len(node.cf_state) == 1:
+ node.cf_is_null = True
+ if (node.allow_null or entry.from_closure
+ or entry.is_pyclass_attr or entry.type.is_error):
+ pass # Can be uninitialized here
+ elif node.cf_is_null:
+ if entry.error_on_uninitialized or (
+ Options.error_on_uninitialized and (
+ entry.type.is_pyobject or entry.type.is_unspecified)):
+ messages.error(
+ node.pos,
+ "local variable '%s' referenced before assignment"
+ % entry.name)
+ else:
+ messages.warning(
+ node.pos,
+ "local variable '%s' referenced before assignment"
+ % entry.name)
+ elif warn_maybe_uninitialized:
+ messages.warning(
+ node.pos,
+ "local variable '%s' might be referenced before assignment"
+ % entry.name)
+ elif Unknown in node.cf_state:
+ # TODO: better cross-closure analysis to know when inner functions
+ # are being called before a variable is being set, and when
+ # a variable is known to be set before even defining the
+ # inner function, etc.
+ node.cf_maybe_null = True
+ else:
+ node.cf_is_null = False
+ node.cf_maybe_null = False
+
+ # Unused result
+ for assmt in assignments:
+ if (not assmt.refs and not assmt.entry.is_pyclass_attr
+ and not assmt.entry.in_closure):
+ if assmt.entry.cf_references and warn_unused_result:
+ if assmt.is_arg:
+ messages.warning(assmt.pos, "Unused argument value '%s'" %
+ assmt.entry.name)
+ else:
+ messages.warning(assmt.pos, "Unused result in '%s'" %
+ assmt.entry.name)
+ assmt.lhs.cf_used = False
+
+ # Unused entries
+ for entry in flow.entries:
+ if (not entry.cf_references
+ and not entry.is_pyclass_attr):
+ if entry.name != '_':
+ # '_' is often used for unused variables, e.g. in loops
+ if entry.is_arg:
+ if warn_unused_arg:
+ messages.warning(entry.pos, "Unused argument '%s'" %
+ entry.name)
+ else:
+ if warn_unused:
+ messages.warning(entry.pos, "Unused entry '%s'" %
+ entry.name)
+ entry.cf_used = False
+
+ messages.report()
+
+ for node in assmt_nodes:
+ node.cf_state = ControlFlowState(node.cf_state)
+ for node in references:
+ node.cf_state = ControlFlowState(node.cf_state)
+
+
+class AssignmentCollector(TreeVisitor):
+ def __init__(self):
+ super(AssignmentCollector, self).__init__()
+ self.assignments = []
+
+ def visit_Node(self):
+ self._visitchildren(self, None)
+
+ def visit_SingleAssignmentNode(self, node):
+ self.assignments.append((node.lhs, node.rhs))
+
+ def visit_CascadedAssignmentNode(self, node):
+ for lhs in node.lhs_list:
+ self.assignments.append((lhs, node.rhs))
+
+
+class ControlFlowAnalysis(CythonTransform):
+
+ def visit_ModuleNode(self, node):
+ self.gv_ctx = GVContext()
+
+ # Set of NameNode reductions
+ self.reductions = set()
+
+ self.in_inplace_assignment = False
+ self.env_stack = []
+ self.env = node.scope
+ self.stack = []
+ self.flow = ControlFlow()
+ self.visitchildren(node)
+
+ check_definitions(self.flow, self.current_directives)
+
+ dot_output = self.current_directives['control_flow.dot_output']
+ if dot_output:
+ annotate_defs = self.current_directives['control_flow.dot_annotate_defs']
+ fp = open(dot_output, 'wt')
+ try:
+ self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs)
+ finally:
+ fp.close()
+ return node
+
+ def visit_FuncDefNode(self, node):
+ for arg in node.args:
+ if arg.default:
+ self.visitchildren(arg)
+ self.visitchildren(node, ('decorators',))
+ self.env_stack.append(self.env)
+ self.env = node.local_scope
+ self.stack.append(self.flow)
+ self.flow = ControlFlow()
+
+ # Collect all entries
+ for entry in node.local_scope.entries.values():
+ if self.flow.is_tracked(entry):
+ self.flow.entries.add(entry)
+
+ self.mark_position(node)
+ # Function body block
+ self.flow.nextblock()
+
+ for arg in node.args:
+ self._visit(arg)
+ if node.star_arg:
+ self.flow.mark_argument(node.star_arg,
+ TypedExprNode(Builtin.tuple_type,
+ may_be_none=False),
+ node.star_arg.entry)
+ if node.starstar_arg:
+ self.flow.mark_argument(node.starstar_arg,
+ TypedExprNode(Builtin.dict_type,
+ may_be_none=False),
+ node.starstar_arg.entry)
+ self._visit(node.body)
+ # Workaround for generators
+ if node.is_generator:
+ self._visit(node.gbody.body)
+
+ # Exit point
+ if self.flow.block:
+ self.flow.block.add_child(self.flow.exit_point)
+
+ # Cleanup graph
+ self.flow.normalize()
+ check_definitions(self.flow, self.current_directives)
+ self.flow.blocks.add(self.flow.entry_point)
+
+ self.gv_ctx.add(GV(node.local_scope.name, self.flow))
+
+ self.flow = self.stack.pop()
+ self.env = self.env_stack.pop()
+ return node
+
+ def visit_DefNode(self, node):
+ node.used = True
+ return self.visit_FuncDefNode(node)
+
+ def visit_GeneratorBodyDefNode(self, node):
+ return node
+
+ def visit_CTypeDefNode(self, node):
+ return node
+
+ def mark_assignment(self, lhs, rhs=None):
+ if not self.flow.block:
+ return
+ if self.flow.exceptions:
+ exc_descr = self.flow.exceptions[-1]
+ self.flow.block.add_child(exc_descr.entry_point)
+ self.flow.nextblock()
+
+ if not rhs:
+ rhs = object_expr
+ if lhs.is_name:
+ if lhs.entry is not None:
+ entry = lhs.entry
+ else:
+ entry = self.env.lookup(lhs.name)
+ if entry is None: # TODO: This shouldn't happen...
+ return
+ self.flow.mark_assignment(lhs, rhs, entry)
+ elif isinstance(lhs, ExprNodes.SequenceNode):
+ for arg in lhs.args:
+ self.mark_assignment(arg)
+ else:
+ self._visit(lhs)
+
+ if self.flow.exceptions:
+ exc_descr = self.flow.exceptions[-1]
+ self.flow.block.add_child(exc_descr.entry_point)
+ self.flow.nextblock()
+
+ def mark_position(self, node):
+ """Mark position if DOT output is enabled."""
+ if self.current_directives['control_flow.dot_output']:
+ self.flow.mark_position(node)
+
+ def visit_FromImportStatNode(self, node):
+ for name, target in node.items:
+ if name != "*":
+ self.mark_assignment(target)
+ self.visitchildren(node)
+ return node
+
+ def visit_AssignmentNode(self, node):
+ raise InternalError("Unhandled assignment node")
+
+ def visit_SingleAssignmentNode(self, node):
+ self._visit(node.rhs)
+ self.mark_assignment(node.lhs, node.rhs)
+ return node
+
+ def visit_CascadedAssignmentNode(self, node):
+ self._visit(node.rhs)
+ for lhs in node.lhs_list:
+ self.mark_assignment(lhs, node.rhs)
+ return node
+
+ def visit_ParallelAssignmentNode(self, node):
+ collector = AssignmentCollector()
+ collector.visitchildren(node)
+ for lhs, rhs in collector.assignments:
+ self._visit(rhs)
+ for lhs, rhs in collector.assignments:
+ self.mark_assignment(lhs, rhs)
+ return node
+
+ def visit_InPlaceAssignmentNode(self, node):
+ self.in_inplace_assignment = True
+ self.visitchildren(node)
+ self.in_inplace_assignment = False
+ self.mark_assignment(node.lhs, node.create_binop_node())
+ return node
+
+ def visit_DelStatNode(self, node):
+ for arg in node.args:
+ if arg.is_name:
+ entry = arg.entry or self.env.lookup(arg.name)
+ if entry.in_closure or entry.from_closure:
+ error(arg.pos,
+ "can not delete variable '%s' "
+ "referenced in nested scope" % entry.name)
+ # Mark reference
+ self._visit(arg)
+ self.flow.mark_deletion(arg, entry)
+ else:
+ self._visit(arg)
+ return node
+
+ def visit_CArgDeclNode(self, node):
+ entry = self.env.lookup(node.name)
+ if entry:
+ may_be_none = not node.not_none
+ self.flow.mark_argument(
+ node, TypedExprNode(entry.type, may_be_none), entry)
+ return node
+
+ def visit_NameNode(self, node):
+ if self.flow.block:
+ entry = node.entry or self.env.lookup(node.name)
+ if entry:
+ self.flow.mark_reference(node, entry)
+
+ if entry in self.reductions and not self.in_inplace_assignment:
+ error(node.pos,
+ "Cannot read reduction variable in loop body")
+
+ return node
+
+ def visit_StatListNode(self, node):
+ if self.flow.block:
+ for stat in node.stats:
+ self._visit(stat)
+ if not self.flow.block:
+ stat.is_terminator = True
+ break
+ return node
+
+ def visit_Node(self, node):
+ self.visitchildren(node)
+ self.mark_position(node)
+ return node
+
+ def visit_IfStatNode(self, node):
+ next_block = self.flow.newblock()
+ parent = self.flow.block
+ # If clauses
+ for clause in node.if_clauses:
+ parent = self.flow.nextblock(parent)
+ self._visit(clause.condition)
+ self.flow.nextblock()
+ self._visit(clause.body)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+ # Else clause
+ if node.else_clause:
+ self.flow.nextblock(parent=parent)
+ self._visit(node.else_clause)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+ else:
+ parent.add_child(next_block)
+
+ if next_block.parents:
+ self.flow.block = next_block
+ else:
+ self.flow.block = None
+ return node
+
+ def visit_WhileStatNode(self, node):
+ condition_block = self.flow.nextblock()
+ next_block = self.flow.newblock()
+ # Condition block
+ self.flow.loops.append(LoopDescr(next_block, condition_block))
+ if node.condition:
+ self._visit(node.condition)
+ # Body block
+ self.flow.nextblock()
+ self._visit(node.body)
+ self.flow.loops.pop()
+ # Loop it
+ if self.flow.block:
+ self.flow.block.add_child(condition_block)
+ self.flow.block.add_child(next_block)
+ # Else clause
+ if node.else_clause:
+ self.flow.nextblock(parent=condition_block)
+ self._visit(node.else_clause)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+ else:
+ condition_block.add_child(next_block)
+
+ if next_block.parents:
+ self.flow.block = next_block
+ else:
+ self.flow.block = None
+ return node
+
+ def mark_forloop_target(self, node):
+ # TODO: Remove redundancy with range optimization...
+ is_special = False
+ sequence = node.iterator.sequence
+ target = node.target
+ if isinstance(sequence, ExprNodes.SimpleCallNode):
+ function = sequence.function
+ if sequence.self is None and function.is_name:
+ entry = self.env.lookup(function.name)
+ if not entry or entry.is_builtin:
+ if function.name == 'reversed' and len(sequence.args) == 1:
+ sequence = sequence.args[0]
+ elif function.name == 'enumerate' and len(sequence.args) == 1:
+ if target.is_sequence_constructor and len(target.args) == 2:
+ iterator = sequence.args[0]
+ if iterator.is_name:
+ iterator_type = iterator.infer_type(self.env)
+ if iterator_type.is_builtin_type:
+ # assume that builtin types have a length within Py_ssize_t
+ self.mark_assignment(
+ target.args[0],
+ ExprNodes.IntNode(target.pos, value='PY_SSIZE_T_MAX',
+ type=PyrexTypes.c_py_ssize_t_type))
+ target = target.args[1]
+ sequence = sequence.args[0]
+ if isinstance(sequence, ExprNodes.SimpleCallNode):
+ function = sequence.function
+ if sequence.self is None and function.is_name:
+ entry = self.env.lookup(function.name)
+ if not entry or entry.is_builtin:
+ if function.name in ('range', 'xrange'):
+ is_special = True
+ for arg in sequence.args[:2]:
+ self.mark_assignment(target, arg)
+ if len(sequence.args) > 2:
+ self.mark_assignment(
+ target,
+ ExprNodes.binop_node(node.pos,
+ '+',
+ sequence.args[0],
+ sequence.args[2]))
+
+ if not is_special:
+ # A for-loop basically translates to subsequent calls to
+ # __getitem__(), so using an IndexNode here allows us to
+ # naturally infer the base type of pointers, C arrays,
+ # Python strings, etc., while correctly falling back to an
+ # object type when the base type cannot be handled.
+
+ self.mark_assignment(target, node.item)
+
+ def visit_ForInStatNode(self, node):
+ condition_block = self.flow.nextblock()
+ next_block = self.flow.newblock()
+ # Condition with iterator
+ self.flow.loops.append(LoopDescr(next_block, condition_block))
+ self._visit(node.iterator)
+ # Target assignment
+ self.flow.nextblock()
+
+ if isinstance(node, Nodes.ForInStatNode):
+ self.mark_forloop_target(node)
+ else: # Parallel
+ self.mark_assignment(node.target)
+
+ # Body block
+ if isinstance(node, Nodes.ParallelRangeNode):
+ # In case of an invalid
+ self._delete_privates(node, exclude=node.target.entry)
+
+ self.flow.nextblock()
+ self._visit(node.body)
+ self.flow.loops.pop()
+
+ # Loop it
+ if self.flow.block:
+ self.flow.block.add_child(condition_block)
+ # Else clause
+ if node.else_clause:
+ self.flow.nextblock(parent=condition_block)
+ self._visit(node.else_clause)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+ else:
+ condition_block.add_child(next_block)
+
+ if next_block.parents:
+ self.flow.block = next_block
+ else:
+ self.flow.block = None
+ return node
+
+ def _delete_privates(self, node, exclude=None):
+ for private_node in node.assigned_nodes:
+ if not exclude or private_node.entry is not exclude:
+ self.flow.mark_deletion(private_node, private_node.entry)
+
+ def visit_ParallelRangeNode(self, node):
+ reductions = self.reductions
+
+ # if node.target is None or not a NameNode, an error will have
+ # been previously issued
+ if hasattr(node.target, 'entry'):
+ self.reductions = set(reductions)
+
+ for private_node in node.assigned_nodes:
+ private_node.entry.error_on_uninitialized = True
+ pos, reduction = node.assignments[private_node.entry]
+ if reduction:
+ self.reductions.add(private_node.entry)
+
+ node = self.visit_ForInStatNode(node)
+
+ self.reductions = reductions
+ return node
+
+ def visit_ParallelWithBlockNode(self, node):
+ for private_node in node.assigned_nodes:
+ private_node.entry.error_on_uninitialized = True
+
+ self._delete_privates(node)
+ self.visitchildren(node)
+ self._delete_privates(node)
+
+ return node
+
+ def visit_ForFromStatNode(self, node):
+ condition_block = self.flow.nextblock()
+ next_block = self.flow.newblock()
+ # Condition with iterator
+ self.flow.loops.append(LoopDescr(next_block, condition_block))
+ self._visit(node.bound1)
+ self._visit(node.bound2)
+ if node.step is not None:
+ self._visit(node.step)
+ # Target assignment
+ self.flow.nextblock()
+ self.mark_assignment(node.target, node.bound1)
+ if node.step is not None:
+ self.mark_assignment(node.target,
+ ExprNodes.binop_node(node.pos, '+',
+ node.bound1, node.step))
+ # Body block
+ self.flow.nextblock()
+ self._visit(node.body)
+ self.flow.loops.pop()
+ # Loop it
+ if self.flow.block:
+ self.flow.block.add_child(condition_block)
+ # Else clause
+ if node.else_clause:
+ self.flow.nextblock(parent=condition_block)
+ self._visit(node.else_clause)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+ else:
+ condition_block.add_child(next_block)
+
+ if next_block.parents:
+ self.flow.block = next_block
+ else:
+ self.flow.block = None
+ return node
+
+ def visit_LoopNode(self, node):
+ raise InternalError("Generic loops are not supported")
+
+ def visit_WithTargetAssignmentStatNode(self, node):
+ self.mark_assignment(node.lhs, node.rhs)
+ return node
+
+ def visit_WithStatNode(self, node):
+ self._visit(node.manager)
+ self._visit(node.enter_call)
+ self._visit(node.body)
+ return node
+
+ def visit_TryExceptStatNode(self, node):
+ # After exception handling
+ next_block = self.flow.newblock()
+ # Body block
+ self.flow.newblock()
+ # Exception entry point
+ entry_point = self.flow.newblock()
+ self.flow.exceptions.append(ExceptionDescr(entry_point))
+ self.flow.nextblock()
+ ## XXX: links to exception handling point should be added by
+ ## XXX: children nodes
+ self.flow.block.add_child(entry_point)
+ self.flow.nextblock()
+ self._visit(node.body)
+ self.flow.exceptions.pop()
+
+ # After exception
+ if self.flow.block:
+ if node.else_clause:
+ self.flow.nextblock()
+ self._visit(node.else_clause)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+
+ for clause in node.except_clauses:
+ self.flow.block = entry_point
+ if clause.pattern:
+ for pattern in clause.pattern:
+ self._visit(pattern)
+ else:
+ # TODO: handle * pattern
+ pass
+ entry_point = self.flow.newblock(parent=self.flow.block)
+ self.flow.nextblock()
+ if clause.target:
+ self.mark_assignment(clause.target)
+ self._visit(clause.body)
+ if self.flow.block:
+ self.flow.block.add_child(next_block)
+
+ if self.flow.exceptions:
+ entry_point.add_child(self.flow.exceptions[-1].entry_point)
+
+ if next_block.parents:
+ self.flow.block = next_block
+ else:
+ self.flow.block = None
+ return node
+
+ def visit_TryFinallyStatNode(self, node):
+ body_block = self.flow.nextblock()
+
+ # Exception entry point
+ entry_point = self.flow.newblock()
+ self.flow.block = entry_point
+ self._visit(node.finally_clause)
+
+ if self.flow.block and self.flow.exceptions:
+ self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
+
+ # Normal execution
+ finally_enter = self.flow.newblock()
+ self.flow.block = finally_enter
+ self._visit(node.finally_clause)
+ finally_exit = self.flow.block
+
+ descr = ExceptionDescr(entry_point, finally_enter, finally_exit)
+ self.flow.exceptions.append(descr)
+ if self.flow.loops:
+ self.flow.loops[-1].exceptions.append(descr)
+ self.flow.block = body_block
+ ## XXX: Is it still required
+ body_block.add_child(entry_point)
+ self.flow.nextblock()
+ self._visit(node.body)
+ self.flow.exceptions.pop()
+ if self.flow.loops:
+ self.flow.loops[-1].exceptions.pop()
+
+ if self.flow.block:
+ self.flow.block.add_child(finally_enter)
+ if finally_exit:
+ self.flow.block = self.flow.nextblock(parent=finally_exit)
+ else:
+ self.flow.block = None
+ return node
+
+ def visit_RaiseStatNode(self, node):
+ self.mark_position(node)
+ self.visitchildren(node)
+ if self.flow.exceptions:
+ self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
+ self.flow.block = None
+ return node
+
+ def visit_ReraiseStatNode(self, node):
+ self.mark_position(node)
+ if self.flow.exceptions:
+ self.flow.block.add_child(self.flow.exceptions[-1].entry_point)
+ self.flow.block = None
+ return node
+
+ def visit_ReturnStatNode(self, node):
+ self.mark_position(node)
+ self.visitchildren(node)
+
+ for exception in self.flow.exceptions[::-1]:
+ if exception.finally_enter:
+ self.flow.block.add_child(exception.finally_enter)
+ if exception.finally_exit:
+ exception.finally_exit.add_child(self.flow.exit_point)
+ break
+ else:
+ if self.flow.block:
+ self.flow.block.add_child(self.flow.exit_point)
+ self.flow.block = None
+ return node
+
+ def visit_BreakStatNode(self, node):
+ if not self.flow.loops:
+ #error(node.pos, "break statement not inside loop")
+ return node
+ loop = self.flow.loops[-1]
+ self.mark_position(node)
+ for exception in loop.exceptions[::-1]:
+ if exception.finally_enter:
+ self.flow.block.add_child(exception.finally_enter)
+ if exception.finally_exit:
+ exception.finally_exit.add_child(loop.next_block)
+ break
+ else:
+ self.flow.block.add_child(loop.next_block)
+ self.flow.block = None
+ return node
+
+ def visit_ContinueStatNode(self, node):
+ if not self.flow.loops:
+ #error(node.pos, "continue statement not inside loop")
+ return node
+ loop = self.flow.loops[-1]
+ self.mark_position(node)
+ for exception in loop.exceptions[::-1]:
+ if exception.finally_enter:
+ self.flow.block.add_child(exception.finally_enter)
+ if exception.finally_exit:
+ exception.finally_exit.add_child(loop.loop_block)
+ break
+ else:
+ self.flow.block.add_child(loop.loop_block)
+ self.flow.block = None
+ return node
+
+ def visit_ComprehensionNode(self, node):
+ if node.expr_scope:
+ self.env_stack.append(self.env)
+ self.env = node.expr_scope
+ # Skip append node here
+ self._visit(node.loop)
+ if node.expr_scope:
+ self.env = self.env_stack.pop()
+ return node
+
+ def visit_ScopedExprNode(self, node):
+ if node.expr_scope:
+ self.env_stack.append(self.env)
+ self.env = node.expr_scope
+ self.visitchildren(node)
+ if node.expr_scope:
+ self.env = self.env_stack.pop()
+ return node
+
+ def visit_PyClassDefNode(self, node):
+ self.visitchildren(node, ('dict', 'metaclass',
+ 'mkw', 'bases', 'class_result'))
+ self.flow.mark_assignment(node.target, object_expr_not_none,
+ self.env.lookup(node.name))
+ self.env_stack.append(self.env)
+ self.env = node.scope
+ self.flow.nextblock()
+ self.visitchildren(node, ('body',))
+ self.flow.nextblock()
+ self.env = self.env_stack.pop()
+ return node
+
+ def visit_AmpersandNode(self, node):
+ if node.operand.is_name:
+ # Fake assignment to silence warning
+ self.mark_assignment(node.operand, fake_rhs_expr)
+ self.visitchildren(node)
+ return node
« no previous file with comments | « third_party/cython/src/Cython/Compiler/FlowControl.pxd ('k') | third_party/cython/src/Cython/Compiler/FusedNode.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698