88import collections
99import dis
1010from types import CodeType
11- from typing import Iterable , Optional
11+ from typing import Iterable , Mapping , Optional
1212
13- from coverage .types import TArc , TOffset
13+ from coverage .types import TArc , TLineNo , TOffset
1414
1515
1616def code_objects (code : CodeType ) -> Iterable [CodeType ]:
@@ -31,7 +31,9 @@ def op_set(*op_names: str) -> set[int]:
3131
3232 The names might not exist in this version of Python, skip those if not.
3333 """
34- return {op for name in op_names if (op := dis .opmap .get (name ))}
34+ ops = {op for name in op_names if (op := dis .opmap .get (name ))}
35+ assert ops , f"At least one opcode must exist: { op_names } "
36+ return ops
3537
3638
3739# Opcodes that are unconditional jumps elsewhere.
@@ -99,10 +101,16 @@ def walk(
99101TBranchTrails = dict [TOffset , TBranchTrailsOneSource ]
100102
101103
102- def branch_trails (code : CodeType ) -> TBranchTrails :
104+ def branch_trails (
105+ code : CodeType ,
106+ multiline_map : Mapping [TLineNo , TLineNo ],
107+ ) -> TBranchTrails :
103108 """
104109 Calculate branch trails for `code`.
105110
111+ `multiline_map` maps line numbers to the first line number of a
112+ multi-line statement.
113+
106114 Instructions can have a jump_target, where they might jump to next. Some
107115 instructions with a jump_target are unconditional jumps (ALWAYS_JUMPS), so
108116 they aren't interesting to us, since they aren't the start of a branch
@@ -128,6 +136,7 @@ def branch_trails(code: CodeType) -> TBranchTrails:
128136 from_line = inst .line_number
129137 if from_line is None :
130138 continue
139+ from_line = multiline_map .get (from_line , from_line )
131140
132141 def add_one_branch_trail (
133142 trails : TBranchTrailsOneSource ,
@@ -138,8 +147,11 @@ def add_one_branch_trail(
138147 to_line = None
139148 for inst2 in iwalker .walk (start_at = start_at , follow_jumps = True ):
140149 inst_offsets .add (inst2 .offset )
141- if inst2 .line_number and inst2 .line_number != from_line :
142- to_line = inst2 .line_number
150+ l2 = inst2 .line_number
151+ if l2 is not None :
152+ l2 = multiline_map .get (l2 , l2 )
153+ if l2 and l2 != from_line :
154+ to_line = l2
143155 break
144156 elif inst2 .jump_target and (inst2 .opcode not in ALWAYS_JUMPS ):
145157 break
0 commit comments