@@ -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,18 @@ 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+ // Bases/keywords are scanned in type_param scope (if generic)
1308+ // class_name is still set, so mangling works via mangled_names filtering
12721309 if let Some ( arguments) = arguments {
12731310 self . scan_expressions ( & arguments. args , ExpressionContext :: Load ) ?;
12741311 for keyword in & arguments. keywords {
@@ -1278,6 +1315,8 @@ impl SymbolTableBuilder {
12781315 if type_params. is_some ( ) {
12791316 self . leave_scope ( ) ;
12801317 }
1318+ // Restore class_name after all ClassDef processing
1319+ self . class_name = prev_class;
12811320 self . scan_decorators ( decorator_list, ExpressionContext :: Load ) ?;
12821321 self . register_ident ( name, SymbolUsage :: Assigned ) ?;
12831322 }
@@ -1506,6 +1545,7 @@ impl SymbolTableBuilder {
15061545 self . enter_type_param_block (
15071546 "TypeAlias" ,
15081547 self . line_index_start ( type_params. range ) ,
1548+ false ,
15091549 ) ?;
15101550 self . scan_type_params ( type_params) ?;
15111551 }
@@ -1833,6 +1873,7 @@ impl SymbolTableBuilder {
18331873 self . line_index_start ( expression. range ( ) ) ,
18341874 false , // lambdas have no return annotation
18351875 false , // lambdas are never async
1876+ false , // don't skip defaults
18361877 ) ?;
18371878 } else {
18381879 self . enter_scope (
@@ -2233,23 +2274,32 @@ impl SymbolTableBuilder {
22332274 Ok ( ( ) )
22342275 }
22352276
2277+ /// Scan default parameter values (evaluated in the enclosing scope)
2278+ fn scan_parameter_defaults ( & mut self , parameters : & ast:: Parameters ) -> SymbolTableResult {
2279+ for default in parameters
2280+ . posonlyargs
2281+ . iter ( )
2282+ . chain ( parameters. args . iter ( ) )
2283+ . chain ( parameters. kwonlyargs . iter ( ) )
2284+ . filter_map ( |arg| arg. default . as_ref ( ) )
2285+ {
2286+ self . scan_expression ( default, ExpressionContext :: Load ) ?;
2287+ }
2288+ Ok ( ( ) )
2289+ }
2290+
22362291 fn enter_scope_with_parameters (
22372292 & mut self ,
22382293 name : & str ,
22392294 parameters : & ast:: Parameters ,
22402295 line_number : u32 ,
22412296 has_return_annotation : bool ,
22422297 is_async : bool ,
2298+ skip_defaults : bool ,
22432299 ) -> 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?
2300+ // Evaluate eventual default parameters (unless already scanned before type_param_block):
2301+ if !skip_defaults {
2302+ self . scan_parameter_defaults ( parameters) ?;
22532303 }
22542304
22552305 // Annotations are scanned in outer scope:
@@ -2381,7 +2431,18 @@ impl SymbolTableBuilder {
23812431 let scope_depth = self . tables . len ( ) ;
23822432 let table = self . tables . last_mut ( ) . unwrap ( ) ;
23832433
2384- let name = mangle_name ( self . class_name . as_deref ( ) , name) ;
2434+ // Add type param names to mangled_names set for selective mangling
2435+ if matches ! ( role, SymbolUsage :: TypeParam )
2436+ && let Some ( ref mut set) = table. mangled_names
2437+ {
2438+ set. insert ( name. to_owned ( ) ) ;
2439+ }
2440+
2441+ let name = maybe_mangle_name (
2442+ self . class_name . as_deref ( ) ,
2443+ table. mangled_names . as_ref ( ) ,
2444+ name,
2445+ ) ;
23852446 // Some checks for the symbol that present on this scope level:
23862447 let symbol = if let Some ( symbol) = table. symbols . get_mut ( name. as_ref ( ) ) {
23872448 let flags = & symbol. flags ;
@@ -2574,11 +2635,27 @@ pub(crate) fn mangle_name<'a>(class_name: Option<&str>, name: &'a str) -> Cow<'a
25742635 if !name. starts_with ( "__" ) || name. ends_with ( "__" ) || name. contains ( '.' ) {
25752636 return name. into ( ) ;
25762637 }
2577- // strip leading underscore
2578- let class_name = class_name. strip_prefix ( |c| c == '_' ) . unwrap_or ( class_name ) ;
2638+ // Strip leading underscores from class name
2639+ let class_name = class_name. trim_start_matches ( '_' ) ;
25792640 let mut ret = String :: with_capacity ( 1 + class_name. len ( ) + name. len ( ) ) ;
25802641 ret. push ( '_' ) ;
25812642 ret. push_str ( class_name) ;
25822643 ret. push_str ( name) ;
25832644 ret. into ( )
25842645}
2646+
2647+ /// Selective mangling for type parameter scopes around generic classes.
2648+ /// If `mangled_names` is Some, only mangle names that are in the set;
2649+ /// other names are left unmangled.
2650+ pub ( crate ) fn maybe_mangle_name < ' a > (
2651+ class_name : Option < & str > ,
2652+ mangled_names : Option < & IndexSet < String > > ,
2653+ name : & ' a str ,
2654+ ) -> Cow < ' a , str > {
2655+ if let Some ( set) = mangled_names
2656+ && !set. contains ( name)
2657+ {
2658+ return name. into ( ) ;
2659+ }
2660+ mangle_name ( class_name, name)
2661+ }
0 commit comments