DebugClients/Python/coverage/parser.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
child 6649
f1b3a73831c9
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
13 from coverage import env 13 from coverage import env
14 from coverage.backward import range # pylint: disable=redefined-builtin 14 from coverage.backward import range # pylint: disable=redefined-builtin
15 from coverage.backward import bytes_to_ints, string_class 15 from coverage.backward import bytes_to_ints, string_class
16 from coverage.bytecode import CodeObjects 16 from coverage.bytecode import CodeObjects
17 from coverage.debug import short_stack 17 from coverage.debug import short_stack
18 from coverage.misc import contract, new_contract, nice_pair, join_regex 18 from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of
19 from coverage.misc import CoverageException, NoSource, NotPython 19 from coverage.misc import NoSource, NotPython, StopEverything
20 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration 20 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration
21 21
22 22
23 class PythonParser(object): 23 class PythonParser(object):
24 """Parse code to find executable lines, excluded lines, etc. 24 """Parse code to find executable lines, excluded lines, etc.
135 empty = True 135 empty = True
136 first_on_line = True 136 first_on_line = True
137 137
138 tokgen = generate_tokens(self.text) 138 tokgen = generate_tokens(self.text)
139 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: 139 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
140 if self.show_tokens: # pragma: not covered 140 if self.show_tokens: # pragma: debugging
141 print("%10s %5s %-20r %r" % ( 141 print("%10s %5s %-20r %r" % (
142 tokenize.tok_name.get(toktype, toktype), 142 tokenize.tok_name.get(toktype, toktype),
143 nice_pair((slineno, elineno)), ttext, ltext 143 nice_pair((slineno, elineno)), ttext, ltext
144 )) 144 ))
145 if toktype == token.INDENT: 145 if toktype == token.INDENT:
368 ) 368 )
369 ) 369 )
370 370
371 # Alternative Python implementations don't always provide all the 371 # Alternative Python implementations don't always provide all the
372 # attributes on code objects that we need to do the analysis. 372 # attributes on code objects that we need to do the analysis.
373 for attr in ['co_lnotab', 'co_firstlineno', 'co_consts']: 373 for attr in ['co_lnotab', 'co_firstlineno']:
374 if not hasattr(self.code, attr): 374 if not hasattr(self.code, attr):
375 raise CoverageException( 375 raise StopEverything( # pragma: only jython
376 "This implementation of Python doesn't support code analysis.\n" 376 "This implementation of Python doesn't support code analysis.\n"
377 "Run coverage.py under CPython for this command." 377 "Run coverage.py under another Python for this command."
378 ) 378 )
379 379
380 def child_parsers(self): 380 def child_parsers(self):
381 """Iterate over all the code objects nested within this one. 381 """Iterate over all the code objects nested within this one.
382 382
430 # AST analysis 430 # AST analysis
431 # 431 #
432 432
433 class LoopBlock(object): 433 class LoopBlock(object):
434 """A block on the block stack representing a `for` or `while` loop.""" 434 """A block on the block stack representing a `for` or `while` loop."""
435 @contract(start=int)
435 def __init__(self, start): 436 def __init__(self, start):
437 # The line number where the loop starts.
436 self.start = start 438 self.start = start
439 # A set of ArcStarts, the arcs from break statements exiting this loop.
437 self.break_exits = set() 440 self.break_exits = set()
438 441
439 442
440 class FunctionBlock(object): 443 class FunctionBlock(object):
441 """A block on the block stack representing a function definition.""" 444 """A block on the block stack representing a function definition."""
445 @contract(start=int, name=str)
442 def __init__(self, start, name): 446 def __init__(self, start, name):
447 # The line number where the function starts.
443 self.start = start 448 self.start = start
449 # The name of the function.
444 self.name = name 450 self.name = name
445 451
446 452
447 class TryBlock(object): 453 class TryBlock(object):
448 """A block on the block stack representing a `try` block.""" 454 """A block on the block stack representing a `try` block."""
449 def __init__(self, handler_start=None, final_start=None): 455 @contract(handler_start='int|None', final_start='int|None')
456 def __init__(self, handler_start, final_start):
457 # The line number of the first "except" handler, if any.
450 self.handler_start = handler_start 458 self.handler_start = handler_start
459 # The line number of the "finally:" clause, if any.
451 self.final_start = final_start 460 self.final_start = final_start
461
462 # The ArcStarts for breaks/continues/returns/raises inside the "try:"
463 # that need to route through the "finally:" clause.
452 self.break_from = set() 464 self.break_from = set()
453 self.continue_from = set() 465 self.continue_from = set()
454 self.return_from = set() 466 self.return_from = set()
455 self.raise_from = set() 467 self.raise_from = set()
456 468
457 469
458 class ArcStart(collections.namedtuple("Arc", "lineno, cause")): 470 class ArcStart(collections.namedtuple("Arc", "lineno, cause")):
459 """The information needed to start an arc. 471 """The information needed to start an arc.
460 472
461 `lineno` is the line number the arc starts from. `cause` is a fragment 473 `lineno` is the line number the arc starts from.
462 used as the startmsg for AstArcAnalyzer.missing_arc_fragments. 474
475 `cause` is an English text fragment used as the `startmsg` for
476 AstArcAnalyzer.missing_arc_fragments. It will be used to describe why an
477 arc wasn't executed, so should fit well into a sentence of the form,
478 "Line 17 didn't run because {cause}." The fragment can include "{lineno}"
479 to have `lineno` interpolated into it.
463 480
464 """ 481 """
465 def __new__(cls, lineno, cause=None): 482 def __new__(cls, lineno, cause=None):
466 return super(ArcStart, cls).__new__(cls, lineno, cause) 483 return super(ArcStart, cls).__new__(cls, lineno, cause)
467 484
468 485
469 # Define contract words that PyContract doesn't have. 486 # Define contract words that PyContract doesn't have.
470 # ArcStarts is for a list or set of ArcStart's. 487 # ArcStarts is for a list or set of ArcStart's.
471 new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq)) 488 new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq))
489
490
491 # Turn on AST dumps with an environment variable.
492 AST_DUMP = bool(int(os.environ.get("COVERAGE_AST_DUMP", 0)))
493
494 class NodeList(object):
495 """A synthetic fictitious node, containing a sequence of nodes.
496
497 This is used when collapsing optimized if-statements, to represent the
498 unconditional execution of one of the clauses.
499
500 """
501 def __init__(self, body):
502 self.body = body
503 self.lineno = body[0].lineno
472 504
473 505
474 class AstArcAnalyzer(object): 506 class AstArcAnalyzer(object):
475 """Analyze source text with an AST to find executable code paths.""" 507 """Analyze source text with an AST to find executable code paths."""
476 508
479 self.root_node = ast.parse(neuter_encoding_declaration(text)) 511 self.root_node = ast.parse(neuter_encoding_declaration(text))
480 # TODO: I think this is happening in too many places. 512 # TODO: I think this is happening in too many places.
481 self.statements = set(multiline.get(l, l) for l in statements) 513 self.statements = set(multiline.get(l, l) for l in statements)
482 self.multiline = multiline 514 self.multiline = multiline
483 515
484 if int(os.environ.get("COVERAGE_ASTDUMP", 0)): # pragma: debugging 516 if AST_DUMP: # pragma: debugging
485 # Dump the AST so that failing tests have helpful output. 517 # Dump the AST so that failing tests have helpful output.
486 print("Statements: {}".format(self.statements)) 518 print("Statements: {0}".format(self.statements))
487 print("Multiline map: {}".format(self.multiline)) 519 print("Multiline map: {0}".format(self.multiline))
488 ast_dump(self.root_node) 520 ast_dump(self.root_node)
489 521
490 self.arcs = set() 522 self.arcs = set()
491 523
492 # A map from arc pairs to a pair of sentence fragments: (startmsg, endmsg). 524 # A map from arc pairs to a list of pairs of sentence fragments:
525 # { (start, end): [(startmsg, endmsg), ...], }
526 #
493 # For an arc from line 17, they should be usable like: 527 # For an arc from line 17, they should be usable like:
494 # "Line 17 {endmsg}, because {startmsg}" 528 # "Line 17 {endmsg}, because {startmsg}"
495 self.missing_arc_fragments = collections.defaultdict(list) 529 self.missing_arc_fragments = collections.defaultdict(list)
496 self.block_stack = [] 530 self.block_stack = []
497 531
510 if code_object_handler is not None: 544 if code_object_handler is not None:
511 code_object_handler(node) 545 code_object_handler(node)
512 546
513 def add_arc(self, start, end, smsg=None, emsg=None): 547 def add_arc(self, start, end, smsg=None, emsg=None):
514 """Add an arc, including message fragments to use if it is missing.""" 548 """Add an arc, including message fragments to use if it is missing."""
515 if self.debug: 549 if self.debug: # pragma: debugging
516 print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg)) 550 print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg))
517 print(short_stack(limit=6)) 551 print(short_stack(limit=6))
518 self.arcs.add((start, end)) 552 self.arcs.add((start, end))
519 553
520 if smsg is not None or emsg is not None: 554 if smsg is not None or emsg is not None:
561 595
562 def _line__Module(self, node): 596 def _line__Module(self, node):
563 if node.body: 597 if node.body:
564 return self.line_for_node(node.body[0]) 598 return self.line_for_node(node.body[0])
565 else: 599 else:
566 # Modules have no line number, they always start at 1. 600 # Empty modules have no line number, they always start at 1.
567 return 1 601 return 1
568 602
603 # The node types that just flow to the next node with no complications.
569 OK_TO_DEFAULT = set([ 604 OK_TO_DEFAULT = set([
570 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", 605 "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global",
571 "Import", "ImportFrom", "Nonlocal", "Pass", "Print", 606 "Import", "ImportFrom", "Nonlocal", "Pass", "Print",
572 ]) 607 ])
573 608
574 @contract(returns='ArcStarts') 609 @contract(returns='ArcStarts')
575 def add_arcs(self, node): 610 def add_arcs(self, node):
576 """Add the arcs for `node`. 611 """Add the arcs for `node`.
577 612
578 Return a set of ArcStarts, exits from this node to the next. 613 Return a set of ArcStarts, exits from this node to the next. Because a
614 node represents an entire sub-tree (including its children), the exits
615 from a node can be arbitrarily complex::
616
617 if something(1):
618 if other(2):
619 doit(3)
620 else:
621 doit(5)
622
623 There are two exits from line 1: they start at line 3 and line 5.
579 624
580 """ 625 """
581 node_name = node.__class__.__name__ 626 node_name = node.__class__.__name__
582 handler = getattr(self, "_handle__" + node_name, None) 627 handler = getattr(self, "_handle__" + node_name, None)
583 if handler is not None: 628 if handler is not None:
584 return handler(node) 629 return handler(node)
585 630 else:
586 if 0: 631 # No handler: either it's something that's ok to default (a simple
587 node_name = node.__class__.__name__ 632 # statement), or it's something we overlooked. Change this 0 to 1
588 if node_name not in self.OK_TO_DEFAULT: 633 # to see if it's overlooked.
589 print("*** Unhandled: {0}".format(node)) 634 if 0:
590 return set([ArcStart(self.line_for_node(node), cause=None)]) 635 if node_name not in self.OK_TO_DEFAULT:
591 636 print("*** Unhandled: {0}".format(node))
637
638 # Default for simple statements: one exit from this node.
639 return set([ArcStart(self.line_for_node(node))])
640
641 @one_of("from_start, prev_starts")
592 @contract(returns='ArcStarts') 642 @contract(returns='ArcStarts')
593 def add_body_arcs(self, body, from_start=None, prev_starts=None): 643 def add_body_arcs(self, body, from_start=None, prev_starts=None):
594 """Add arcs for the body of a compound statement. 644 """Add arcs for the body of a compound statement.
595 645
596 `body` is the body node. `from_start` is a single `ArcStart` that can 646 `body` is the body node. `from_start` is a single `ArcStart` that can
605 prev_starts = set([from_start]) 655 prev_starts = set([from_start])
606 for body_node in body: 656 for body_node in body:
607 lineno = self.line_for_node(body_node) 657 lineno = self.line_for_node(body_node)
608 first_line = self.multiline.get(lineno, lineno) 658 first_line = self.multiline.get(lineno, lineno)
609 if first_line not in self.statements: 659 if first_line not in self.statements:
610 continue 660 body_node = self.find_non_missing_node(body_node)
661 if body_node is None:
662 continue
663 lineno = self.line_for_node(body_node)
611 for prev_start in prev_starts: 664 for prev_start in prev_starts:
612 self.add_arc(prev_start.lineno, lineno, prev_start.cause) 665 self.add_arc(prev_start.lineno, lineno, prev_start.cause)
613 prev_starts = self.add_arcs(body_node) 666 prev_starts = self.add_arcs(body_node)
614 return prev_starts 667 return prev_starts
615 668
669 def find_non_missing_node(self, node):
670 """Search `node` looking for a child that has not been optimized away.
671
672 This might return the node you started with, or it will work recursively
673 to find a child node in self.statements.
674
675 Returns a node, or None if none of the node remains.
676
677 """
678 # This repeats work just done in add_body_arcs, but this duplication
679 # means we can avoid a function call in the 99.9999% case of not
680 # optimizing away statements.
681 lineno = self.line_for_node(node)
682 first_line = self.multiline.get(lineno, lineno)
683 if first_line in self.statements:
684 return node
685
686 missing_fn = getattr(self, "_missing__" + node.__class__.__name__, None)
687 if missing_fn:
688 node = missing_fn(node)
689 else:
690 node = None
691 return node
692
693 def _missing__If(self, node):
694 # If the if-node is missing, then one of its children might still be
695 # here, but not both. So return the first of the two that isn't missing.
696 # Use a NodeList to hold the clauses as a single node.
697 non_missing = self.find_non_missing_node(NodeList(node.body))
698 if non_missing:
699 return non_missing
700 if node.orelse:
701 return self.find_non_missing_node(NodeList(node.orelse))
702 return None
703
704 def _missing__NodeList(self, node):
705 # A NodeList might be a mixture of missing and present nodes. Find the
706 # ones that are present.
707 non_missing_children = []
708 for child in node.body:
709 child = self.find_non_missing_node(child)
710 if child is not None:
711 non_missing_children.append(child)
712
713 # Return the simplest representation of the present children.
714 if not non_missing_children:
715 return None
716 if len(non_missing_children) == 1:
717 return non_missing_children[0]
718 return NodeList(non_missing_children)
719
616 def is_constant_expr(self, node): 720 def is_constant_expr(self, node):
617 """Is this a compile-time constant?""" 721 """Is this a compile-time constant?"""
618 node_name = node.__class__.__name__ 722 node_name = node.__class__.__name__
619 if node_name in ["NameConstant", "Num"]: 723 if node_name in ["NameConstant", "Num"]:
620 return True 724 return "Num"
621 elif node_name == "Name": 725 elif node_name == "Name":
622 if env.PY3 and node.id in ["True", "False", "None"]: 726 if node.id in ["True", "False", "None", "__debug__"]:
623 return True 727 return "Name"
624 return False 728 return None
625 729
626 # tests to write: 730 # In the fullness of time, these might be good tests to write:
627 # TODO: while EXPR: 731 # while EXPR:
628 # TODO: while False: 732 # while False:
629 # TODO: listcomps hidden deep in other expressions 733 # listcomps hidden deep in other expressions
630 # TODO: listcomps hidden in lists: x = [[i for i in range(10)]] 734 # listcomps hidden in lists: x = [[i for i in range(10)]]
631 # TODO: nested function definitions 735 # nested function definitions
736
737
738 # Exit processing: process_*_exits
739 #
740 # These functions process the four kinds of jump exits: break, continue,
741 # raise, and return. To figure out where an exit goes, we have to look at
742 # the block stack context. For example, a break will jump to the nearest
743 # enclosing loop block, or the nearest enclosing finally block, whichever
744 # is nearer.
632 745
633 @contract(exits='ArcStarts') 746 @contract(exits='ArcStarts')
634 def process_break_exits(self, exits): 747 def process_break_exits(self, exits):
635 """Add arcs due to jumps from `exits` being breaks.""" 748 """Add arcs due to jumps from `exits` being breaks."""
636 for block in self.nearest_blocks(): 749 for block in self.nearest_blocks():
686 xit.lineno, -block.start, xit.cause, 799 xit.lineno, -block.start, xit.cause,
687 "didn't return from function '{0}'".format(block.name), 800 "didn't return from function '{0}'".format(block.name),
688 ) 801 )
689 break 802 break
690 803
691 ## Handlers 804
805 # Handlers: _handle__*
806 #
807 # Each handler deals with a specific AST node type, dispatched from
808 # add_arcs. Each deals with a particular kind of node type, and returns
809 # the set of exits from that node. These functions mirror the Python
810 # semantics of each syntactic construct. See the docstring for add_arcs to
811 # understand the concept of exits from a node.
692 812
693 @contract(returns='ArcStarts') 813 @contract(returns='ArcStarts')
694 def _handle__Break(self, node): 814 def _handle__Break(self, node):
695 here = self.line_for_node(node) 815 here = self.line_for_node(node)
696 break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed") 816 break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed")
709 last = dec_start 829 last = dec_start
710 # The definition line may have been missed, but we should have it 830 # The definition line may have been missed, but we should have it
711 # in `self.statements`. For some constructs, `line_for_node` is 831 # in `self.statements`. For some constructs, `line_for_node` is
712 # not what we'd think of as the first line in the statement, so map 832 # not what we'd think of as the first line in the statement, so map
713 # it to the first one. 833 # it to the first one.
714 body_start = self.line_for_node(node.body[0]) 834 if node.body:
715 body_start = self.multiline.get(body_start, body_start) 835 body_start = self.line_for_node(node.body[0])
716 for lineno in range(last+1, body_start): 836 body_start = self.multiline.get(body_start, body_start)
717 if lineno in self.statements: 837 for lineno in range(last+1, body_start):
718 self.add_arc(last, lineno) 838 if lineno in self.statements:
719 last = lineno 839 self.add_arc(last, lineno)
840 last = lineno
720 # The body is handled in collect_arcs. 841 # The body is handled in collect_arcs.
721 return set([ArcStart(last, cause=None)]) 842 return set([ArcStart(last)])
722 843
723 _handle__ClassDef = _handle_decorated 844 _handle__ClassDef = _handle_decorated
724 845
725 @contract(returns='ArcStarts') 846 @contract(returns='ArcStarts')
726 def _handle__Continue(self, node): 847 def _handle__Continue(self, node):
743 from_start = ArcStart(start, cause="the loop on line {lineno} didn't complete") 864 from_start = ArcStart(start, cause="the loop on line {lineno} didn't complete")
744 if node.orelse: 865 if node.orelse:
745 else_exits = self.add_body_arcs(node.orelse, from_start=from_start) 866 else_exits = self.add_body_arcs(node.orelse, from_start=from_start)
746 exits |= else_exits 867 exits |= else_exits
747 else: 868 else:
748 # no else clause: exit from the for line. 869 # No else clause: exit from the for line.
749 exits.add(from_start) 870 exits.add(from_start)
750 return exits 871 return exits
751 872
752 _handle__AsyncFor = _handle__For 873 _handle__AsyncFor = _handle__For
753 874
762 from_start = ArcStart(start, cause="the condition on line {lineno} was never false") 883 from_start = ArcStart(start, cause="the condition on line {lineno} was never false")
763 exits |= self.add_body_arcs(node.orelse, from_start=from_start) 884 exits |= self.add_body_arcs(node.orelse, from_start=from_start)
764 return exits 885 return exits
765 886
766 @contract(returns='ArcStarts') 887 @contract(returns='ArcStarts')
888 def _handle__NodeList(self, node):
889 start = self.line_for_node(node)
890 exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
891 return exits
892
893 @contract(returns='ArcStarts')
767 def _handle__Raise(self, node): 894 def _handle__Raise(self, node):
768 here = self.line_for_node(node) 895 here = self.line_for_node(node)
769 raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed") 896 raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed")
770 self.process_raise_exits([raise_start]) 897 self.process_raise_exits([raise_start])
771 # `raise` statement jumps away, no exits from here. 898 # `raise` statement jumps away, no exits from here.
789 if node.finalbody: 916 if node.finalbody:
790 final_start = self.line_for_node(node.finalbody[0]) 917 final_start = self.line_for_node(node.finalbody[0])
791 else: 918 else:
792 final_start = None 919 final_start = None
793 920
794 try_block = TryBlock(handler_start=handler_start, final_start=final_start) 921 try_block = TryBlock(handler_start, final_start)
795 self.block_stack.append(try_block) 922 self.block_stack.append(try_block)
796 923
797 start = self.line_for_node(node) 924 start = self.line_for_node(node)
798 exits = self.add_body_arcs(node.body, from_start=ArcStart(start, cause=None)) 925 exits = self.add_body_arcs(node.body, from_start=ArcStart(start))
799 926
800 # We're done with the `try` body, so this block no longer handles 927 # We're done with the `try` body, so this block no longer handles
801 # exceptions. We keep the block so the `finally` clause can pick up 928 # exceptions. We keep the block so the `finally` clause can pick up
802 # flows from the handlers and `else` clause. 929 # flows from the handlers and `else` clause.
803 if node.finalbody: 930 if node.finalbody:
836 try_block.continue_from | # or a `continue`, 963 try_block.continue_from | # or a `continue`,
837 try_block.raise_from | # or a `raise`, 964 try_block.raise_from | # or a `raise`,
838 try_block.return_from # or a `return`. 965 try_block.return_from # or a `return`.
839 ) 966 )
840 967
841 exits = self.add_body_arcs(node.finalbody, prev_starts=final_from) 968 final_exits = self.add_body_arcs(node.finalbody, prev_starts=final_from)
969
842 if try_block.break_from: 970 if try_block.break_from:
843 break_exits = self._combine_finally_starts(try_block.break_from, exits) 971 self.process_break_exits(
844 self.process_break_exits(break_exits) 972 self._combine_finally_starts(try_block.break_from, final_exits)
973 )
845 if try_block.continue_from: 974 if try_block.continue_from:
846 continue_exits = self._combine_finally_starts(try_block.continue_from, exits) 975 self.process_continue_exits(
847 self.process_continue_exits(continue_exits) 976 self._combine_finally_starts(try_block.continue_from, final_exits)
977 )
848 if try_block.raise_from: 978 if try_block.raise_from:
849 raise_exits = self._combine_finally_starts(try_block.raise_from, exits) 979 self.process_raise_exits(
850 self.process_raise_exits(raise_exits) 980 self._combine_finally_starts(try_block.raise_from, final_exits)
981 )
851 if try_block.return_from: 982 if try_block.return_from:
852 return_exits = self._combine_finally_starts(try_block.return_from, exits) 983 self.process_return_exits(
853 self.process_return_exits(return_exits) 984 self._combine_finally_starts(try_block.return_from, final_exits)
985 )
986
987 if exits:
988 # The finally clause's exits are only exits for the try block
989 # as a whole if the try block had some exits to begin with.
990 exits = final_exits
854 991
855 return exits 992 return exits
856 993
994 @contract(starts='ArcStarts', exits='ArcStarts', returns='ArcStarts')
857 def _combine_finally_starts(self, starts, exits): 995 def _combine_finally_starts(self, starts, exits):
858 """Helper for building the cause of `finally` branches.""" 996 """Helper for building the cause of `finally` branches.
997
998 "finally" clauses might not execute their exits, and the causes could
999 be due to a failure to execute any of the exits in the try block. So
1000 we use the causes from `starts` as the causes for `exits`.
1001 """
859 causes = [] 1002 causes = []
860 for lineno, cause in sorted(starts): 1003 for start in sorted(starts):
861 if cause is not None: 1004 if start.cause is not None:
862 causes.append(cause.format(lineno=lineno)) 1005 causes.append(start.cause.format(lineno=start.lineno))
863 cause = " or ".join(causes) 1006 cause = " or ".join(causes)
864 exits = set(ArcStart(ex.lineno, cause) for ex in exits) 1007 exits = set(ArcStart(xit.lineno, cause) for xit in exits)
865 return exits 1008 return exits
866 1009
867 @contract(returns='ArcStarts') 1010 @contract(returns='ArcStarts')
868 def _handle__TryExcept(self, node): 1011 def _handle__TryExcept(self, node):
869 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get 1012 # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get
891 1034
892 @contract(returns='ArcStarts') 1035 @contract(returns='ArcStarts')
893 def _handle__While(self, node): 1036 def _handle__While(self, node):
894 constant_test = self.is_constant_expr(node.test) 1037 constant_test = self.is_constant_expr(node.test)
895 start = to_top = self.line_for_node(node.test) 1038 start = to_top = self.line_for_node(node.test)
896 if constant_test: 1039 if constant_test and (env.PY3 or constant_test == "Num"):
897 to_top = self.line_for_node(node.body[0]) 1040 to_top = self.line_for_node(node.body[0])
898 self.block_stack.append(LoopBlock(start=start)) 1041 self.block_stack.append(LoopBlock(start=to_top))
899 from_start = ArcStart(start, cause="the condition on line {lineno} was never true") 1042 from_start = ArcStart(start, cause="the condition on line {lineno} was never true")
900 exits = self.add_body_arcs(node.body, from_start=from_start) 1043 exits = self.add_body_arcs(node.body, from_start=from_start)
901 for xit in exits: 1044 for xit in exits:
902 self.add_arc(xit.lineno, to_top, xit.cause) 1045 self.add_arc(xit.lineno, to_top, xit.cause)
903 exits = set() 1046 exits = set()
968 _code_object__SetComp = _make_oneline_code_method("set comprehension") 1111 _code_object__SetComp = _make_oneline_code_method("set comprehension")
969 if env.PY3: 1112 if env.PY3:
970 _code_object__ListComp = _make_oneline_code_method("list comprehension") 1113 _code_object__ListComp = _make_oneline_code_method("list comprehension")
971 1114
972 1115
973 SKIP_DUMP_FIELDS = ["ctx"] 1116 if AST_DUMP: # pragma: debugging
974 1117 # Code only used when dumping the AST for debugging.
975 def _is_simple_value(value): 1118
976 """Is `value` simple enough to be displayed on a single line?""" 1119 SKIP_DUMP_FIELDS = ["ctx"]
977 return ( 1120
978 value in [None, [], (), {}, set()] or 1121 def _is_simple_value(value):
979 isinstance(value, (string_class, int, float)) 1122 """Is `value` simple enough to be displayed on a single line?"""
980 ) 1123 return (
981 1124 value in [None, [], (), {}, set()] or
982 # TODO: a test of ast_dump? 1125 isinstance(value, (string_class, int, float))
983 def ast_dump(node, depth=0): 1126 )
984 """Dump the AST for `node`. 1127
985 1128 def ast_dump(node, depth=0):
986 This recursively walks the AST, printing a readable version. 1129 """Dump the AST for `node`.
987 1130
988 """ 1131 This recursively walks the AST, printing a readable version.
989 indent = " " * depth 1132
990 if not isinstance(node, ast.AST): 1133 """
991 print("{0}<{1} {2!r}>".format(indent, node.__class__.__name__, node)) 1134 indent = " " * depth
992 return 1135 if not isinstance(node, ast.AST):
993 1136 print("{0}<{1} {2!r}>".format(indent, node.__class__.__name__, node))
994 lineno = getattr(node, "lineno", None) 1137 return
995 if lineno is not None: 1138
996 linemark = " @ {0}".format(node.lineno) 1139 lineno = getattr(node, "lineno", None)
997 else: 1140 if lineno is not None:
998 linemark = "" 1141 linemark = " @ {0}".format(node.lineno)
999 head = "{0}<{1}{2}".format(indent, node.__class__.__name__, linemark) 1142 else:
1000 1143 linemark = ""
1001 named_fields = [ 1144 head = "{0}<{1}{2}".format(indent, node.__class__.__name__, linemark)
1002 (name, value) 1145
1003 for name, value in ast.iter_fields(node) 1146 named_fields = [
1004 if name not in SKIP_DUMP_FIELDS 1147 (name, value)
1005 ] 1148 for name, value in ast.iter_fields(node)
1006 if not named_fields: 1149 if name not in SKIP_DUMP_FIELDS
1007 print("{0}>".format(head)) 1150 ]
1008 elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]): 1151 if not named_fields:
1009 field_name, value = named_fields[0] 1152 print("{0}>".format(head))
1010 print("{0} {1}: {2!r}>".format(head, field_name, value)) 1153 elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]):
1011 else: 1154 field_name, value = named_fields[0]
1012 print(head) 1155 print("{0} {1}: {2!r}>".format(head, field_name, value))
1013 if 0: 1156 else:
1014 print("{0}# mro: {1}".format( 1157 print(head)
1015 indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]), 1158 if 0:
1016 )) 1159 print("{0}# mro: {1}".format(
1017 next_indent = indent + " " 1160 indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]),
1018 for field_name, value in named_fields: 1161 ))
1019 prefix = "{0}{1}:".format(next_indent, field_name) 1162 next_indent = indent + " "
1020 if _is_simple_value(value): 1163 for field_name, value in named_fields:
1021 print("{0} {1!r}".format(prefix, value)) 1164 prefix = "{0}{1}:".format(next_indent, field_name)
1022 elif isinstance(value, list): 1165 if _is_simple_value(value):
1023 print("{0} [".format(prefix)) 1166 print("{0} {1!r}".format(prefix, value))
1024 for n in value: 1167 elif isinstance(value, list):
1025 ast_dump(n, depth + 8) 1168 print("{0} [".format(prefix))
1026 print("{0}]".format(next_indent)) 1169 for n in value:
1027 else: 1170 ast_dump(n, depth + 8)
1028 print(prefix) 1171 print("{0}]".format(next_indent))
1029 ast_dump(value, depth + 8) 1172 else:
1030 1173 print(prefix)
1031 print("{0}>".format(indent)) 1174 ast_dump(value, depth + 8)
1175
1176 print("{0}>".format(indent))

eric ide

mercurial