@@ -402,12 +402,8 @@ impl Compiler<'_> {
402402 . map ( |( var, _) | var. clone ( ) )
403403 . collect ( ) ;
404404
405- // Calculate qualname based on the current qualified path
406- let qualname = if self . qualified_path . is_empty ( ) {
407- Some ( obj_name. clone ( ) )
408- } else {
409- Some ( self . qualified_path . join ( "." ) )
410- } ;
405+ // Qualname will be set later by set_qualname for non-module scopes
406+ let qualname = None ;
411407
412408 let info = ir:: CodeInfo {
413409 flags,
@@ -464,6 +460,101 @@ impl Compiler<'_> {
464460 . to_u32 ( )
465461 }
466462
463+ /// Set the qualified name for the current code object, based on CPython's compiler_set_qualname
464+ fn set_qualname ( & mut self ) -> String {
465+ let qualname = self . make_qualname ( ) ;
466+ self . current_code_info ( ) . qualname = Some ( qualname. clone ( ) ) ;
467+ qualname
468+ }
469+ fn make_qualname ( & mut self ) -> String {
470+ let stack_size = self . code_stack . len ( ) ;
471+ assert ! ( stack_size >= 1 ) ;
472+
473+ let current_obj_name = self . current_code_info ( ) . obj_name . clone ( ) ;
474+
475+ // If we're at the module level (stack_size == 1), qualname is just the name
476+ if stack_size <= 1 {
477+ return current_obj_name;
478+ }
479+
480+ // Check parent scope
481+ let mut parent_idx = stack_size - 2 ;
482+ let mut parent = & self . code_stack [ parent_idx] ;
483+
484+ // If parent is a type parameter scope, look at grandparent
485+ if parent. obj_name . starts_with ( "<generic parameters of " ) {
486+ if stack_size == 2 {
487+ // If we're immediately within the module after type params,
488+ // qualname is just the name
489+ return current_obj_name;
490+ }
491+ parent_idx = stack_size - 3 ;
492+ parent = & self . code_stack [ parent_idx] ;
493+ }
494+
495+ // Check if this is a global class/function
496+ let mut force_global = false ;
497+ if stack_size > self . symbol_table_stack . len ( ) {
498+ // We might be in a situation where symbol table isn't pushed yet
499+ // In this case, check the parent symbol table
500+ if let Some ( parent_table) = self . symbol_table_stack . last ( ) {
501+ if let Some ( symbol) = parent_table. lookup ( & current_obj_name) {
502+ if symbol. scope == SymbolScope :: GlobalExplicit {
503+ force_global = true ;
504+ }
505+ }
506+ }
507+ } else if let Some ( _current_table) = self . symbol_table_stack . last ( ) {
508+ // Mangle the name if necessary (for private names in classes)
509+ let mangled_name = self . mangle ( & current_obj_name) ;
510+
511+ // Look up in parent symbol table to check scope
512+ if self . symbol_table_stack . len ( ) >= 2 {
513+ let parent_table = & self . symbol_table_stack [ self . symbol_table_stack . len ( ) - 2 ] ;
514+ if let Some ( symbol) = parent_table. lookup ( & mangled_name) {
515+ if symbol. scope == SymbolScope :: GlobalExplicit {
516+ force_global = true ;
517+ }
518+ }
519+ }
520+ }
521+
522+ // Build the qualified name
523+ if force_global {
524+ // For global symbols, qualname is just the name
525+ current_obj_name
526+ } else {
527+ // Check parent scope type
528+ let parent_obj_name = & parent. obj_name ;
529+
530+ // Determine if parent is a function-like scope
531+ let is_function_parent = parent. flags . contains ( bytecode:: CodeFlags :: IS_OPTIMIZED )
532+ && !parent_obj_name. starts_with ( "<" ) // Not a special scope like <lambda>, <listcomp>, etc.
533+ && parent_obj_name != "main" ; // Not the module scope
534+
535+ let path_len = self . qualified_path . len ( ) ;
536+
537+ if is_function_parent {
538+ // For functions, append .<locals> to parent qualname
539+ // Use parent's qualname if available, otherwise use parent_obj_name
540+ let parent_qualname = parent. qualname . as_ref ( ) . unwrap_or ( parent_obj_name) ;
541+ format ! ( "{parent_qualname}.<locals>.{current_obj_name}" )
542+ } else {
543+ // For classes and other scopes, use qualified_path without current name
544+ // (since current name is already pushed to qualified_path)
545+ if path_len > 0 && self . qualified_path [ path_len - 1 ] == current_obj_name {
546+ // Current name is already in qualified_path, just join
547+ self . qualified_path . join ( "." )
548+ } else if self . qualified_path . is_empty ( ) {
549+ current_obj_name
550+ } else {
551+ // Append current name to qualified_path
552+ format ! ( "{}.{}" , self . qualified_path. join( "." ) , current_obj_name)
553+ }
554+ }
555+ }
556+ }
557+
467558 fn compile_program (
468559 & mut self ,
469560 body : & ModModule ,
@@ -1503,10 +1594,9 @@ impl Compiler<'_> {
15031594 } ;
15041595
15051596 self . push_qualified_path ( name) ;
1506- let qualified_name = self . qualified_path . join ( "." ) ;
15071597
1508- // Update the qualname in the current code info
1509- self . code_stack . last_mut ( ) . unwrap ( ) . qualname = Some ( qualified_name . clone ( ) ) ;
1598+ // Set the qualified name using set_qualname
1599+ let qualname = self . set_qualname ( ) ;
15101600
15111601 self . push_qualified_path ( "<locals>" ) ;
15121602
@@ -1593,7 +1683,7 @@ impl Compiler<'_> {
15931683 code : Box :: new ( code) ,
15941684 } ) ;
15951685 self . emit_load_const ( ConstantData :: Str {
1596- value : qualified_name . into ( ) ,
1686+ value : qualname . into ( ) ,
15971687 } ) ;
15981688
15991689 // Turn code object into function object:
@@ -1719,7 +1809,6 @@ impl Compiler<'_> {
17191809 global_path_prefix. append ( & mut self . qualified_path ) ;
17201810 }
17211811 self . push_qualified_path ( name) ;
1722- let qualified_name = self . qualified_path . join ( "." ) ;
17231812
17241813 // If there are type params, we need to push a special symbol table just for them
17251814 if let Some ( type_params) = type_params {
@@ -1732,17 +1821,18 @@ impl Compiler<'_> {
17321821
17331822 self . push_output ( bytecode:: CodeFlags :: empty ( ) , 0 , 0 , 0 , name. to_owned ( ) ) ;
17341823
1735- // Update the qualname in the current code info
1736- self . code_stack . last_mut ( ) . unwrap ( ) . qualname = Some ( qualified_name . clone ( ) ) ;
1824+ // Set the qualified name using set_qualname
1825+ let qualname = self . set_qualname ( ) ;
17371826
17381827 let ( doc_str, body) = split_doc ( body, & self . opts ) ;
17391828
17401829 let dunder_name = self . name ( "__name__" ) ;
17411830 emit ! ( self , Instruction :: LoadGlobal ( dunder_name) ) ;
17421831 let dunder_module = self . name ( "__module__" ) ;
17431832 emit ! ( self , Instruction :: StoreLocal ( dunder_module) ) ;
1833+
17441834 self . emit_load_const ( ConstantData :: Str {
1745- value : qualified_name . into ( ) ,
1835+ value : qualname . into ( ) ,
17461836 } ) ;
17471837 let qualname = self . name ( "__qualname__" ) ;
17481838 emit ! ( self , Instruction :: StoreLocal ( qualname) ) ;
@@ -3510,8 +3600,8 @@ impl Compiler<'_> {
35103600 let mut func_flags = self
35113601 . enter_function ( & name, parameters. as_deref ( ) . unwrap_or ( & Default :: default ( ) ) ) ?;
35123602
3513- // Lambda qualname should be < lambda>
3514- self . code_stack . last_mut ( ) . unwrap ( ) . qualname = Some ( name . clone ( ) ) ;
3603+ // Set qualname for lambda
3604+ self . set_qualname ( ) ;
35153605
35163606 self . ctx = CompileContext {
35173607 loop_data : Option :: None ,
@@ -3976,7 +4066,7 @@ impl Compiler<'_> {
39764066 self . push_output ( flags, 1 , 1 , 0 , name. to_owned ( ) ) ;
39774067
39784068 // Set qualname for comprehension
3979- self . code_stack . last_mut ( ) . unwrap ( ) . qualname = Some ( name . to_owned ( ) ) ;
4069+ self . set_qualname ( ) ;
39804070
39814071 let arg0 = self . varname ( ".0" ) ?;
39824072
0 commit comments