Skip to content

Commit 62b081b

Browse files
authored
Resolve test_inspect bytecode parity gaps (#7926)
* Resolve test_inspect bytecode parity gaps * Address code review feedback
1 parent 26f5bbf commit 62b081b

2 files changed

Lines changed: 132 additions & 46 deletions

File tree

Lib/test/test_inspect/test_inspect.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,6 @@ def test_getsource_on_generated_class(self):
888888
self.assertRaises(OSError, inspect.getsourcelines, A)
889889
self.assertIsNone(inspect.getcomments(A))
890890

891-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by getsource
892891
def test_getsource_on_class_without_firstlineno(self):
893892
__firstlineno__ = 1
894893
class C:

crates/codegen/src/compile.rs

Lines changed: 132 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,7 +2723,7 @@ impl Compiler {
27232723
let mut parent_idx = stack_size - 2;
27242724
let mut parent = &self.code_stack[parent_idx];
27252725

2726-
let parent_scope = self
2726+
let mut parent_scope = self
27272727
.symbol_table_stack
27282728
.get(parent_idx)
27292729
.map(|table| table.typ);
@@ -2742,6 +2742,10 @@ impl Compiler {
27422742
// Use grandparent
27432743
parent_idx = stack_size - 3;
27442744
parent = &self.code_stack[parent_idx];
2745+
parent_scope = self
2746+
.symbol_table_stack
2747+
.get(parent_idx)
2748+
.map(|table| table.typ);
27452749
}
27462750

27472751
// Check if this is a global class/function
@@ -2779,9 +2783,12 @@ impl Compiler {
27792783
let parent_obj_name = &parent.metadata.name;
27802784

27812785
// Determine if parent is a function-like scope
2782-
let is_function_parent = parent.flags.contains(bytecode::CodeFlags::OPTIMIZED)
2783-
&& !parent_obj_name.starts_with("<") // Not a special scope like <lambda>, <listcomp>, etc.
2784-
&& parent_obj_name != "<module>"; // Not the module scope
2786+
let is_function_parent = matches!(
2787+
parent_scope,
2788+
Some(
2789+
CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda
2790+
)
2791+
);
27852792

27862793
if is_function_parent {
27872794
// For functions, append .<locals> to parent qualname
@@ -3251,6 +3258,18 @@ impl Compiler {
32513258
CompilerScope::Annotation | CompilerScope::TypeParams
32523259
) {
32533260
SymbolScope::GlobalImplicit
3261+
} else if matches!(
3262+
name.as_ref(),
3263+
"__name__"
3264+
| "__module__"
3265+
| "__qualname__"
3266+
| "__firstlineno__"
3267+
| "__doc__"
3268+
| "__static_attributes__"
3269+
| "__classdictcell__"
3270+
| "__classcell__"
3271+
) {
3272+
SymbolScope::Unknown
32543273
} else {
32553274
return Err(self.error(CodegenErrorType::SyntaxError(format!(
32563275
"the symbol '{name}' must be present in the symbol table"
@@ -6604,39 +6623,20 @@ impl Compiler {
66046623
let (doc_str, body) = split_doc(body, &self.opts);
66056624

66066625
// Load __name__ and store as __module__
6607-
let dunder_name = self.name("__name__");
6608-
emit!(self, Instruction::LoadName { namei: dunder_name });
6609-
let dunder_module = self.name("__module__");
6610-
emit!(
6611-
self,
6612-
Instruction::StoreName {
6613-
namei: dunder_module
6614-
}
6615-
);
6626+
self.load_name("__name__")?;
6627+
self.store_name("__module__")?;
66166628

66176629
// Store __qualname__
66186630
self.emit_load_const(ConstantData::Str {
66196631
value: qualname.into(),
66206632
});
6621-
let qualname_name = self.name("__qualname__");
6622-
emit!(
6623-
self,
6624-
Instruction::StoreName {
6625-
namei: qualname_name
6626-
}
6627-
);
6633+
self.store_name("__qualname__")?;
66286634

66296635
// Store __firstlineno__ before __doc__
66306636
self.emit_load_const(ConstantData::Integer {
66316637
value: BigInt::from(firstlineno),
66326638
});
6633-
let firstlineno_name = self.name("__firstlineno__");
6634-
emit!(
6635-
self,
6636-
Instruction::StoreName {
6637-
namei: firstlineno_name
6638-
}
6639-
);
6639+
self.store_name("__firstlineno__")?;
66406640

66416641
// Set __type_params__ from the enclosing type-params closure when
66426642
// compiling a generic class body.
@@ -6668,8 +6668,7 @@ impl Compiler {
66686668
// Store __doc__ only if there's an explicit docstring.
66696669
if let Some(doc) = doc_str {
66706670
self.emit_load_const(ConstantData::Str { value: doc.into() });
6671-
let doc_name = self.name("__doc__");
6672-
emit!(self, Instruction::StoreName { namei: doc_name });
6671+
self.store_name("__doc__")?;
66736672
}
66746673

66756674
// 3. Compile the class body
@@ -6707,13 +6706,7 @@ impl Compiler {
67076706
.collect(),
67086707
});
67096708
self.set_no_location();
6710-
let static_attrs_name = self.name("__static_attributes__");
6711-
emit!(
6712-
self,
6713-
Instruction::StoreName {
6714-
namei: static_attrs_name
6715-
}
6716-
);
6709+
self.store_name("__static_attributes__")?;
67176710
self.set_no_location();
67186711
}
67196712

@@ -6722,13 +6715,7 @@ impl Compiler {
67226715
let classdict_idx = u32::from(self.get_cell_var_index("__classdict__"));
67236716
emit!(self, PseudoInstruction::LoadClosure { i: classdict_idx });
67246717
self.set_no_location();
6725-
let classdictcell = self.name("__classdictcell__");
6726-
emit!(
6727-
self,
6728-
Instruction::StoreName {
6729-
namei: classdictcell
6730-
}
6731-
);
6718+
self.store_name("__classdictcell__")?;
67326719
self.set_no_location();
67336720
}
67346721

@@ -6742,8 +6729,7 @@ impl Compiler {
67426729
self.set_no_location();
67436730
emit!(self, Instruction::Copy { i: 1 });
67446731
self.set_no_location();
6745-
let classcell = self.name("__classcell__");
6746-
emit!(self, Instruction::StoreName { namei: classcell });
6732+
self.store_name("__classcell__")?;
67476733
self.set_no_location();
67486734
} else {
67496735
self.emit_load_const(ConstantData::None);
@@ -13999,6 +13985,23 @@ def f(buffer, pos, last_char):
1399913985
})
1400013986
}
1400113987

13988+
fn localsplus_name(code: &CodeObject, idx: usize) -> Option<&str> {
13989+
if idx < code.varnames.len() {
13990+
return Some(code.varnames[idx].as_str());
13991+
}
13992+
13993+
let mut extra_idx = idx - code.varnames.len();
13994+
for cellvar in &code.cellvars {
13995+
if !code.varnames.iter().any(|varname| varname == cellvar) {
13996+
if extra_idx == 0 {
13997+
return Some(cellvar.as_str());
13998+
}
13999+
extra_idx -= 1;
14000+
}
14001+
}
14002+
code.freevars.get(extra_idx).map(|name| name.as_str())
14003+
}
14004+
1400214005
fn has_common_constant(code: &CodeObject, expected: bytecode::CommonConstant) -> bool {
1400314006
code.instructions.iter().any(|unit| match unit.op {
1400414007
Instruction::LoadCommonConstant { idx } => {
@@ -20925,6 +20928,90 @@ class C:
2092520928
}
2092620929
}
2092720930

20931+
#[test]
20932+
fn test_class_firstlineno_store_uses_name_resolution() {
20933+
let code = compile_exec(
20934+
"\
20935+
def f():
20936+
__firstlineno__ = 1
20937+
class C:
20938+
nonlocal __firstlineno__
20939+
return C
20940+
",
20941+
);
20942+
let class_code = find_code(&code, "C").expect("missing class code");
20943+
20944+
assert!(
20945+
class_code
20946+
.freevars
20947+
.iter()
20948+
.any(|name| name == "__firstlineno__"),
20949+
"class should close over nonlocal __firstlineno__, got freevars={:?}",
20950+
class_code.freevars
20951+
);
20952+
assert!(
20953+
class_code.instructions.iter().any(|unit| match unit.op {
20954+
Instruction::StoreDeref { i } => {
20955+
let idx = i.get(OpArg::new(u32::from(u8::from(unit.arg)))).as_usize();
20956+
localsplus_name(class_code, idx) == Some("__firstlineno__")
20957+
}
20958+
_ => false,
20959+
}),
20960+
"CPython routes __firstlineno__ through name resolution and emits STORE_DEREF for __firstlineno__, got ops={:?} freevars={:?}",
20961+
class_code.instructions,
20962+
class_code.freevars
20963+
);
20964+
assert!(
20965+
!class_code.instructions.iter().any(|unit| {
20966+
matches!(
20967+
unit.op,
20968+
Instruction::StoreName { namei }
20969+
if class_code.names
20970+
[namei.get(OpArg::new(u32::from(u8::from(unit.arg)))) as usize]
20971+
.as_str()
20972+
== "__firstlineno__"
20973+
)
20974+
}),
20975+
"nonlocal __firstlineno__ should not be stored with STORE_NAME, got ops={:?}",
20976+
class_code.instructions
20977+
);
20978+
}
20979+
20980+
#[test]
20981+
fn test_lambda_parent_qualname_includes_locals() {
20982+
let code = compile_exec(
20983+
"\
20984+
def f():
20985+
return lambda: (lambda: None)
20986+
",
20987+
);
20988+
let mut lambda_qualnames = Vec::new();
20989+
fn collect_lambda_qualnames(code: &CodeObject, out: &mut Vec<String>) {
20990+
if code.obj_name == "<lambda>" {
20991+
out.push(code.qualname.to_string());
20992+
}
20993+
for constant in code.constants.iter() {
20994+
if let ConstantData::Code { code } = constant {
20995+
collect_lambda_qualnames(code, out);
20996+
}
20997+
}
20998+
}
20999+
collect_lambda_qualnames(&code, &mut lambda_qualnames);
21000+
21001+
assert!(
21002+
lambda_qualnames
21003+
.iter()
21004+
.any(|name| name == "f.<locals>.<lambda>"),
21005+
"missing outer lambda qualname, got {lambda_qualnames:?}"
21006+
);
21007+
assert!(
21008+
lambda_qualnames
21009+
.iter()
21010+
.any(|name| name == "f.<locals>.<lambda>.<locals>.<lambda>"),
21011+
"nested lambda parent should include .<locals> like CPython, got {lambda_qualnames:?}"
21012+
);
21013+
}
21014+
2092821015
#[test]
2092921016
fn test_future_annotations_class_uses_direct_annotation_store() {
2093021017
let code = compile_exec(

0 commit comments

Comments
 (0)