Skip to content

Commit 6f29d09

Browse files
committed
impl sys.monitoring
1 parent d949871 commit 6f29d09

File tree

10 files changed

+1344
-151
lines changed

10 files changed

+1344
-151
lines changed

Lib/test/test_compile.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,8 +1012,6 @@ def return_genexp():
10121012
code_lines = self.get_code_lines(genexp_code)
10131013
self.assertEqual(genexp_lines, code_lines)
10141014

1015-
# TODO: RUSTPYTHON; implicit return line number after async for
1016-
@unittest.expectedFailure
10171015
def test_line_number_implicit_return_after_async_for(self):
10181016

10191017
async def test(aseq):

crates/codegen/src/compile.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,6 +2865,9 @@ impl Compiler {
28652865
// Normal path jumps here to skip exception path blocks
28662866
let end_block = self.new_block();
28672867

2868+
// Emit NOP at the try: line so LINE events fire for it
2869+
emit!(self, Instruction::Nop);
2870+
28682871
// Setup a finally block if we have a finally statement.
28692872
// Push fblock with handler info for exception table generation
28702873
// IMPORTANT: handler goes to finally_except_block (exception path), not finally_block
@@ -3018,8 +3021,10 @@ impl Compiler {
30183021
type_,
30193022
name,
30203023
body,
3024+
range: handler_range,
30213025
..
30223026
}) = &handler;
3027+
self.set_source_range(*handler_range);
30233028
let next_handler = self.new_block();
30243029

30253030
// If we gave a typ,
@@ -3313,6 +3318,9 @@ impl Compiler {
33133318
};
33143319
let exit_block = self.new_block();
33153320

3321+
// Emit NOP at the try: line so LINE events fire for it
3322+
emit!(self, Instruction::Nop);
3323+
33163324
// Push fblock with handler info for exception table generation
33173325
if !finalbody.is_empty() {
33183326
emit!(
@@ -3743,6 +3751,9 @@ impl Compiler {
37433751
is_async: bool,
37443752
funcflags: bytecode::MakeFunctionFlags,
37453753
) -> CompileResult<()> {
3754+
// Save source range so MAKE_FUNCTION gets the `def` line, not the body's last line
3755+
let saved_range = self.current_source_range;
3756+
37463757
// Always enter function scope
37473758
self.enter_function(name, parameters)?;
37483759
self.current_code_info()
@@ -3796,6 +3807,9 @@ impl Compiler {
37963807
let code = self.exit_scope();
37973808
self.ctx = prev_ctx;
37983809

3810+
// Restore source range so MAKE_FUNCTION is attributed to the `def` line
3811+
self.set_source_range(saved_range);
3812+
37993813
// Create function object with closure
38003814
self.make_closure(code, funcflags)?;
38013815

@@ -5169,15 +5183,22 @@ impl Compiler {
51695183
if is_async {
51705184
emit!(self, Instruction::EndAsyncFor);
51715185
} else {
5172-
// END_FOR + POP_ITER pattern (CPython 3.14)
5173-
// FOR_ITER jumps to END_FOR, but VM skips it (+1) to reach POP_ITER
5186+
// END_FOR + POP_ITER are on the `for` line, not the body's last line
5187+
let saved_range = self.current_source_range;
5188+
self.set_source_range(iter.range());
51745189
emit!(self, Instruction::EndFor);
51755190
emit!(self, Instruction::PopIter);
5191+
self.set_source_range(saved_range);
51765192
}
51775193
self.compile_statements(orelse)?;
51785194

51795195
self.switch_to_block(after_block);
51805196

5197+
// Restore source range to the `for` line so any implicit return
5198+
// (LOAD_CONST None, RETURN_VALUE) is attributed to the `for` line,
5199+
// not the loop body's last line.
5200+
self.set_source_range(iter.range());
5201+
51815202
self.leave_conditional_block();
51825203
Ok(())
51835204
}

crates/codegen/src/ir.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,33 @@ impl CodeInfo {
244244
let mut locations = Vec::new();
245245
let mut linetable_locations: Vec<LineTableLocation> = Vec::new();
246246

247-
// Convert pseudo ops and remove resulting NOPs
247+
// Convert pseudo ops and remove resulting NOPs (keep line-marker NOPs)
248248
convert_pseudo_ops(&mut blocks, varname_cache.len() as u32);
249249
for block in blocks
250250
.iter_mut()
251251
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
252252
{
253-
block
253+
// Collect lines that have non-NOP instructions in this block
254+
let non_nop_lines: std::collections::HashSet<_> = block
254255
.instructions
255-
.retain(|ins| !matches!(ins.instr.real(), Some(Instruction::Nop)));
256+
.iter()
257+
.filter(|ins| !matches!(ins.instr.real(), Some(Instruction::Nop)))
258+
.map(|ins| ins.location.line)
259+
.collect();
260+
let mut kept_nop_lines: std::collections::HashSet<OneIndexed> =
261+
std::collections::HashSet::new();
262+
block.instructions.retain(|ins| {
263+
if matches!(ins.instr.real(), Some(Instruction::Nop)) {
264+
let line = ins.location.line;
265+
// Remove if another instruction covers this line,
266+
// or if we already kept a NOP for this line
267+
if non_nop_lines.contains(&line) || kept_nop_lines.contains(&line) {
268+
return false;
269+
}
270+
kept_nop_lines.insert(line);
271+
}
272+
true
273+
});
256274
}
257275

258276
let mut block_to_offset = vec![Label(0); blocks.len()];
@@ -495,9 +513,14 @@ impl CodeInfo {
495513
let tuple_const = ConstantData::Tuple { elements };
496514
let (const_idx, _) = self.metadata.consts.insert_full(tuple_const);
497515

498-
// Replace preceding LOAD instructions with NOP
516+
// Replace preceding LOAD instructions with NOP, using the
517+
// BUILD_TUPLE location so remove_nops() treats them as
518+
// same-line and removes them (multi-line tuple literals
519+
// would otherwise leave line-introducing NOPs behind).
520+
let folded_loc = block.instructions[i].location;
499521
for j in start_idx..i {
500522
block.instructions[j].instr = Instruction::Nop.into();
523+
block.instructions[j].location = folded_loc;
501524
}
502525

503526
// Replace BUILD_TUPLE with LOAD_CONST
@@ -667,12 +690,23 @@ impl CodeInfo {
667690
}
668691
}
669692

670-
/// Remove NOP instructions from all blocks
693+
/// Remove NOP instructions from all blocks, but keep NOPs that introduce
694+
/// a new source line (they serve as line markers for monitoring LINE events).
671695
fn remove_nops(&mut self) {
672696
for block in &mut self.blocks {
673-
block
674-
.instructions
675-
.retain(|ins| !matches!(ins.instr.real(), Some(Instruction::Nop)));
697+
let mut prev_line = None;
698+
block.instructions.retain(|ins| {
699+
if matches!(ins.instr.real(), Some(Instruction::Nop)) {
700+
let line = ins.location.line;
701+
if prev_line == Some(line) {
702+
// Same line as previous instruction — safe to remove
703+
return false;
704+
}
705+
// This NOP introduces a new line — keep it
706+
}
707+
prev_line = Some(ins.location.line);
708+
true
709+
});
676710
}
677711
}
678712

0 commit comments

Comments
 (0)