--- a/eric7/DebugClients/Python/coverage/parser.py Fri Aug 20 19:56:17 2021 +0200 +++ b/eric7/DebugClients/Python/coverage/parser.py Sat Aug 21 14:21:44 2021 +0200 @@ -205,6 +205,12 @@ if not empty: self.raw_statements.update(self.byte_parser._find_statements()) + # The first line of modules can lie and say 1 always, even if the first + # line of code is later. If so, map 1 to the actual first line of the + # module. + if env.PYBEHAVIOR.module_firstline_1 and self._multiline: + self._multiline[1] = min(self.raw_statements) + def first_line(self, line): """Return the first line number of the statement including `line`.""" if line < 0: @@ -220,7 +226,7 @@ Returns a set of the first lines. """ - return set(self.first_line(l) for l in lines) + return {self.first_line(l) for l in lines} def translate_lines(self, lines): """Implement `FileReporter.translate_lines`.""" @@ -332,9 +338,7 @@ fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)]) msgs = [] - for fragment_pair in fragment_pairs: - smsg, emsg = fragment_pair - + for smsg, emsg in fragment_pairs: if emsg is None: if end < 0: # Hmm, maybe we have a one-line callable, let's check. @@ -389,34 +393,35 @@ """ return (ByteParser(self.text, code=c) for c in code_objects(self.code)) - def _bytes_lines(self): - """Map byte offsets to line numbers in `code`. - - Uses co_lnotab described in Python/compile.c to map byte offsets to - line numbers. Produces a sequence: (b0, l0), (b1, l1), ... + def _line_numbers(self): + """Yield the line numbers possible in this code object. - Only byte offsets that correspond to line numbers are included in the - results. - + Uses co_lnotab described in Python/compile.c to find the + line numbers. Produces a sequence: l0, l1, ... """ - # Adapted from dis.py in the standard library. - byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) - line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) + if hasattr(self.code, "co_lines"): + for _, _, line in self.code.co_lines(): + if line is not None: + yield line + else: + # Adapted from dis.py in the standard library. + byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) + line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) - last_line_num = None - line_num = self.code.co_firstlineno - byte_num = 0 - for byte_incr, line_incr in zip(byte_increments, line_increments): - if byte_incr: - if line_num != last_line_num: - yield (byte_num, line_num) - last_line_num = line_num - byte_num += byte_incr - if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: - line_incr -= 0x100 - line_num += line_incr - if line_num != last_line_num: - yield (byte_num, line_num) + last_line_num = None + line_num = self.code.co_firstlineno + byte_num = 0 + for byte_incr, line_incr in zip(byte_increments, line_increments): + if byte_incr: + if line_num != last_line_num: + yield line_num + last_line_num = line_num + byte_num += byte_incr + if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80: + line_incr -= 0x100 + line_num += line_incr + if line_num != last_line_num: + yield line_num def _find_statements(self): """Find the statements in `self.code`. @@ -427,7 +432,7 @@ """ for bp in self.child_parsers(): # Get all of the lineno information from this code. - for _, l in bp._bytes_lines(): + for l in bp._line_numbers(): yield l @@ -520,7 +525,7 @@ def __init__(self, text, statements, multiline): self.root_node = ast.parse(neuter_encoding_declaration(text)) # TODO: I think this is happening in too many places. - self.statements = set(multiline.get(l, l) for l in statements) + self.statements = {multiline.get(l, l) for l in statements} self.multiline = multiline if AST_DUMP: # pragma: debugging @@ -619,17 +624,19 @@ return node.lineno def _line__Module(self, node): - if node.body: + if env.PYBEHAVIOR.module_firstline_1: + return 1 + elif node.body: return self.line_for_node(node.body[0]) else: # Empty modules have no line number, they always start at 1. return 1 # The node types that just flow to the next node with no complications. - OK_TO_DEFAULT = set([ + OK_TO_DEFAULT = { "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", "Import", "ImportFrom", "Nonlocal", "Pass", "Print", - ]) + } @contract(returns='ArcStarts') def add_arcs(self, node): @@ -661,7 +668,7 @@ print("*** Unhandled: {}".format(node)) # Default for simple statements: one exit from this node. - return set([ArcStart(self.line_for_node(node))]) + return {ArcStart(self.line_for_node(node))} @one_of("from_start, prev_starts") @contract(returns='ArcStarts') @@ -677,7 +684,7 @@ """ if prev_starts is None: - prev_starts = set([from_start]) + prev_starts = {from_start} for body_node in body: lineno = self.line_for_node(body_node) first_line = self.multiline.get(lineno, lineno) @@ -890,7 +897,7 @@ self.add_arc(last, lineno) last = lineno # The body is handled in collect_arcs. - return set([ArcStart(last)]) + return {ArcStart(last)} _handle__ClassDef = _handle_decorated @@ -984,7 +991,7 @@ # If there are `except` clauses, then raises in the try body # will already jump to them. Start this set over for raises in # `except` and `else`. - try_block.raise_from = set([]) + try_block.raise_from = set() else: self.block_stack.pop() @@ -1079,7 +1086,7 @@ if start.cause is not None: causes.append(start.cause.format(lineno=start.lineno)) cause = " or ".join(causes) - exits = set(ArcStart(xit.lineno, cause) for xit in exits) + exits = {ArcStart(xit.lineno, cause) for xit in exits} return exits @contract(returns='ArcStarts') @@ -1109,9 +1116,14 @@ @contract(returns='ArcStarts') def _handle__While(self, node): + start = to_top = self.line_for_node(node.test) constant_test = self.is_constant_expr(node.test) - start = to_top = self.line_for_node(node.test) + top_is_body0 = False if constant_test and (env.PY3 or constant_test == "Num"): + top_is_body0 = True + if env.PYBEHAVIOR.keep_constant_test: + top_is_body0 = False + if top_is_body0: to_top = self.line_for_node(node.body[0]) self.block_stack.append(LoopBlock(start=to_top)) from_start = ArcStart(start, cause="the condition on line {lineno} was never true")