OLD | NEW |
(Empty) | |
| 1 import cython |
| 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) |
| 8 |
| 9 import Builtin |
| 10 import ExprNodes |
| 11 import Nodes |
| 12 import Options |
| 13 from PyrexTypes import py_object_type, unspecified_type |
| 14 import PyrexTypes |
| 15 |
| 16 from Visitor import TreeVisitor, CythonTransform |
| 17 from Errors import error, warning, InternalError |
| 18 |
| 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) |
| 23 self.type = type |
| 24 self._may_be_none = may_be_none |
| 25 |
| 26 def may_be_none(self): |
| 27 return self._may_be_none != False |
| 28 |
| 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) |
| 33 |
| 34 |
| 35 class ControlBlock(object): |
| 36 """Control flow graph node. Sequence of assignments and name references. |
| 37 |
| 38 children set of children nodes |
| 39 parents set of parent nodes |
| 40 positions set of position markers |
| 41 |
| 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 |
| 45 |
| 46 Example: |
| 47 |
| 48 a = 1 |
| 49 b = a + c # 'c' is already bounded or exception here |
| 50 |
| 51 stats = [Assignment(a), NameReference(a), NameReference(c), |
| 52 Assignment(b)] |
| 53 gen = {Entry(a): Assignment(a), Entry(b): Assignment(b)} |
| 54 bounded = set([Entry(a), Entry(c)]) |
| 55 |
| 56 """ |
| 57 |
| 58 def __init__(self): |
| 59 self.children = set() |
| 60 self.parents = set() |
| 61 self.positions = set() |
| 62 |
| 63 self.stats = [] |
| 64 self.gen = {} |
| 65 self.bounded = set() |
| 66 |
| 67 self.i_input = 0 |
| 68 self.i_output = 0 |
| 69 self.i_gen = 0 |
| 70 self.i_kill = 0 |
| 71 self.i_state = 0 |
| 72 |
| 73 def empty(self): |
| 74 return (not self.stats and not self.positions) |
| 75 |
| 76 def detach(self): |
| 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) |
| 82 self.parents.clear() |
| 83 self.children.clear() |
| 84 |
| 85 def add_child(self, block): |
| 86 self.children.add(block) |
| 87 block.parents.add(self) |
| 88 |
| 89 |
| 90 class ExitBlock(ControlBlock): |
| 91 """Non-empty exit point block.""" |
| 92 |
| 93 def empty(self): |
| 94 return False |
| 95 |
| 96 |
| 97 class AssignmentList(object): |
| 98 def __init__(self): |
| 99 self.stats = [] |
| 100 |
| 101 |
| 102 class ControlFlow(object): |
| 103 """Control-flow graph. |
| 104 |
| 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 |
| 112 """ |
| 113 |
| 114 def __init__(self): |
| 115 self.blocks = set() |
| 116 self.entries = set() |
| 117 self.loops = [] |
| 118 self.exceptions = [] |
| 119 |
| 120 self.entry_point = ControlBlock() |
| 121 self.exit_point = ExitBlock() |
| 122 self.blocks.add(self.exit_point) |
| 123 self.block = self.entry_point |
| 124 |
| 125 def newblock(self, parent=None): |
| 126 """Create floating block linked to `parent` if given. |
| 127 |
| 128 NOTE: Block is NOT added to self.blocks |
| 129 """ |
| 130 block = ControlBlock() |
| 131 self.blocks.add(block) |
| 132 if parent: |
| 133 parent.add_child(block) |
| 134 return block |
| 135 |
| 136 def nextblock(self, parent=None): |
| 137 """Create block children block linked to current or `parent` if given. |
| 138 |
| 139 NOTE: Block is added to self.blocks |
| 140 """ |
| 141 block = ControlBlock() |
| 142 self.blocks.add(block) |
| 143 if parent: |
| 144 parent.add_child(block) |
| 145 elif self.block: |
| 146 self.block.add_child(block) |
| 147 self.block = block |
| 148 return self.block |
| 149 |
| 150 def is_tracked(self, entry): |
| 151 if entry.is_anonymous: |
| 152 return False |
| 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) |
| 156 |
| 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 |
| 164 return True |
| 165 return False |
| 166 |
| 167 def mark_position(self, node): |
| 168 """Mark position, will be used to draw graph nodes.""" |
| 169 if self.block: |
| 170 self.block.positions.add(node.pos[:2]) |
| 171 |
| 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) |
| 178 |
| 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) |
| 185 |
| 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) |
| 192 |
| 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) |
| 202 |
| 203 def normalize(self): |
| 204 """Delete unreachable and orphan blocks.""" |
| 205 queue = set([self.entry_point]) |
| 206 visited = set() |
| 207 while queue: |
| 208 root = queue.pop() |
| 209 visited.add(root) |
| 210 for child in root.children: |
| 211 if child not in visited: |
| 212 queue.add(child) |
| 213 unreachable = self.blocks - visited |
| 214 for block in unreachable: |
| 215 block.detach() |
| 216 visited.remove(self.entry_point) |
| 217 for block in visited: |
| 218 if block.empty(): |
| 219 for parent in block.parents: # Re-parent |
| 220 for child in block.children: |
| 221 parent.add_child(child) |
| 222 block.detach() |
| 223 unreachable.add(block) |
| 224 self.blocks -= unreachable |
| 225 |
| 226 def initialize(self): |
| 227 """Set initial state, map assignments to bits.""" |
| 228 self.assmts = {} |
| 229 |
| 230 bit = 1 |
| 231 for entry in self.entries: |
| 232 assmts = AssignmentList() |
| 233 assmts.mask = assmts.bit = bit |
| 234 self.assmts[entry] = assmts |
| 235 bit <<= 1 |
| 236 |
| 237 for block in self.blocks: |
| 238 for stat in block.stats: |
| 239 if isinstance(stat, NameAssignment): |
| 240 stat.bit = bit |
| 241 assmts = self.assmts[stat.entry] |
| 242 assmts.stats.append(stat) |
| 243 assmts.mask |= bit |
| 244 bit <<= 1 |
| 245 |
| 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 |
| 251 else: |
| 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 |
| 257 |
| 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 |
| 261 |
| 262 def map_one(self, istate, entry): |
| 263 ret = set() |
| 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: |
| 269 ret.add(Unknown) |
| 270 else: |
| 271 ret.add(Uninitialized) |
| 272 for assmt in assmts.stats: |
| 273 if istate & assmt.bit: |
| 274 ret.add(assmt) |
| 275 return ret |
| 276 |
| 277 def reaching_definitions(self): |
| 278 """Per-block reaching definitions analysis.""" |
| 279 dirty = True |
| 280 while dirty: |
| 281 dirty = False |
| 282 for block in self.blocks: |
| 283 i_input = 0 |
| 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: |
| 288 dirty = True |
| 289 block.i_input = i_input |
| 290 block.i_output = i_output |
| 291 |
| 292 |
| 293 class LoopDescr(object): |
| 294 def __init__(self, next_block, loop_block): |
| 295 self.next_block = next_block |
| 296 self.loop_block = loop_block |
| 297 self.exceptions = [] |
| 298 |
| 299 |
| 300 class ExceptionDescr(object): |
| 301 """Exception handling helper. |
| 302 |
| 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 |
| 306 """ |
| 307 |
| 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 |
| 312 |
| 313 |
| 314 class NameAssignment(object): |
| 315 def __init__(self, lhs, rhs, entry): |
| 316 if lhs.cf_state is None: |
| 317 lhs.cf_state = set() |
| 318 self.lhs = lhs |
| 319 self.rhs = rhs |
| 320 self.entry = entry |
| 321 self.pos = lhs.pos |
| 322 self.refs = set() |
| 323 self.is_arg = False |
| 324 self.is_deletion = False |
| 325 self.inferred_type = None |
| 326 |
| 327 def __repr__(self): |
| 328 return '%s(entry=%r)' % (self.__class__.__name__, self.entry) |
| 329 |
| 330 def infer_type(self): |
| 331 self.inferred_type = self.rhs.infer_type(self.entry.scope) |
| 332 return self.inferred_type |
| 333 |
| 334 def type_dependencies(self): |
| 335 return self.rhs.type_dependencies(self.entry.scope) |
| 336 |
| 337 @property |
| 338 def type(self): |
| 339 if not self.entry.type.is_unspecified: |
| 340 return self.entry.type |
| 341 return self.inferred_type |
| 342 |
| 343 |
| 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: |
| 348 may_be_none = False |
| 349 else: |
| 350 may_be_none = None # unknown |
| 351 lhs = TypedExprNode( |
| 352 entry.type, may_be_none=may_be_none, pos=entry.pos) |
| 353 super(StaticAssignment, self).__init__(lhs, lhs, entry) |
| 354 |
| 355 def infer_type(self): |
| 356 return self.entry.type |
| 357 |
| 358 def type_dependencies(self): |
| 359 return () |
| 360 |
| 361 |
| 362 class Argument(NameAssignment): |
| 363 def __init__(self, lhs, rhs, entry): |
| 364 NameAssignment.__init__(self, lhs, rhs, entry) |
| 365 self.is_arg = True |
| 366 |
| 367 |
| 368 class NameDeletion(NameAssignment): |
| 369 def __init__(self, lhs, entry): |
| 370 NameAssignment.__init__(self, lhs, lhs, entry) |
| 371 self.is_deletion = True |
| 372 |
| 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 |
| 379 return inferred_type |
| 380 |
| 381 |
| 382 class Uninitialized(object): |
| 383 """Definitely not initialised yet.""" |
| 384 |
| 385 |
| 386 class Unknown(object): |
| 387 """Coming from outer closure, might be initialised or not.""" |
| 388 |
| 389 |
| 390 class NameReference(object): |
| 391 def __init__(self, node, entry): |
| 392 if node.cf_state is None: |
| 393 node.cf_state = set() |
| 394 self.node = node |
| 395 self.entry = entry |
| 396 self.pos = node.pos |
| 397 |
| 398 def __repr__(self): |
| 399 return '%s(entry=%r)' % (self.__class__.__name__, self.entry) |
| 400 |
| 401 |
| 402 class ControlFlowState(list): |
| 403 # Keeps track of Node's entry assignments |
| 404 # |
| 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 |
| 408 |
| 409 cf_maybe_null = False |
| 410 cf_is_null = False |
| 411 is_single = False |
| 412 |
| 413 def __init__(self, state): |
| 414 if Uninitialized in state: |
| 415 state.discard(Uninitialized) |
| 416 self.cf_maybe_null = True |
| 417 if not state: |
| 418 self.cf_is_null = True |
| 419 elif Unknown in state: |
| 420 state.discard(Unknown) |
| 421 self.cf_maybe_null = True |
| 422 else: |
| 423 if len(state) == 1: |
| 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]) |
| 428 |
| 429 def one(self): |
| 430 return self[0] |
| 431 |
| 432 |
| 433 class GVContext(object): |
| 434 """Graphviz subgraph object.""" |
| 435 |
| 436 def __init__(self): |
| 437 self.blockids = {} |
| 438 self.nextid = 0 |
| 439 self.children = [] |
| 440 self.sources = {} |
| 441 |
| 442 def add(self, child): |
| 443 self.children.append(child) |
| 444 |
| 445 def nodeid(self, block): |
| 446 if block not in self.blockids: |
| 447 self.blockids[block] = 'block%d' % self.nextid |
| 448 self.nextid += 1 |
| 449 return self.blockids[block] |
| 450 |
| 451 def extract_sources(self, block): |
| 452 if not block.positions: |
| 453 return '' |
| 454 start = min(block.positions) |
| 455 stop = max(block.positions) |
| 456 srcdescr = start[0] |
| 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]]]) |
| 461 |
| 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) |
| 468 fp.write('}\n') |
| 469 |
| 470 def escape(self, text): |
| 471 return text.replace('"', '\\"').replace('\n', '\\n') |
| 472 |
| 473 |
| 474 class GV(object): |
| 475 """Graphviz DOT renderer.""" |
| 476 |
| 477 def __init__(self, name, flow): |
| 478 self.name = name |
| 479 self.flow = flow |
| 480 |
| 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) |
| 485 if annotate_defs: |
| 486 for stat in block.stats: |
| 487 if isinstance(stat, NameAssignment): |
| 488 label += '\n %s [definition]' % stat.entry.name |
| 489 elif isinstance(stat, NameReference): |
| 490 if stat.entry: |
| 491 label += '\n %s [reference]' % stat.entry.name |
| 492 if not label: |
| 493 label = 'empty' |
| 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))) |
| 500 fp.write(' }\n') |
| 501 |
| 502 |
| 503 class MessageCollection(object): |
| 504 """Collect error/warnings messages first then sort""" |
| 505 def __init__(self): |
| 506 self.messages = [] |
| 507 |
| 508 def error(self, pos, message): |
| 509 self.messages.append((pos, True, message)) |
| 510 |
| 511 def warning(self, pos, message): |
| 512 self.messages.append((pos, False, message)) |
| 513 |
| 514 def report(self): |
| 515 self.messages.sort() |
| 516 for pos, is_error, message in self.messages: |
| 517 if is_error: |
| 518 error(pos, message) |
| 519 else: |
| 520 warning(pos, message, 2) |
| 521 |
| 522 |
| 523 def check_definitions(flow, compiler_directives): |
| 524 flow.initialize() |
| 525 flow.reaching_definitions() |
| 526 |
| 527 # Track down state |
| 528 assignments = set() |
| 529 # Node to entry map |
| 530 references = {} |
| 531 assmt_nodes = set() |
| 532 |
| 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 |
| 542 if stat.is_deletion: |
| 543 i_state |= i_assmts.bit |
| 544 else: |
| 545 i_state |= stat.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) |
| 558 for assmt in state: |
| 559 assmt.refs.add(stat) |
| 560 |
| 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'] |
| 566 |
| 567 messages = MessageCollection() |
| 568 |
| 569 # assignment hints |
| 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 |
| 575 else: |
| 576 node.cf_is_null = False |
| 577 elif Unknown in node.cf_state: |
| 578 node.cf_maybe_null = True |
| 579 else: |
| 580 node.cf_is_null = False |
| 581 node.cf_maybe_null = False |
| 582 |
| 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)): |
| 596 messages.error( |
| 597 node.pos, |
| 598 "local variable '%s' referenced before assignment" |
| 599 % entry.name) |
| 600 else: |
| 601 messages.warning( |
| 602 node.pos, |
| 603 "local variable '%s' referenced before assignment" |
| 604 % entry.name) |
| 605 elif warn_maybe_uninitialized: |
| 606 messages.warning( |
| 607 node.pos, |
| 608 "local variable '%s' might be referenced before assignment" |
| 609 % entry.name) |
| 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 |
| 616 else: |
| 617 node.cf_is_null = False |
| 618 node.cf_maybe_null = False |
| 619 |
| 620 # Unused result |
| 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: |
| 625 if assmt.is_arg: |
| 626 messages.warning(assmt.pos, "Unused argument value '%s'" % |
| 627 assmt.entry.name) |
| 628 else: |
| 629 messages.warning(assmt.pos, "Unused result in '%s'" % |
| 630 assmt.entry.name) |
| 631 assmt.lhs.cf_used = False |
| 632 |
| 633 # Unused entries |
| 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 |
| 639 if entry.is_arg: |
| 640 if warn_unused_arg: |
| 641 messages.warning(entry.pos, "Unused argument '%s'" % |
| 642 entry.name) |
| 643 else: |
| 644 if warn_unused: |
| 645 messages.warning(entry.pos, "Unused entry '%s'" % |
| 646 entry.name) |
| 647 entry.cf_used = False |
| 648 |
| 649 messages.report() |
| 650 |
| 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) |
| 655 |
| 656 |
| 657 class AssignmentCollector(TreeVisitor): |
| 658 def __init__(self): |
| 659 super(AssignmentCollector, self).__init__() |
| 660 self.assignments = [] |
| 661 |
| 662 def visit_Node(self): |
| 663 self._visitchildren(self, None) |
| 664 |
| 665 def visit_SingleAssignmentNode(self, node): |
| 666 self.assignments.append((node.lhs, node.rhs)) |
| 667 |
| 668 def visit_CascadedAssignmentNode(self, node): |
| 669 for lhs in node.lhs_list: |
| 670 self.assignments.append((lhs, node.rhs)) |
| 671 |
| 672 |
| 673 class ControlFlowAnalysis(CythonTransform): |
| 674 |
| 675 def visit_ModuleNode(self, node): |
| 676 self.gv_ctx = GVContext() |
| 677 |
| 678 # Set of NameNode reductions |
| 679 self.reductions = set() |
| 680 |
| 681 self.in_inplace_assignment = False |
| 682 self.env_stack = [] |
| 683 self.env = node.scope |
| 684 self.stack = [] |
| 685 self.flow = ControlFlow() |
| 686 self.visitchildren(node) |
| 687 |
| 688 check_definitions(self.flow, self.current_directives) |
| 689 |
| 690 dot_output = self.current_directives['control_flow.dot_output'] |
| 691 if dot_output: |
| 692 annotate_defs = self.current_directives['control_flow.dot_annotate_d
efs'] |
| 693 fp = open(dot_output, 'wt') |
| 694 try: |
| 695 self.gv_ctx.render(fp, 'module', annotate_defs=annotate_defs) |
| 696 finally: |
| 697 fp.close() |
| 698 return node |
| 699 |
| 700 def visit_FuncDefNode(self, node): |
| 701 for arg in node.args: |
| 702 if arg.default: |
| 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() |
| 709 |
| 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) |
| 714 |
| 715 self.mark_position(node) |
| 716 # Function body block |
| 717 self.flow.nextblock() |
| 718 |
| 719 for arg in node.args: |
| 720 self._visit(arg) |
| 721 if node.star_arg: |
| 722 self.flow.mark_argument(node.star_arg, |
| 723 TypedExprNode(Builtin.tuple_type, |
| 724 may_be_none=False), |
| 725 node.star_arg.entry) |
| 726 if node.starstar_arg: |
| 727 self.flow.mark_argument(node.starstar_arg, |
| 728 TypedExprNode(Builtin.dict_type, |
| 729 may_be_none=False), |
| 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) |
| 735 |
| 736 # Exit point |
| 737 if self.flow.block: |
| 738 self.flow.block.add_child(self.flow.exit_point) |
| 739 |
| 740 # Cleanup graph |
| 741 self.flow.normalize() |
| 742 check_definitions(self.flow, self.current_directives) |
| 743 self.flow.blocks.add(self.flow.entry_point) |
| 744 |
| 745 self.gv_ctx.add(GV(node.local_scope.name, self.flow)) |
| 746 |
| 747 self.flow = self.stack.pop() |
| 748 self.env = self.env_stack.pop() |
| 749 return node |
| 750 |
| 751 def visit_DefNode(self, node): |
| 752 node.used = True |
| 753 return self.visit_FuncDefNode(node) |
| 754 |
| 755 def visit_GeneratorBodyDefNode(self, node): |
| 756 return node |
| 757 |
| 758 def visit_CTypeDefNode(self, node): |
| 759 return node |
| 760 |
| 761 def mark_assignment(self, lhs, rhs=None): |
| 762 if not self.flow.block: |
| 763 return |
| 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() |
| 768 |
| 769 if not rhs: |
| 770 rhs = object_expr |
| 771 if lhs.is_name: |
| 772 if lhs.entry is not None: |
| 773 entry = lhs.entry |
| 774 else: |
| 775 entry = self.env.lookup(lhs.name) |
| 776 if entry is None: # TODO: This shouldn't happen... |
| 777 return |
| 778 self.flow.mark_assignment(lhs, rhs, entry) |
| 779 elif isinstance(lhs, ExprNodes.SequenceNode): |
| 780 for arg in lhs.args: |
| 781 self.mark_assignment(arg) |
| 782 else: |
| 783 self._visit(lhs) |
| 784 |
| 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() |
| 789 |
| 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) |
| 794 |
| 795 def visit_FromImportStatNode(self, node): |
| 796 for name, target in node.items: |
| 797 if name != "*": |
| 798 self.mark_assignment(target) |
| 799 self.visitchildren(node) |
| 800 return node |
| 801 |
| 802 def visit_AssignmentNode(self, node): |
| 803 raise InternalError("Unhandled assignment node") |
| 804 |
| 805 def visit_SingleAssignmentNode(self, node): |
| 806 self._visit(node.rhs) |
| 807 self.mark_assignment(node.lhs, node.rhs) |
| 808 return node |
| 809 |
| 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) |
| 814 return node |
| 815 |
| 816 def visit_ParallelAssignmentNode(self, node): |
| 817 collector = AssignmentCollector() |
| 818 collector.visitchildren(node) |
| 819 for lhs, rhs in collector.assignments: |
| 820 self._visit(rhs) |
| 821 for lhs, rhs in collector.assignments: |
| 822 self.mark_assignment(lhs, rhs) |
| 823 return node |
| 824 |
| 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()) |
| 830 return node |
| 831 |
| 832 def visit_DelStatNode(self, node): |
| 833 for arg in node.args: |
| 834 if arg.is_name: |
| 835 entry = arg.entry or self.env.lookup(arg.name) |
| 836 if entry.in_closure or entry.from_closure: |
| 837 error(arg.pos, |
| 838 "can not delete variable '%s' " |
| 839 "referenced in nested scope" % entry.name) |
| 840 # Mark reference |
| 841 self._visit(arg) |
| 842 self.flow.mark_deletion(arg, entry) |
| 843 else: |
| 844 self._visit(arg) |
| 845 return node |
| 846 |
| 847 def visit_CArgDeclNode(self, node): |
| 848 entry = self.env.lookup(node.name) |
| 849 if entry: |
| 850 may_be_none = not node.not_none |
| 851 self.flow.mark_argument( |
| 852 node, TypedExprNode(entry.type, may_be_none), entry) |
| 853 return node |
| 854 |
| 855 def visit_NameNode(self, node): |
| 856 if self.flow.block: |
| 857 entry = node.entry or self.env.lookup(node.name) |
| 858 if entry: |
| 859 self.flow.mark_reference(node, entry) |
| 860 |
| 861 if entry in self.reductions and not self.in_inplace_assignment: |
| 862 error(node.pos, |
| 863 "Cannot read reduction variable in loop body") |
| 864 |
| 865 return node |
| 866 |
| 867 def visit_StatListNode(self, node): |
| 868 if self.flow.block: |
| 869 for stat in node.stats: |
| 870 self._visit(stat) |
| 871 if not self.flow.block: |
| 872 stat.is_terminator = True |
| 873 break |
| 874 return node |
| 875 |
| 876 def visit_Node(self, node): |
| 877 self.visitchildren(node) |
| 878 self.mark_position(node) |
| 879 return node |
| 880 |
| 881 def visit_IfStatNode(self, node): |
| 882 next_block = self.flow.newblock() |
| 883 parent = self.flow.block |
| 884 # If clauses |
| 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) |
| 890 if self.flow.block: |
| 891 self.flow.block.add_child(next_block) |
| 892 # Else clause |
| 893 if node.else_clause: |
| 894 self.flow.nextblock(parent=parent) |
| 895 self._visit(node.else_clause) |
| 896 if self.flow.block: |
| 897 self.flow.block.add_child(next_block) |
| 898 else: |
| 899 parent.add_child(next_block) |
| 900 |
| 901 if next_block.parents: |
| 902 self.flow.block = next_block |
| 903 else: |
| 904 self.flow.block = None |
| 905 return node |
| 906 |
| 907 def visit_WhileStatNode(self, node): |
| 908 condition_block = self.flow.nextblock() |
| 909 next_block = self.flow.newblock() |
| 910 # Condition block |
| 911 self.flow.loops.append(LoopDescr(next_block, condition_block)) |
| 912 if node.condition: |
| 913 self._visit(node.condition) |
| 914 # Body block |
| 915 self.flow.nextblock() |
| 916 self._visit(node.body) |
| 917 self.flow.loops.pop() |
| 918 # Loop it |
| 919 if self.flow.block: |
| 920 self.flow.block.add_child(condition_block) |
| 921 self.flow.block.add_child(next_block) |
| 922 # Else clause |
| 923 if node.else_clause: |
| 924 self.flow.nextblock(parent=condition_block) |
| 925 self._visit(node.else_clause) |
| 926 if self.flow.block: |
| 927 self.flow.block.add_child(next_block) |
| 928 else: |
| 929 condition_block.add_child(next_block) |
| 930 |
| 931 if next_block.parents: |
| 932 self.flow.block = next_block |
| 933 else: |
| 934 self.flow.block = None |
| 935 return node |
| 936 |
| 937 def mark_forloop_target(self, node): |
| 938 # TODO: Remove redundancy with range optimization... |
| 939 is_special = False |
| 940 sequence = node.iterator.sequence |
| 941 target = node.target |
| 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] |
| 952 if iterator.is_name: |
| 953 iterator_type = iterator.infer_type(self.env) |
| 954 if iterator_type.is_builtin_type: |
| 955 # assume that builtin types have a length wi
thin Py_ssize_t |
| 956 self.mark_assignment( |
| 957 target.args[0], |
| 958 ExprNodes.IntNode(target.pos, value='PY_
SSIZE_T_MAX', |
| 959 type=PyrexTypes.c_py_s
size_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'): |
| 968 is_special = True |
| 969 for arg in sequence.args[:2]: |
| 970 self.mark_assignment(target, arg) |
| 971 if len(sequence.args) > 2: |
| 972 self.mark_assignment( |
| 973 target, |
| 974 ExprNodes.binop_node(node.pos, |
| 975 '+', |
| 976 sequence.args[0], |
| 977 sequence.args[2])) |
| 978 |
| 979 if not is_special: |
| 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. |
| 985 |
| 986 self.mark_assignment(target, node.item) |
| 987 |
| 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) |
| 994 # Target assignment |
| 995 self.flow.nextblock() |
| 996 |
| 997 if isinstance(node, Nodes.ForInStatNode): |
| 998 self.mark_forloop_target(node) |
| 999 else: # Parallel |
| 1000 self.mark_assignment(node.target) |
| 1001 |
| 1002 # Body block |
| 1003 if isinstance(node, Nodes.ParallelRangeNode): |
| 1004 # In case of an invalid |
| 1005 self._delete_privates(node, exclude=node.target.entry) |
| 1006 |
| 1007 self.flow.nextblock() |
| 1008 self._visit(node.body) |
| 1009 self.flow.loops.pop() |
| 1010 |
| 1011 # Loop it |
| 1012 if self.flow.block: |
| 1013 self.flow.block.add_child(condition_block) |
| 1014 # Else clause |
| 1015 if node.else_clause: |
| 1016 self.flow.nextblock(parent=condition_block) |
| 1017 self._visit(node.else_clause) |
| 1018 if self.flow.block: |
| 1019 self.flow.block.add_child(next_block) |
| 1020 else: |
| 1021 condition_block.add_child(next_block) |
| 1022 |
| 1023 if next_block.parents: |
| 1024 self.flow.block = next_block |
| 1025 else: |
| 1026 self.flow.block = None |
| 1027 return node |
| 1028 |
| 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) |
| 1033 |
| 1034 def visit_ParallelRangeNode(self, node): |
| 1035 reductions = self.reductions |
| 1036 |
| 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) |
| 1041 |
| 1042 for private_node in node.assigned_nodes: |
| 1043 private_node.entry.error_on_uninitialized = True |
| 1044 pos, reduction = node.assignments[private_node.entry] |
| 1045 if reduction: |
| 1046 self.reductions.add(private_node.entry) |
| 1047 |
| 1048 node = self.visit_ForInStatNode(node) |
| 1049 |
| 1050 self.reductions = reductions |
| 1051 return node |
| 1052 |
| 1053 def visit_ParallelWithBlockNode(self, node): |
| 1054 for private_node in node.assigned_nodes: |
| 1055 private_node.entry.error_on_uninitialized = True |
| 1056 |
| 1057 self._delete_privates(node) |
| 1058 self.visitchildren(node) |
| 1059 self._delete_privates(node) |
| 1060 |
| 1061 return node |
| 1062 |
| 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) |
| 1072 # Target assignment |
| 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)) |
| 1079 # Body block |
| 1080 self.flow.nextblock() |
| 1081 self._visit(node.body) |
| 1082 self.flow.loops.pop() |
| 1083 # Loop it |
| 1084 if self.flow.block: |
| 1085 self.flow.block.add_child(condition_block) |
| 1086 # Else clause |
| 1087 if node.else_clause: |
| 1088 self.flow.nextblock(parent=condition_block) |
| 1089 self._visit(node.else_clause) |
| 1090 if self.flow.block: |
| 1091 self.flow.block.add_child(next_block) |
| 1092 else: |
| 1093 condition_block.add_child(next_block) |
| 1094 |
| 1095 if next_block.parents: |
| 1096 self.flow.block = next_block |
| 1097 else: |
| 1098 self.flow.block = None |
| 1099 return node |
| 1100 |
| 1101 def visit_LoopNode(self, node): |
| 1102 raise InternalError("Generic loops are not supported") |
| 1103 |
| 1104 def visit_WithTargetAssignmentStatNode(self, node): |
| 1105 self.mark_assignment(node.lhs, node.rhs) |
| 1106 return node |
| 1107 |
| 1108 def visit_WithStatNode(self, node): |
| 1109 self._visit(node.manager) |
| 1110 self._visit(node.enter_call) |
| 1111 self._visit(node.body) |
| 1112 return node |
| 1113 |
| 1114 def visit_TryExceptStatNode(self, node): |
| 1115 # After exception handling |
| 1116 next_block = self.flow.newblock() |
| 1117 # Body block |
| 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() |
| 1129 |
| 1130 # After exception |
| 1131 if self.flow.block: |
| 1132 if node.else_clause: |
| 1133 self.flow.nextblock() |
| 1134 self._visit(node.else_clause) |
| 1135 if self.flow.block: |
| 1136 self.flow.block.add_child(next_block) |
| 1137 |
| 1138 for clause in node.except_clauses: |
| 1139 self.flow.block = entry_point |
| 1140 if clause.pattern: |
| 1141 for pattern in clause.pattern: |
| 1142 self._visit(pattern) |
| 1143 else: |
| 1144 # TODO: handle * pattern |
| 1145 pass |
| 1146 entry_point = self.flow.newblock(parent=self.flow.block) |
| 1147 self.flow.nextblock() |
| 1148 if clause.target: |
| 1149 self.mark_assignment(clause.target) |
| 1150 self._visit(clause.body) |
| 1151 if self.flow.block: |
| 1152 self.flow.block.add_child(next_block) |
| 1153 |
| 1154 if self.flow.exceptions: |
| 1155 entry_point.add_child(self.flow.exceptions[-1].entry_point) |
| 1156 |
| 1157 if next_block.parents: |
| 1158 self.flow.block = next_block |
| 1159 else: |
| 1160 self.flow.block = None |
| 1161 return node |
| 1162 |
| 1163 def visit_TryFinallyStatNode(self, node): |
| 1164 body_block = self.flow.nextblock() |
| 1165 |
| 1166 # Exception entry point |
| 1167 entry_point = self.flow.newblock() |
| 1168 self.flow.block = entry_point |
| 1169 self._visit(node.finally_clause) |
| 1170 |
| 1171 if self.flow.block and self.flow.exceptions: |
| 1172 self.flow.block.add_child(self.flow.exceptions[-1].entry_point) |
| 1173 |
| 1174 # Normal execution |
| 1175 finally_enter = self.flow.newblock() |
| 1176 self.flow.block = finally_enter |
| 1177 self._visit(node.finally_clause) |
| 1178 finally_exit = self.flow.block |
| 1179 |
| 1180 descr = ExceptionDescr(entry_point, finally_enter, finally_exit) |
| 1181 self.flow.exceptions.append(descr) |
| 1182 if self.flow.loops: |
| 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() |
| 1190 if self.flow.loops: |
| 1191 self.flow.loops[-1].exceptions.pop() |
| 1192 |
| 1193 if self.flow.block: |
| 1194 self.flow.block.add_child(finally_enter) |
| 1195 if finally_exit: |
| 1196 self.flow.block = self.flow.nextblock(parent=finally_exit) |
| 1197 else: |
| 1198 self.flow.block = None |
| 1199 return node |
| 1200 |
| 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 |
| 1207 return node |
| 1208 |
| 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 |
| 1214 return node |
| 1215 |
| 1216 def visit_ReturnStatNode(self, node): |
| 1217 self.mark_position(node) |
| 1218 self.visitchildren(node) |
| 1219 |
| 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) |
| 1225 break |
| 1226 else: |
| 1227 if self.flow.block: |
| 1228 self.flow.block.add_child(self.flow.exit_point) |
| 1229 self.flow.block = None |
| 1230 return node |
| 1231 |
| 1232 def visit_BreakStatNode(self, node): |
| 1233 if not self.flow.loops: |
| 1234 #error(node.pos, "break statement not inside loop") |
| 1235 return node |
| 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) |
| 1243 break |
| 1244 else: |
| 1245 self.flow.block.add_child(loop.next_block) |
| 1246 self.flow.block = None |
| 1247 return node |
| 1248 |
| 1249 def visit_ContinueStatNode(self, node): |
| 1250 if not self.flow.loops: |
| 1251 #error(node.pos, "continue statement not inside loop") |
| 1252 return node |
| 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) |
| 1260 break |
| 1261 else: |
| 1262 self.flow.block.add_child(loop.loop_block) |
| 1263 self.flow.block = None |
| 1264 return node |
| 1265 |
| 1266 def visit_ComprehensionNode(self, node): |
| 1267 if node.expr_scope: |
| 1268 self.env_stack.append(self.env) |
| 1269 self.env = node.expr_scope |
| 1270 # Skip append node here |
| 1271 self._visit(node.loop) |
| 1272 if node.expr_scope: |
| 1273 self.env = self.env_stack.pop() |
| 1274 return node |
| 1275 |
| 1276 def visit_ScopedExprNode(self, node): |
| 1277 if node.expr_scope: |
| 1278 self.env_stack.append(self.env) |
| 1279 self.env = node.expr_scope |
| 1280 self.visitchildren(node) |
| 1281 if node.expr_scope: |
| 1282 self.env = self.env_stack.pop() |
| 1283 return node |
| 1284 |
| 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() |
| 1296 return node |
| 1297 |
| 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) |
| 1303 return node |
OLD | NEW |