@@ -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