@@ -68,6 +68,11 @@ pub struct SymbolTable {
6868
6969 /// Whether `from __future__ import annotations` is active
7070 pub future_annotations : bool ,
71+
72+ /// Names of type parameters that should still be mangled in type param scopes.
73+ /// When Some, only names in this set are mangled; other names are left unmangled.
74+ /// Set on type param blocks for generic classes; inherited by non-class child scopes.
75+ pub mangled_names : Option < IndexSet < String > > ,
7176}
7277
7378impl SymbolTable {
@@ -88,6 +93,7 @@ impl SymbolTable {
8893 annotation_block : None ,
8994 has_conditional_annotations : false ,
9095 future_annotations : false ,
96+ mangled_names : None ,
9197 }
9298 }
9399
@@ -905,14 +911,26 @@ impl SymbolTableBuilder {
905911 )
906912 } )
907913 . unwrap_or ( false ) ;
908- let table = SymbolTable :: new ( name. to_owned ( ) , typ, line_number, is_nested) ;
914+ // Inherit mangled_names from parent for non-class scopes
915+ let inherited_mangled_names = self
916+ . tables
917+ . last ( )
918+ . and_then ( |t| t. mangled_names . clone ( ) )
919+ . filter ( |_| typ != CompilerScope :: Class ) ;
920+ let mut table = SymbolTable :: new ( name. to_owned ( ) , typ, line_number, is_nested) ;
921+ table. mangled_names = inherited_mangled_names;
909922 self . tables . push ( table) ;
910923 // Save parent's varnames and start fresh for the new scope
911924 self . varnames_stack
912925 . push ( core:: mem:: take ( & mut self . current_varnames ) ) ;
913926 }
914927
915- fn enter_type_param_block ( & mut self , name : & str , line_number : u32 ) -> SymbolTableResult {
928+ fn enter_type_param_block (
929+ & mut self ,
930+ name : & str ,
931+ line_number : u32 ,
932+ for_class : bool ,
933+ ) -> SymbolTableResult {
916934 // Check if we're in a class scope
917935 let in_class = self
918936 . tables
@@ -921,16 +939,21 @@ impl SymbolTableBuilder {
921939
922940 self . enter_scope ( name, CompilerScope :: TypeParams , line_number) ;
923941
924- // If we're in a class, mark that this type param scope can see the class scope
942+ // Set properties on the newly created type param scope
925943 if let Some ( table) = self . tables . last_mut ( ) {
926944 table. can_see_class_scope = in_class;
927-
928- // Add __classdict__ as a USE symbol in type param scope if in class
929- if in_class {
930- self . register_name ( "__classdict__" , SymbolUsage :: Used , TextRange :: default ( ) ) ? ;
945+ // For generic classes, create mangled_names set so that only
946+ // type parameter names get mangled (not bases or other expressions)
947+ if for_class {
948+ table . mangled_names = Some ( IndexSet :: default ( ) ) ;
931949 }
932950 }
933951
952+ // Add __classdict__ as a USE symbol in type param scope if in class
953+ if in_class {
954+ self . register_name ( "__classdict__" , SymbolUsage :: Used , TextRange :: default ( ) ) ?;
955+ }
956+
934957 // Register .type_params as a SET symbol (it will be converted to cell variable later)
935958 self . register_name ( ".type_params" , SymbolUsage :: Assigned , TextRange :: default ( ) ) ?;
936959
@@ -1202,12 +1225,20 @@ impl SymbolTableBuilder {
12021225 None
12031226 } ;
12041227
1228+ // For generic functions, scan defaults before entering type_param_block
1229+ // (defaults are evaluated in the enclosing scope, not the type param scope)
1230+ let has_type_params = type_params. is_some ( ) ;
1231+ if has_type_params {
1232+ self . scan_parameter_defaults ( parameters) ?;
1233+ }
1234+
12051235 // For generic functions, enter type_param block FIRST so that
12061236 // annotation scopes are nested inside and can see type parameters.
12071237 if let Some ( type_params) = type_params {
12081238 self . enter_type_param_block (
12091239 & format ! ( "<generic parameters of {}>" , name. as_str( ) ) ,
12101240 self . line_index_start ( type_params. range ) ,
1241+ false ,
12111242 ) ?;
12121243 self . scan_type_params ( type_params) ?;
12131244 }
@@ -1223,6 +1254,7 @@ impl SymbolTableBuilder {
12231254 self . line_index_start ( * range) ,
12241255 has_return_annotation,
12251256 * is_async,
1257+ has_type_params, // skip_defaults: already scanned above
12261258 ) ?;
12271259 self . scan_statements ( body) ?;
12281260 self . leave_scope ( ) ;
@@ -1244,11 +1276,16 @@ impl SymbolTableBuilder {
12441276 range,
12451277 node_index : _,
12461278 } ) => {
1279+ // Save class_name for the entire ClassDef processing
1280+ let prev_class = self . class_name . take ( ) ;
12471281 if let Some ( type_params) = type_params {
12481282 self . enter_type_param_block (
12491283 & format ! ( "<generic parameters of {}>" , name. as_str( ) ) ,
12501284 self . line_index_start ( type_params. range ) ,
1285+ true , // for_class: enable selective mangling
12511286 ) ?;
1287+ // Set class_name for mangling in type param scope
1288+ self . class_name = Some ( name. to_string ( ) ) ;
12521289 self . scan_type_params ( type_params) ?;
12531290 }
12541291 self . enter_scope (
@@ -1257,18 +1294,23 @@ impl SymbolTableBuilder {
12571294 self . line_index_start ( * range) ,
12581295 ) ;
12591296 // Reset in_conditional_block for new class scope
1260- // (each scope has its own conditional context)
12611297 let saved_in_conditional = self . in_conditional_block ;
12621298 self . in_conditional_block = false ;
1263- let prev_class = self . class_name . replace ( name. to_string ( ) ) ;
1299+ self . class_name = Some ( name. to_string ( ) ) ;
12641300 self . register_name ( "__module__" , SymbolUsage :: Assigned , * range) ?;
12651301 self . register_name ( "__qualname__" , SymbolUsage :: Assigned , * range) ?;
12661302 self . register_name ( "__doc__" , SymbolUsage :: Assigned , * range) ?;
12671303 self . register_name ( "__class__" , SymbolUsage :: Assigned , * range) ?;
12681304 self . scan_statements ( body) ?;
12691305 self . leave_scope ( ) ;
12701306 self . in_conditional_block = saved_in_conditional;
1271- self . class_name = prev_class;
1307+ // For non-generic classes, restore class_name before base scanning.
1308+ // Bases are evaluated in the enclosing scope, not the class scope.
1309+ // For generic classes, bases are scanned within the type_param scope
1310+ // where class_name is already correctly set.
1311+ if type_params. is_none ( ) {
1312+ self . class_name = prev_class. clone ( ) ;
1313+ }
12721314 if let Some ( arguments) = arguments {
12731315 self . scan_expressions ( & arguments. args , ExpressionContext :: Load ) ?;
12741316 for keyword in & arguments. keywords {
@@ -1278,6 +1320,8 @@ impl SymbolTableBuilder {
12781320 if type_params. is_some ( ) {
12791321 self . leave_scope ( ) ;
12801322 }
1323+ // Restore class_name after all ClassDef processing
1324+ self . class_name = prev_class;
12811325 self . scan_decorators ( decorator_list, ExpressionContext :: Load ) ?;
12821326 self . register_ident ( name, SymbolUsage :: Assigned ) ?;
12831327 }
@@ -1506,6 +1550,7 @@ impl SymbolTableBuilder {
15061550 self . enter_type_param_block (
15071551 "TypeAlias" ,
15081552 self . line_index_start ( type_params. range ) ,
1553+ false ,
15091554 ) ?;
15101555 self . scan_type_params ( type_params) ?;
15111556 }
@@ -1833,6 +1878,7 @@ impl SymbolTableBuilder {
18331878 self . line_index_start ( expression. range ( ) ) ,
18341879 false , // lambdas have no return annotation
18351880 false , // lambdas are never async
1881+ false , // don't skip defaults
18361882 ) ?;
18371883 } else {
18381884 self . enter_scope (
@@ -2233,23 +2279,32 @@ impl SymbolTableBuilder {
22332279 Ok ( ( ) )
22342280 }
22352281
2282+ /// Scan default parameter values (evaluated in the enclosing scope)
2283+ fn scan_parameter_defaults ( & mut self , parameters : & ast:: Parameters ) -> SymbolTableResult {
2284+ for default in parameters
2285+ . posonlyargs
2286+ . iter ( )
2287+ . chain ( parameters. args . iter ( ) )
2288+ . chain ( parameters. kwonlyargs . iter ( ) )
2289+ . filter_map ( |arg| arg. default . as_ref ( ) )
2290+ {
2291+ self . scan_expression ( default, ExpressionContext :: Load ) ?;
2292+ }
2293+ Ok ( ( ) )
2294+ }
2295+
22362296 fn enter_scope_with_parameters (
22372297 & mut self ,
22382298 name : & str ,
22392299 parameters : & ast:: Parameters ,
22402300 line_number : u32 ,
22412301 has_return_annotation : bool ,
22422302 is_async : bool ,
2303+ skip_defaults : bool ,
22432304 ) -> SymbolTableResult {
2244- // Evaluate eventual default parameters:
2245- for default in parameters
2246- . posonlyargs
2247- . iter ( )
2248- . chain ( parameters. args . iter ( ) )
2249- . chain ( parameters. kwonlyargs . iter ( ) )
2250- . filter_map ( |arg| arg. default . as_ref ( ) )
2251- {
2252- self . scan_expression ( default, ExpressionContext :: Load ) ?; // not ExprContext?
2305+ // Evaluate eventual default parameters (unless already scanned before type_param_block):
2306+ if !skip_defaults {
2307+ self . scan_parameter_defaults ( parameters) ?;
22532308 }
22542309
22552310 // Annotations are scanned in outer scope:
@@ -2381,7 +2436,18 @@ impl SymbolTableBuilder {
23812436 let scope_depth = self . tables . len ( ) ;
23822437 let table = self . tables . last_mut ( ) . unwrap ( ) ;
23832438
2384- let name = mangle_name ( self . class_name . as_deref ( ) , name) ;
2439+ // Add type param names to mangled_names set for selective mangling
2440+ if matches ! ( role, SymbolUsage :: TypeParam )
2441+ && let Some ( ref mut set) = table. mangled_names
2442+ {
2443+ set. insert ( name. to_owned ( ) ) ;
2444+ }
2445+
2446+ let name = maybe_mangle_name (
2447+ self . class_name . as_deref ( ) ,
2448+ table. mangled_names . as_ref ( ) ,
2449+ name,
2450+ ) ;
23852451 // Some checks for the symbol that present on this scope level:
23862452 let symbol = if let Some ( symbol) = table. symbols . get_mut ( name. as_ref ( ) ) {
23872453 let flags = & symbol. flags ;
@@ -2574,11 +2640,27 @@ pub(crate) fn mangle_name<'a>(class_name: Option<&str>, name: &'a str) -> Cow<'a
25742640 if !name. starts_with ( "__" ) || name. ends_with ( "__" ) || name. contains ( '.' ) {
25752641 return name. into ( ) ;
25762642 }
2577- // strip leading underscore
2578- let class_name = class_name. strip_prefix ( |c| c == '_' ) . unwrap_or ( class_name ) ;
2643+ // Strip leading underscores from class name
2644+ let class_name = class_name. trim_start_matches ( '_' ) ;
25792645 let mut ret = String :: with_capacity ( 1 + class_name. len ( ) + name. len ( ) ) ;
25802646 ret. push ( '_' ) ;
25812647 ret. push_str ( class_name) ;
25822648 ret. push_str ( name) ;
25832649 ret. into ( )
25842650}
2651+
2652+ /// Selective mangling for type parameter scopes around generic classes.
2653+ /// If `mangled_names` is Some, only mangle names that are in the set;
2654+ /// other names are left unmangled.
2655+ pub ( crate ) fn maybe_mangle_name < ' a > (
2656+ class_name : Option < & str > ,
2657+ mangled_names : Option < & IndexSet < String > > ,
2658+ name : & ' a str ,
2659+ ) -> Cow < ' a , str > {
2660+ if let Some ( set) = mangled_names
2661+ && !set. contains ( name)
2662+ {
2663+ return name. into ( ) ;
2664+ }
2665+ mangle_name ( class_name, name)
2666+ }
0 commit comments