2 cython
.declare(PyrexTypes
=object, ExprNodes
=object, Nodes
=object,
3 Builtin
=object, InternalError
=object,
4 error
=object, warning
=object,
5 py_object_type
=object, unspecified_type
=object,
6 object_expr
=object, object_expr_not_none
=object,
7 fake_rhs_expr
=object, TypedExprNode
=object)
13 from PyrexTypes
import py_object_type
, unspecified_type
16 from Visitor
import TreeVisitor
, CythonTransform
17 from Errors
import error
, warning
, InternalError
19 class TypedExprNode(ExprNodes
.ExprNode
):
20 # Used for declaring assignments of a specified type without a known entry.
21 def __init__(self
, type, may_be_none
=None, pos
=None):
22 super(TypedExprNode
, self
).__init
__(pos
)
24 self
._may
_be
_none
= may_be_none
26 def may_be_none(self
):
27 return self
._may
_be
_none
!= False
29 object_expr
= TypedExprNode(py_object_type
, may_be_none
=True)
30 object_expr_not_none
= TypedExprNode(py_object_type
, may_be_none
=False)
31 # Fake rhs to silence "unused variable" warning
32 fake_rhs_expr
= TypedExprNode(unspecified_type
)
35 class ControlBlock(object):
36 """Control flow graph node. Sequence of assignments and name references.
38 children set of children nodes
39 parents set of parent nodes
40 positions set of position markers
42 stats list of block statements
43 gen dict of assignments generated by this block
44 bounded set of entries that are definitely bounded in this block
49 b = a + c # 'c' is already bounded or exception here
51 stats = [Assignment(a), NameReference(a), NameReference(c),
53 gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)}
54 bounded = set([Entry(a), Entry(c)])
61 self
.positions
= set()
74 return (not self
.stats
and not self
.positions
)
77 """Detach block from parents and children."""
78 for child
in self
.children
:
79 child
.parents
.remove(self
)
80 for parent
in self
.parents
:
81 parent
.children
.remove(self
)
85 def add_child(self
, block
):
86 self
.children
.add(block
)
87 block
.parents
.add(self
)
90 class ExitBlock(ControlBlock
):
91 """Non-empty exit point block."""
97 class AssignmentList(object):
102 class ControlFlow(object):
103 """Control-flow graph.
105 entry_point ControlBlock entry point for this graph
106 exit_point ControlBlock normal exit point
107 block ControlBlock current block
108 blocks set children nodes
109 entries set tracked entries
110 loops list stack for loop descriptors
111 exceptions list stack for exception descriptors
120 self
.entry_point
= ControlBlock()
121 self
.exit_point
= ExitBlock()
122 self
.blocks
.add(self
.exit_point
)
123 self
.block
= self
.entry_point
125 def newblock(self
, parent
=None):
126 """Create floating block linked to `parent` if given.
128 NOTE: Block is NOT added to self.blocks
130 block
= ControlBlock()
131 self
.blocks
.add(block
)
133 parent
.add_child(block
)
136 def nextblock(self
, parent
=None):
137 """Create block children block linked to current or `parent` if given.
139 NOTE: Block is added to self.blocks
141 block
= ControlBlock()
142 self
.blocks
.add(block
)
144 parent
.add_child(block
)
146 self
.block
.add_child(block
)
150 def is_tracked(self
, entry
):
151 if entry
.is_anonymous
:
153 return (entry
.is_local
or entry
.is_pyclass_attr
or entry
.is_arg
or
154 entry
.from_closure
or entry
.in_closure
or
155 entry
.error_on_uninitialized
)
157 def is_statically_assigned(self
, entry
):
158 if (entry
.is_local
and entry
.is_variable
and
159 (entry
.type.is_struct_or_union
or
160 entry
.type.is_complex
or
161 entry
.type.is_array
or
162 entry
.type.is_cpp_class
)):
163 # stack allocated structured variable => never uninitialised
167 def mark_position(self
, node
):
168 """Mark position, will be used to draw graph nodes."""
170 self
.block
.positions
.add(node
.pos
[:2])
172 def mark_assignment(self
, lhs
, rhs
, entry
):
173 if self
.block
and self
.is_tracked(entry
):
174 assignment
= NameAssignment(lhs
, rhs
, entry
)
175 self
.block
.stats
.append(assignment
)
176 self
.block
.gen
[entry
] = assignment
177 self
.entries
.add(entry
)
179 def mark_argument(self
, lhs
, rhs
, entry
):
180 if self
.block
and self
.is_tracked(entry
):
181 assignment
= Argument(lhs
, rhs
, entry
)
182 self
.block
.stats
.append(assignment
)
183 self
.block
.gen
[entry
] = assignment
184 self
.entries
.add(entry
)
186 def mark_deletion(self
, node
, entry
):
187 if self
.block
and self
.is_tracked(entry
):
188 assignment
= NameDeletion(node
, entry
)
189 self
.block
.stats
.append(assignment
)
190 self
.block
.gen
[entry
] = Uninitialized
191 self
.entries
.add(entry
)
193 def mark_reference(self
, node
, entry
):
194 if self
.block
and self
.is_tracked(entry
):
195 self
.block
.stats
.append(NameReference(node
, entry
))
196 ## XXX: We don't track expression evaluation order so we can't use
197 ## XXX: successful reference as initialization sign.
198 ## # Local variable is definitely bound after this reference
199 ## if not node.allow_null:
200 ## self.block.bounded.add(entry)
201 self
.entries
.add(entry
)
204 """Delete unreachable and orphan blocks."""
205 queue
= set([self
.entry_point
])
210 for child
in root
.children
:
211 if child
not in visited
:
213 unreachable
= self
.blocks
- visited
214 for block
in unreachable
:
216 visited
.remove(self
.entry_point
)
217 for block
in visited
:
219 for parent
in block
.parents
: # Re-parent
220 for child
in block
.children
:
221 parent
.add_child(child
)
223 unreachable
.add(block
)
224 self
.blocks
-= unreachable
226 def initialize(self
):
227 """Set initial state, map assignments to bits."""
231 for entry
in self
.entries
:
232 assmts
= AssignmentList()
233 assmts
.mask
= assmts
.bit
= bit
234 self
.assmts
[entry
] = assmts
237 for block
in self
.blocks
:
238 for stat
in block
.stats
:
239 if isinstance(stat
, NameAssignment
):
241 assmts
= self
.assmts
[stat
.entry
]
242 assmts
.stats
.append(stat
)
246 for block
in self
.blocks
:
247 for entry
, stat
in block
.gen
.items():
248 assmts
= self
.assmts
[entry
]
249 if stat
is Uninitialized
:
250 block
.i_gen |
= assmts
.bit
252 block
.i_gen |
= stat
.bit
253 block
.i_kill |
= assmts
.mask
254 block
.i_output
= block
.i_gen
255 for entry
in block
.bounded
:
256 block
.i_kill |
= self
.assmts
[entry
].bit
258 for assmts
in self
.assmts
.itervalues():
259 self
.entry_point
.i_gen |
= assmts
.bit
260 self
.entry_point
.i_output
= self
.entry_point
.i_gen
262 def map_one(self
, istate
, entry
):
264 assmts
= self
.assmts
[entry
]
265 if istate
& assmts
.bit
:
266 if self
.is_statically_assigned(entry
):
267 ret
.add(StaticAssignment(entry
))
268 elif entry
.from_closure
:
271 ret
.add(Uninitialized
)
272 for assmt
in assmts
.stats
:
273 if istate
& assmt
.bit
:
277 def reaching_definitions(self
):
278 """Per-block reaching definitions analysis."""
282 for block
in self
.blocks
:
284 for parent
in block
.parents
:
285 i_input |
= parent
.i_output
286 i_output
= (i_input
& ~block
.i_kill
) | block
.i_gen
287 if i_output
!= block
.i_output
:
289 block
.i_input
= i_input
290 block
.i_output
= i_output
293 class LoopDescr(object):
294 def __init__(self
, next_block
, loop_block
):
295 self
.next_block
= next_block
296 self
.loop_block
= loop_block
300 class ExceptionDescr(object):
301 """Exception handling helper.
303 entry_point ControlBlock Exception handling entry point
304 finally_enter ControlBlock Normal finally clause entry point
305 finally_exit ControlBlock Normal finally clause exit point
308 def __init__(self
, entry_point
, finally_enter
=None, finally_exit
=None):
309 self
.entry_point
= entry_point
310 self
.finally_enter
= finally_enter
311 self
.finally_exit
= finally_exit
314 class NameAssignment(object):
315 def __init__(self
, lhs
, rhs
, entry
):
316 if lhs
.cf_state
is None:
324 self
.is_deletion
= False
325 self
.inferred_type
= None
328 return '%s(entry=%r)' % (self
.__class
__.__name
__, self
.entry
)
330 def infer_type(self
):
331 self
.inferred_type
= self
.rhs
.infer_type(self
.entry
.scope
)
332 return self
.inferred_type
334 def type_dependencies(self
):
335 return self
.rhs
.type_dependencies(self
.entry
.scope
)
339 if not self
.entry
.type.is_unspecified
:
340 return self
.entry
.type
341 return self
.inferred_type
344 class StaticAssignment(NameAssignment
):
345 """Initialised at declaration time, e.g. stack allocation."""
346 def __init__(self
, entry
):
347 if not entry
.type.is_pyobject
:
350 may_be_none
= None # unknown
352 entry
.type, may_be_none
=may_be_none
, pos
=entry
.pos
)
353 super(StaticAssignment
, self
).__init
__(lhs
, lhs
, entry
)
355 def infer_type(self
):
356 return self
.entry
.type
358 def type_dependencies(self
):
362 class Argument(NameAssignment
):
363 def __init__(self
, lhs
, rhs
, entry
):
364 NameAssignment
.__init
__(self
, lhs
, rhs
, entry
)
368 class NameDeletion(NameAssignment
):
369 def __init__(self
, lhs
, entry
):
370 NameAssignment
.__init
__(self
, lhs
, lhs
, entry
)
371 self
.is_deletion
= True
373 def infer_type(self
):
374 inferred_type
= self
.rhs
.infer_type(self
.entry
.scope
)
375 if (not inferred_type
.is_pyobject
and
376 inferred_type
.can_coerce_to_pyobject(self
.entry
.scope
)):
377 return py_object_type
378 self
.inferred_type
= inferred_type
382 class Uninitialized(object):
383 """Definitely not initialised yet."""
386 class Unknown(object):
387 """Coming from outer closure, might be initialised or not."""
390 class NameReference(object):
391 def __init__(self
, node
, entry
):
392 if node
.cf_state
is None:
393 node
.cf_state
= set()
399 return '%s(entry=%r)' % (self
.__class
__.__name
__, self
.entry
)
402 class ControlFlowState(list):
403 # Keeps track of Node's entry assignments
405 # cf_is_null [boolean] It is uninitialized
406 # cf_maybe_null [boolean] May be uninitialized
407 # is_single [boolean] Has only one assignment at this point
409 cf_maybe_null
= False
413 def __init__(self
, state
):
414 if Uninitialized
in state
:
415 state
.discard(Uninitialized
)
416 self
.cf_maybe_null
= True
418 self
.cf_is_null
= True
419 elif Unknown
in state
:
420 state
.discard(Unknown
)
421 self
.cf_maybe_null
= True
424 self
.is_single
= True
425 # XXX: Remove fake_rhs_expr
426 super(ControlFlowState
, self
).__init
__(
427 [i
for i
in state
if i
.rhs
is not fake_rhs_expr
])
433 class GVContext(object):
434 """Graphviz subgraph object."""
442 def add(self
, child
):
443 self
.children
.append(child
)
445 def nodeid(self
, block
):
446 if block
not in self
.blockids
:
447 self
.blockids
[block
] = 'block%d' % self
.nextid
449 return self
.blockids
[block
]
451 def extract_sources(self
, block
):
452 if not block
.positions
:
454 start
= min(block
.positions
)
455 stop
= max(block
.positions
)
457 if not srcdescr
in self
.sources
:
458 self
.sources
[srcdescr
] = list(srcdescr
.get_lines())
459 lines
= self
.sources
[srcdescr
]
460 return '\\n'.join([l
.strip() for l
in lines
[start
[1] - 1:stop
[1]]])
462 def render(self
, fp
, name
, annotate_defs
=False):
463 """Render graphviz dot graph"""
464 fp
.write('digraph %s {\n' % name
)
465 fp
.write(' node [shape=box];\n')
466 for child
in self
.children
:
467 child
.render(fp
, self
, annotate_defs
)
470 def escape(self
, text
):
471 return text
.replace('"', '\\"').replace('\n', '\\n')
475 """Graphviz DOT renderer."""
477 def __init__(self
, name
, flow
):
481 def render(self
, fp
, ctx
, annotate_defs
=False):
482 fp
.write(' subgraph %s {\n' % self
.name
)
483 for block
in self
.flow
.blocks
:
484 label
= ctx
.extract_sources(block
)
486 for stat
in block
.stats
:
487 if isinstance(stat
, NameAssignment
):
488 label
+= '\n %s [definition]' % stat
.entry
.name
489 elif isinstance(stat
, NameReference
):
491 label
+= '\n %s [reference]' % stat
.entry
.name
494 pid
= ctx
.nodeid(block
)
495 fp
.write(' %s [label="%s"];\n' % (pid
, ctx
.escape(label
)))
496 for block
in self
.flow
.blocks
:
497 pid
= ctx
.nodeid(block
)
498 for child
in block
.children
:
499 fp
.write(' %s -> %s;\n' % (pid
, ctx
.nodeid(child
)))
503 class MessageCollection(object):
504 """Collect error/warnings messages first then sort"""
508 def error(self
, pos
, message
):
509 self
.messages
.append((pos
, True, message
))
511 def warning(self
, pos
, message
):
512 self
.messages
.append((pos
, False, message
))
516 for pos
, is_error
, message
in self
.messages
:
520 warning(pos
, message
, 2)
523 def check_definitions(flow
, compiler_directives
):
525 flow
.reaching_definitions()
533 for block
in flow
.blocks
:
534 i_state
= block
.i_input
535 for stat
in block
.stats
:
536 i_assmts
= flow
.assmts
[stat
.entry
]
537 state
= flow
.map_one(i_state
, stat
.entry
)
538 if isinstance(stat
, NameAssignment
):
539 stat
.lhs
.cf_state
.update(state
)
540 assmt_nodes
.add(stat
.lhs
)
541 i_state
= i_state
& ~i_assmts
.mask
543 i_state |
= i_assmts
.bit
546 assignments
.add(stat
)
547 if stat
.rhs
is not fake_rhs_expr
:
548 stat
.entry
.cf_assignments
.append(stat
)
549 elif isinstance(stat
, NameReference
):
550 references
[stat
.node
] = stat
.entry
551 stat
.entry
.cf_references
.append(stat
)
552 stat
.node
.cf_state
.update(state
)
553 ## if not stat.node.allow_null:
554 ## i_state &= ~i_assmts.bit
555 ## # after successful read, the state is known to be initialised
556 state
.discard(Uninitialized
)
557 state
.discard(Unknown
)
561 # Check variable usage
562 warn_maybe_uninitialized
= compiler_directives
['warn.maybe_uninitialized']
563 warn_unused_result
= compiler_directives
['warn.unused_result']
564 warn_unused
= compiler_directives
['warn.unused']
565 warn_unused_arg
= compiler_directives
['warn.unused_arg']
567 messages
= MessageCollection()
570 for node
in assmt_nodes
:
571 if Uninitialized
in node
.cf_state
:
572 node
.cf_maybe_null
= True
573 if len(node
.cf_state
) == 1:
574 node
.cf_is_null
= True
576 node
.cf_is_null
= False
577 elif Unknown
in node
.cf_state
:
578 node
.cf_maybe_null
= True
580 node
.cf_is_null
= False
581 node
.cf_maybe_null
= False
583 # Find uninitialized references and cf-hints
584 for node
, entry
in references
.iteritems():
585 if Uninitialized
in node
.cf_state
:
586 node
.cf_maybe_null
= True
587 if not entry
.from_closure
and len(node
.cf_state
) == 1:
588 node
.cf_is_null
= True
589 if (node
.allow_null
or entry
.from_closure
590 or entry
.is_pyclass_attr
or entry
.type.is_error
):
591 pass # Can be uninitialized here
592 elif node
.cf_is_null
:
593 if entry
.error_on_uninitialized
or (
594 Options
.error_on_uninitialized
and (
595 entry
.type.is_pyobject
or entry
.type.is_unspecified
)):
598 "local variable '%s' referenced before assignment"
603 "local variable '%s' referenced before assignment"
605 elif warn_maybe_uninitialized
:
608 "local variable '%s' might be referenced before assignment"
610 elif Unknown
in node
.cf_state
:
611 # TODO: better cross-closure analysis to know when inner functions
612 # are being called before a variable is being set, and when
613 # a variable is known to be set before even defining the
614 # inner function, etc.
615 node
.cf_maybe_null
= True
617 node
.cf_is_null
= False
618 node
.cf_maybe_null
= False
621 for assmt
in assignments
:
622 if (not assmt
.refs
and not assmt
.entry
.is_pyclass_attr
623 and not assmt
.entry
.in_closure
):
624 if assmt
.entry
.cf_references
and warn_unused_result
:
626 messages
.warning(assmt
.pos
, "Unused argument value '%s'" %
629 messages
.warning(assmt
.pos
, "Unused result in '%s'" %
631 assmt
.lhs
.cf_used
= False
634 for entry
in flow
.entries
:
635 if (not entry
.cf_references
636 and not entry
.is_pyclass_attr
):
637 if entry
.name
!= '_':
638 # '_' is often used for unused variables, e.g. in loops
641 messages
.warning(entry
.pos
, "Unused argument '%s'" %
645 messages
.warning(entry
.pos
, "Unused entry '%s'" %
647 entry
.cf_used
= False
651 for node
in assmt_nodes
:
652 node
.cf_state
= ControlFlowState(node
.cf_state
)
653 for node
in references
:
654 node
.cf_state
= ControlFlowState(node
.cf_state
)
657 class AssignmentCollector(TreeVisitor
):
659 super(AssignmentCollector
, self
).__init
__()
660 self
.assignments
= []
662 def visit_Node(self
):
663 self
._visitchildren
(self
, None)
665 def visit_SingleAssignmentNode(self
, node
):
666 self
.assignments
.append((node
.lhs
, node
.rhs
))
668 def visit_CascadedAssignmentNode(self
, node
):
669 for lhs
in node
.lhs_list
:
670 self
.assignments
.append((lhs
, node
.rhs
))
673 class ControlFlowAnalysis(CythonTransform
):
675 def visit_ModuleNode(self
, node
):
676 self
.gv_ctx
= GVContext()
678 # Set of NameNode reductions
679 self
.reductions
= set()
681 self
.in_inplace_assignment
= False
683 self
.env
= node
.scope
685 self
.flow
= ControlFlow()
686 self
.visitchildren(node
)
688 check_definitions(self
.flow
, self
.current_directives
)
690 dot_output
= self
.current_directives
['control_flow.dot_output']
692 annotate_defs
= self
.current_directives
['control_flow.dot_annotate_defs']
693 fp
= open(dot_output
, 'wt')
695 self
.gv_ctx
.render(fp
, 'module', annotate_defs
=annotate_defs
)
700 def visit_FuncDefNode(self
, node
):
701 for arg
in node
.args
:
703 self
.visitchildren(arg
)
704 self
.visitchildren(node
, ('decorators',))
705 self
.env_stack
.append(self
.env
)
706 self
.env
= node
.local_scope
707 self
.stack
.append(self
.flow
)
708 self
.flow
= ControlFlow()
710 # Collect all entries
711 for entry
in node
.local_scope
.entries
.values():
712 if self
.flow
.is_tracked(entry
):
713 self
.flow
.entries
.add(entry
)
715 self
.mark_position(node
)
716 # Function body block
717 self
.flow
.nextblock()
719 for arg
in node
.args
:
722 self
.flow
.mark_argument(node
.star_arg
,
723 TypedExprNode(Builtin
.tuple_type
,
726 if node
.starstar_arg
:
727 self
.flow
.mark_argument(node
.starstar_arg
,
728 TypedExprNode(Builtin
.dict_type
,
730 node
.starstar_arg
.entry
)
731 self
._visit
(node
.body
)
732 # Workaround for generators
733 if node
.is_generator
:
734 self
._visit
(node
.gbody
.body
)
738 self
.flow
.block
.add_child(self
.flow
.exit_point
)
741 self
.flow
.normalize()
742 check_definitions(self
.flow
, self
.current_directives
)
743 self
.flow
.blocks
.add(self
.flow
.entry_point
)
745 self
.gv_ctx
.add(GV(node
.local_scope
.name
, self
.flow
))
747 self
.flow
= self
.stack
.pop()
748 self
.env
= self
.env_stack
.pop()
751 def visit_DefNode(self
, node
):
753 return self
.visit_FuncDefNode(node
)
755 def visit_GeneratorBodyDefNode(self
, node
):
758 def visit_CTypeDefNode(self
, node
):
761 def mark_assignment(self
, lhs
, rhs
=None):
762 if not self
.flow
.block
:
764 if self
.flow
.exceptions
:
765 exc_descr
= self
.flow
.exceptions
[-1]
766 self
.flow
.block
.add_child(exc_descr
.entry_point
)
767 self
.flow
.nextblock()
772 if lhs
.entry
is not None:
775 entry
= self
.env
.lookup(lhs
.name
)
776 if entry
is None: # TODO: This shouldn't happen...
778 self
.flow
.mark_assignment(lhs
, rhs
, entry
)
779 elif isinstance(lhs
, ExprNodes
.SequenceNode
):
781 self
.mark_assignment(arg
)
785 if self
.flow
.exceptions
:
786 exc_descr
= self
.flow
.exceptions
[-1]
787 self
.flow
.block
.add_child(exc_descr
.entry_point
)
788 self
.flow
.nextblock()
790 def mark_position(self
, node
):
791 """Mark position if DOT output is enabled."""
792 if self
.current_directives
['control_flow.dot_output']:
793 self
.flow
.mark_position(node
)
795 def visit_FromImportStatNode(self
, node
):
796 for name
, target
in node
.items
:
798 self
.mark_assignment(target
)
799 self
.visitchildren(node
)
802 def visit_AssignmentNode(self
, node
):
803 raise InternalError("Unhandled assignment node")
805 def visit_SingleAssignmentNode(self
, node
):
806 self
._visit
(node
.rhs
)
807 self
.mark_assignment(node
.lhs
, node
.rhs
)
810 def visit_CascadedAssignmentNode(self
, node
):
811 self
._visit
(node
.rhs
)
812 for lhs
in node
.lhs_list
:
813 self
.mark_assignment(lhs
, node
.rhs
)
816 def visit_ParallelAssignmentNode(self
, node
):
817 collector
= AssignmentCollector()
818 collector
.visitchildren(node
)
819 for lhs
, rhs
in collector
.assignments
:
821 for lhs
, rhs
in collector
.assignments
:
822 self
.mark_assignment(lhs
, rhs
)
825 def visit_InPlaceAssignmentNode(self
, node
):
826 self
.in_inplace_assignment
= True
827 self
.visitchildren(node
)
828 self
.in_inplace_assignment
= False
829 self
.mark_assignment(node
.lhs
, node
.create_binop_node())
832 def visit_DelStatNode(self
, node
):
833 for arg
in node
.args
:
835 entry
= arg
.entry
or self
.env
.lookup(arg
.name
)
836 if entry
.in_closure
or entry
.from_closure
:
838 "can not delete variable '%s' "
839 "referenced in nested scope" % entry
.name
)
842 self
.flow
.mark_deletion(arg
, entry
)
847 def visit_CArgDeclNode(self
, node
):
848 entry
= self
.env
.lookup(node
.name
)
850 may_be_none
= not node
.not_none
851 self
.flow
.mark_argument(
852 node
, TypedExprNode(entry
.type, may_be_none
), entry
)
855 def visit_NameNode(self
, node
):
857 entry
= node
.entry
or self
.env
.lookup(node
.name
)
859 self
.flow
.mark_reference(node
, entry
)
861 if entry
in self
.reductions
and not self
.in_inplace_assignment
:
863 "Cannot read reduction variable in loop body")
867 def visit_StatListNode(self
, node
):
869 for stat
in node
.stats
:
871 if not self
.flow
.block
:
872 stat
.is_terminator
= True
876 def visit_Node(self
, node
):
877 self
.visitchildren(node
)
878 self
.mark_position(node
)
881 def visit_IfStatNode(self
, node
):
882 next_block
= self
.flow
.newblock()
883 parent
= self
.flow
.block
885 for clause
in node
.if_clauses
:
886 parent
= self
.flow
.nextblock(parent
)
887 self
._visit
(clause
.condition
)
888 self
.flow
.nextblock()
889 self
._visit
(clause
.body
)
891 self
.flow
.block
.add_child(next_block
)
894 self
.flow
.nextblock(parent
=parent
)
895 self
._visit
(node
.else_clause
)
897 self
.flow
.block
.add_child(next_block
)
899 parent
.add_child(next_block
)
901 if next_block
.parents
:
902 self
.flow
.block
= next_block
904 self
.flow
.block
= None
907 def visit_WhileStatNode(self
, node
):
908 condition_block
= self
.flow
.nextblock()
909 next_block
= self
.flow
.newblock()
911 self
.flow
.loops
.append(LoopDescr(next_block
, condition_block
))
913 self
._visit
(node
.condition
)
915 self
.flow
.nextblock()
916 self
._visit
(node
.body
)
917 self
.flow
.loops
.pop()
920 self
.flow
.block
.add_child(condition_block
)
921 self
.flow
.block
.add_child(next_block
)
924 self
.flow
.nextblock(parent
=condition_block
)
925 self
._visit
(node
.else_clause
)
927 self
.flow
.block
.add_child(next_block
)
929 condition_block
.add_child(next_block
)
931 if next_block
.parents
:
932 self
.flow
.block
= next_block
934 self
.flow
.block
= None
937 def mark_forloop_target(self
, node
):
938 # TODO: Remove redundancy with range optimization...
940 sequence
= node
.iterator
.sequence
942 if isinstance(sequence
, ExprNodes
.SimpleCallNode
):
943 function
= sequence
.function
944 if sequence
.self
is None and function
.is_name
:
945 entry
= self
.env
.lookup(function
.name
)
946 if not entry
or entry
.is_builtin
:
947 if function
.name
== 'reversed' and len(sequence
.args
) == 1:
948 sequence
= sequence
.args
[0]
949 elif function
.name
== 'enumerate' and len(sequence
.args
) == 1:
950 if target
.is_sequence_constructor
and len(target
.args
) == 2:
951 iterator
= sequence
.args
[0]
953 iterator_type
= iterator
.infer_type(self
.env
)
954 if iterator_type
.is_builtin_type
:
955 # assume that builtin types have a length within Py_ssize_t
956 self
.mark_assignment(
958 ExprNodes
.IntNode(target
.pos
, value
='PY_SSIZE_T_MAX',
959 type=PyrexTypes
.c_py_ssize_t_type
))
960 target
= target
.args
[1]
961 sequence
= sequence
.args
[0]
962 if isinstance(sequence
, ExprNodes
.SimpleCallNode
):
963 function
= sequence
.function
964 if sequence
.self
is None and function
.is_name
:
965 entry
= self
.env
.lookup(function
.name
)
966 if not entry
or entry
.is_builtin
:
967 if function
.name
in ('range', 'xrange'):
969 for arg
in sequence
.args
[:2]:
970 self
.mark_assignment(target
, arg
)
971 if len(sequence
.args
) > 2:
972 self
.mark_assignment(
974 ExprNodes
.binop_node(node
.pos
,
980 # A for-loop basically translates to subsequent calls to
981 # __getitem__(), so using an IndexNode here allows us to
982 # naturally infer the base type of pointers, C arrays,
983 # Python strings, etc., while correctly falling back to an
984 # object type when the base type cannot be handled.
986 self
.mark_assignment(target
, node
.item
)
988 def visit_ForInStatNode(self
, node
):
989 condition_block
= self
.flow
.nextblock()
990 next_block
= self
.flow
.newblock()
991 # Condition with iterator
992 self
.flow
.loops
.append(LoopDescr(next_block
, condition_block
))
993 self
._visit
(node
.iterator
)
995 self
.flow
.nextblock()
997 if isinstance(node
, Nodes
.ForInStatNode
):
998 self
.mark_forloop_target(node
)
1000 self
.mark_assignment(node
.target
)
1003 if isinstance(node
, Nodes
.ParallelRangeNode
):
1004 # In case of an invalid
1005 self
._delete
_privates
(node
, exclude
=node
.target
.entry
)
1007 self
.flow
.nextblock()
1008 self
._visit
(node
.body
)
1009 self
.flow
.loops
.pop()
1013 self
.flow
.block
.add_child(condition_block
)
1015 if node
.else_clause
:
1016 self
.flow
.nextblock(parent
=condition_block
)
1017 self
._visit
(node
.else_clause
)
1019 self
.flow
.block
.add_child(next_block
)
1021 condition_block
.add_child(next_block
)
1023 if next_block
.parents
:
1024 self
.flow
.block
= next_block
1026 self
.flow
.block
= None
1029 def _delete_privates(self
, node
, exclude
=None):
1030 for private_node
in node
.assigned_nodes
:
1031 if not exclude
or private_node
.entry
is not exclude
:
1032 self
.flow
.mark_deletion(private_node
, private_node
.entry
)
1034 def visit_ParallelRangeNode(self
, node
):
1035 reductions
= self
.reductions
1037 # if node.target is None or not a NameNode, an error will have
1038 # been previously issued
1039 if hasattr(node
.target
, 'entry'):
1040 self
.reductions
= set(reductions
)
1042 for private_node
in node
.assigned_nodes
:
1043 private_node
.entry
.error_on_uninitialized
= True
1044 pos
, reduction
= node
.assignments
[private_node
.entry
]
1046 self
.reductions
.add(private_node
.entry
)
1048 node
= self
.visit_ForInStatNode(node
)
1050 self
.reductions
= reductions
1053 def visit_ParallelWithBlockNode(self
, node
):
1054 for private_node
in node
.assigned_nodes
:
1055 private_node
.entry
.error_on_uninitialized
= True
1057 self
._delete
_privates
(node
)
1058 self
.visitchildren(node
)
1059 self
._delete
_privates
(node
)
1063 def visit_ForFromStatNode(self
, node
):
1064 condition_block
= self
.flow
.nextblock()
1065 next_block
= self
.flow
.newblock()
1066 # Condition with iterator
1067 self
.flow
.loops
.append(LoopDescr(next_block
, condition_block
))
1068 self
._visit
(node
.bound1
)
1069 self
._visit
(node
.bound2
)
1070 if node
.step
is not None:
1071 self
._visit
(node
.step
)
1073 self
.flow
.nextblock()
1074 self
.mark_assignment(node
.target
, node
.bound1
)
1075 if node
.step
is not None:
1076 self
.mark_assignment(node
.target
,
1077 ExprNodes
.binop_node(node
.pos
, '+',
1078 node
.bound1
, node
.step
))
1080 self
.flow
.nextblock()
1081 self
._visit
(node
.body
)
1082 self
.flow
.loops
.pop()
1085 self
.flow
.block
.add_child(condition_block
)
1087 if node
.else_clause
:
1088 self
.flow
.nextblock(parent
=condition_block
)
1089 self
._visit
(node
.else_clause
)
1091 self
.flow
.block
.add_child(next_block
)
1093 condition_block
.add_child(next_block
)
1095 if next_block
.parents
:
1096 self
.flow
.block
= next_block
1098 self
.flow
.block
= None
1101 def visit_LoopNode(self
, node
):
1102 raise InternalError("Generic loops are not supported")
1104 def visit_WithTargetAssignmentStatNode(self
, node
):
1105 self
.mark_assignment(node
.lhs
, node
.rhs
)
1108 def visit_WithStatNode(self
, node
):
1109 self
._visit
(node
.manager
)
1110 self
._visit
(node
.enter_call
)
1111 self
._visit
(node
.body
)
1114 def visit_TryExceptStatNode(self
, node
):
1115 # After exception handling
1116 next_block
= self
.flow
.newblock()
1118 self
.flow
.newblock()
1119 # Exception entry point
1120 entry_point
= self
.flow
.newblock()
1121 self
.flow
.exceptions
.append(ExceptionDescr(entry_point
))
1122 self
.flow
.nextblock()
1123 ## XXX: links to exception handling point should be added by
1124 ## XXX: children nodes
1125 self
.flow
.block
.add_child(entry_point
)
1126 self
.flow
.nextblock()
1127 self
._visit
(node
.body
)
1128 self
.flow
.exceptions
.pop()
1132 if node
.else_clause
:
1133 self
.flow
.nextblock()
1134 self
._visit
(node
.else_clause
)
1136 self
.flow
.block
.add_child(next_block
)
1138 for clause
in node
.except_clauses
:
1139 self
.flow
.block
= entry_point
1141 for pattern
in clause
.pattern
:
1142 self
._visit
(pattern
)
1144 # TODO: handle * pattern
1146 entry_point
= self
.flow
.newblock(parent
=self
.flow
.block
)
1147 self
.flow
.nextblock()
1149 self
.mark_assignment(clause
.target
)
1150 self
._visit
(clause
.body
)
1152 self
.flow
.block
.add_child(next_block
)
1154 if self
.flow
.exceptions
:
1155 entry_point
.add_child(self
.flow
.exceptions
[-1].entry_point
)
1157 if next_block
.parents
:
1158 self
.flow
.block
= next_block
1160 self
.flow
.block
= None
1163 def visit_TryFinallyStatNode(self
, node
):
1164 body_block
= self
.flow
.nextblock()
1166 # Exception entry point
1167 entry_point
= self
.flow
.newblock()
1168 self
.flow
.block
= entry_point
1169 self
._visit
(node
.finally_clause
)
1171 if self
.flow
.block
and self
.flow
.exceptions
:
1172 self
.flow
.block
.add_child(self
.flow
.exceptions
[-1].entry_point
)
1175 finally_enter
= self
.flow
.newblock()
1176 self
.flow
.block
= finally_enter
1177 self
._visit
(node
.finally_clause
)
1178 finally_exit
= self
.flow
.block
1180 descr
= ExceptionDescr(entry_point
, finally_enter
, finally_exit
)
1181 self
.flow
.exceptions
.append(descr
)
1183 self
.flow
.loops
[-1].exceptions
.append(descr
)
1184 self
.flow
.block
= body_block
1185 ## XXX: Is it still required
1186 body_block
.add_child(entry_point
)
1187 self
.flow
.nextblock()
1188 self
._visit
(node
.body
)
1189 self
.flow
.exceptions
.pop()
1191 self
.flow
.loops
[-1].exceptions
.pop()
1194 self
.flow
.block
.add_child(finally_enter
)
1196 self
.flow
.block
= self
.flow
.nextblock(parent
=finally_exit
)
1198 self
.flow
.block
= None
1201 def visit_RaiseStatNode(self
, node
):
1202 self
.mark_position(node
)
1203 self
.visitchildren(node
)
1204 if self
.flow
.exceptions
:
1205 self
.flow
.block
.add_child(self
.flow
.exceptions
[-1].entry_point
)
1206 self
.flow
.block
= None
1209 def visit_ReraiseStatNode(self
, node
):
1210 self
.mark_position(node
)
1211 if self
.flow
.exceptions
:
1212 self
.flow
.block
.add_child(self
.flow
.exceptions
[-1].entry_point
)
1213 self
.flow
.block
= None
1216 def visit_ReturnStatNode(self
, node
):
1217 self
.mark_position(node
)
1218 self
.visitchildren(node
)
1220 for exception
in self
.flow
.exceptions
[::-1]:
1221 if exception
.finally_enter
:
1222 self
.flow
.block
.add_child(exception
.finally_enter
)
1223 if exception
.finally_exit
:
1224 exception
.finally_exit
.add_child(self
.flow
.exit_point
)
1228 self
.flow
.block
.add_child(self
.flow
.exit_point
)
1229 self
.flow
.block
= None
1232 def visit_BreakStatNode(self
, node
):
1233 if not self
.flow
.loops
:
1234 #error(node.pos, "break statement not inside loop")
1236 loop
= self
.flow
.loops
[-1]
1237 self
.mark_position(node
)
1238 for exception
in loop
.exceptions
[::-1]:
1239 if exception
.finally_enter
:
1240 self
.flow
.block
.add_child(exception
.finally_enter
)
1241 if exception
.finally_exit
:
1242 exception
.finally_exit
.add_child(loop
.next_block
)
1245 self
.flow
.block
.add_child(loop
.next_block
)
1246 self
.flow
.block
= None
1249 def visit_ContinueStatNode(self
, node
):
1250 if not self
.flow
.loops
:
1251 #error(node.pos, "continue statement not inside loop")
1253 loop
= self
.flow
.loops
[-1]
1254 self
.mark_position(node
)
1255 for exception
in loop
.exceptions
[::-1]:
1256 if exception
.finally_enter
:
1257 self
.flow
.block
.add_child(exception
.finally_enter
)
1258 if exception
.finally_exit
:
1259 exception
.finally_exit
.add_child(loop
.loop_block
)
1262 self
.flow
.block
.add_child(loop
.loop_block
)
1263 self
.flow
.block
= None
1266 def visit_ComprehensionNode(self
, node
):
1268 self
.env_stack
.append(self
.env
)
1269 self
.env
= node
.expr_scope
1270 # Skip append node here
1271 self
._visit
(node
.loop
)
1273 self
.env
= self
.env_stack
.pop()
1276 def visit_ScopedExprNode(self
, node
):
1278 self
.env_stack
.append(self
.env
)
1279 self
.env
= node
.expr_scope
1280 self
.visitchildren(node
)
1282 self
.env
= self
.env_stack
.pop()
1285 def visit_PyClassDefNode(self
, node
):
1286 self
.visitchildren(node
, ('dict', 'metaclass',
1287 'mkw', 'bases', 'class_result'))
1288 self
.flow
.mark_assignment(node
.target
, object_expr_not_none
,
1289 self
.env
.lookup(node
.name
))
1290 self
.env_stack
.append(self
.env
)
1291 self
.env
= node
.scope
1292 self
.flow
.nextblock()
1293 self
.visitchildren(node
, ('body',))
1294 self
.flow
.nextblock()
1295 self
.env
= self
.env_stack
.pop()
1298 def visit_AmpersandNode(self
, node
):
1299 if node
.operand
.is_name
:
1300 # Fake assignment to silence warning
1301 self
.mark_assignment(node
.operand
, fake_rhs_expr
)
1302 self
.visitchildren(node
)