From ddecd7f71f13f40a0d26164322c8d46b3892e16a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Tue, 19 May 2026 20:47:44 +0900 Subject: [PATCH 1/2] Resolve test_inspect bytecode parity gaps --- Lib/test/test_inspect/test_inspect.py | 1 - crates/codegen/src/compile.rs | 146 ++++++++++++++++++-------- 2 files changed, 101 insertions(+), 46 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index d479f21f558..512adba2813 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -888,7 +888,6 @@ def test_getsource_on_generated_class(self): self.assertRaises(OSError, inspect.getsourcelines, A) self.assertIsNone(inspect.getcomments(A)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by getsource def test_getsource_on_class_without_firstlineno(self): __firstlineno__ = 1 class C: diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index f975565cbdc..b8405777d52 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2723,7 +2723,7 @@ impl Compiler { let mut parent_idx = stack_size - 2; let mut parent = &self.code_stack[parent_idx]; - let parent_scope = self + let mut parent_scope = self .symbol_table_stack .get(parent_idx) .map(|table| table.typ); @@ -2742,6 +2742,10 @@ impl Compiler { // Use grandparent parent_idx = stack_size - 3; parent = &self.code_stack[parent_idx]; + parent_scope = self + .symbol_table_stack + .get(parent_idx) + .map(|table| table.typ); } // Check if this is a global class/function @@ -2779,9 +2783,12 @@ impl Compiler { let parent_obj_name = &parent.metadata.name; // Determine if parent is a function-like scope - let is_function_parent = parent.flags.contains(bytecode::CodeFlags::OPTIMIZED) - && !parent_obj_name.starts_with("<") // Not a special scope like , , etc. - && parent_obj_name != ""; // Not the module scope + let is_function_parent = matches!( + parent_scope, + Some( + CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda + ) + ); if is_function_parent { // For functions, append . to parent qualname @@ -3251,6 +3258,8 @@ impl Compiler { CompilerScope::Annotation | CompilerScope::TypeParams ) { SymbolScope::GlobalImplicit + } else if name.starts_with('_') { + SymbolScope::Unknown } else { return Err(self.error(CodegenErrorType::SyntaxError(format!( "the symbol '{name}' must be present in the symbol table" @@ -6613,39 +6622,20 @@ impl Compiler { let (doc_str, body) = split_doc(body, &self.opts); // Load __name__ and store as __module__ - let dunder_name = self.name("__name__"); - emit!(self, Instruction::LoadName { namei: dunder_name }); - let dunder_module = self.name("__module__"); - emit!( - self, - Instruction::StoreName { - namei: dunder_module - } - ); + self.load_name("__name__")?; + self.store_name("__module__")?; // Store __qualname__ self.emit_load_const(ConstantData::Str { value: qualname.into(), }); - let qualname_name = self.name("__qualname__"); - emit!( - self, - Instruction::StoreName { - namei: qualname_name - } - ); + self.store_name("__qualname__")?; // Store __firstlineno__ before __doc__ self.emit_load_const(ConstantData::Integer { value: BigInt::from(firstlineno), }); - let firstlineno_name = self.name("__firstlineno__"); - emit!( - self, - Instruction::StoreName { - namei: firstlineno_name - } - ); + self.store_name("__firstlineno__")?; // Set __type_params__ from the enclosing type-params closure when // compiling a generic class body. @@ -6677,8 +6667,7 @@ impl Compiler { // Store __doc__ only if there's an explicit docstring. if let Some(doc) = doc_str { self.emit_load_const(ConstantData::Str { value: doc.into() }); - let doc_name = self.name("__doc__"); - emit!(self, Instruction::StoreName { namei: doc_name }); + self.store_name("__doc__")?; } // 3. Compile the class body @@ -6716,13 +6705,7 @@ impl Compiler { .collect(), }); self.set_no_location(); - let static_attrs_name = self.name("__static_attributes__"); - emit!( - self, - Instruction::StoreName { - namei: static_attrs_name - } - ); + self.store_name("__static_attributes__")?; self.set_no_location(); } @@ -6731,13 +6714,7 @@ impl Compiler { let classdict_idx = u32::from(self.get_cell_var_index("__classdict__")); emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx }); self.set_no_location(); - let classdictcell = self.name("__classdictcell__"); - emit!( - self, - Instruction::StoreName { - namei: classdictcell - } - ); + self.store_name("__classdictcell__")?; self.set_no_location(); } @@ -6751,8 +6728,7 @@ impl Compiler { self.set_no_location(); emit!(self, Instruction::Copy { i: 1 }); self.set_no_location(); - let classcell = self.name("__classcell__"); - emit!(self, Instruction::StoreName { namei: classcell }); + self.store_name("__classcell__")?; self.set_no_location(); } else { self.emit_load_const(ConstantData::None); @@ -20934,6 +20910,86 @@ class C: } } + #[test] + fn test_class_firstlineno_store_uses_name_resolution() { + let code = compile_exec( + "\ +def f(): + __firstlineno__ = 1 + class C: + nonlocal __firstlineno__ + return C +", + ); + let class_code = find_code(&code, "C").expect("missing class code"); + + assert!( + class_code + .freevars + .iter() + .any(|name| name == "__firstlineno__"), + "class should close over nonlocal __firstlineno__, got freevars={:?}", + class_code.freevars + ); + assert!( + class_code + .instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::StoreDeref { .. })), + "CPython routes __firstlineno__ through name resolution and emits STORE_DEREF, got ops={:?}", + class_code.instructions + ); + assert!( + !class_code.instructions.iter().any(|unit| { + matches!( + unit.op, + Instruction::StoreName { namei } + if class_code.names + [namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize] + .as_str() + == "__firstlineno__" + ) + }), + "nonlocal __firstlineno__ should not be stored with STORE_NAME, got ops={:?}", + class_code.instructions + ); + } + + #[test] + fn test_lambda_parent_qualname_includes_locals() { + let code = compile_exec( + "\ +def f(): + return lambda: (lambda: None) +", + ); + let mut lambda_qualnames = Vec::new(); + fn collect_lambda_qualnames(code: &CodeObject, out: &mut Vec) { + if code.obj_name == "" { + out.push(code.qualname.to_string()); + } + for constant in code.constants.iter() { + if let ConstantData::Code { code } = constant { + collect_lambda_qualnames(code, out); + } + } + } + collect_lambda_qualnames(&code, &mut lambda_qualnames); + + assert!( + lambda_qualnames + .iter() + .any(|name| name == "f.."), + "missing outer lambda qualname, got {lambda_qualnames:?}" + ); + assert!( + lambda_qualnames + .iter() + .any(|name| name == "f...."), + "nested lambda parent should include . like CPython, got {lambda_qualnames:?}" + ); + } + #[test] fn test_future_annotations_class_uses_direct_annotation_store() { let code = compile_exec( From 13549d8cdd663e64a3467c3b75cf064efd0cd39f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Tue, 19 May 2026 21:26:24 +0900 Subject: [PATCH 2/2] Address code review feedback --- crates/codegen/src/compile.rs | 45 +++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index b8405777d52..6024732bdc8 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -3258,7 +3258,17 @@ impl Compiler { CompilerScope::Annotation | CompilerScope::TypeParams ) { SymbolScope::GlobalImplicit - } else if name.starts_with('_') { + } else if matches!( + name.as_ref(), + "__name__" + | "__module__" + | "__qualname__" + | "__firstlineno__" + | "__doc__" + | "__static_attributes__" + | "__classdictcell__" + | "__classcell__" + ) { SymbolScope::Unknown } else { return Err(self.error(CodegenErrorType::SyntaxError(format!( @@ -13984,6 +13994,23 @@ def f(buffer, pos, last_char): }) } + fn localsplus_name(code: &CodeObject, idx: usize) -> Option<&str> { + if idx < code.varnames.len() { + return Some(code.varnames[idx].as_str()); + } + + let mut extra_idx = idx - code.varnames.len(); + for cellvar in &code.cellvars { + if !code.varnames.iter().any(|varname| varname == cellvar) { + if extra_idx == 0 { + return Some(cellvar.as_str()); + } + extra_idx -= 1; + } + } + code.freevars.get(extra_idx).map(|name| name.as_str()) + } + fn has_common_constant(code: &CodeObject, expected: bytecode::CommonConstant) -> bool { code.instructions.iter().any(|unit| match unit.op { Instruction::LoadCommonConstant { idx } => { @@ -20932,12 +20959,16 @@ def f(): class_code.freevars ); assert!( - class_code - .instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::StoreDeref { .. })), - "CPython routes __firstlineno__ through name resolution and emits STORE_DEREF, got ops={:?}", - class_code.instructions + class_code.instructions.iter().any(|unit| match unit.op { + Instruction::StoreDeref { i } => { + let idx = i.get(OpArg::new(u32::from(u8::from(unit.arg)))).as_usize(); + localsplus_name(class_code, idx) == Some("__firstlineno__") + } + _ => false, + }), + "CPython routes __firstlineno__ through name resolution and emits STORE_DEREF for __firstlineno__, got ops={:?} freevars={:?}", + class_code.instructions, + class_code.freevars ); assert!( !class_code.instructions.iter().any(|unit| {