Skip to content

Commit 2ea604a

Browse files
committed
Fix typing, typevar, genericalias, and symboltable
- TypeVar/ParamSpec repr: use infer_variance flag - ParamSpec: add type_check on bound argument - ParamSpecArgs/Kwargs: use equality instead of identity - NoDefault: change to IMMUTABLETYPE flag - subscript_generic: wrap TypeVarTuple in Unpack - symboltable: selective name mangling in type param scopes - symboltable: fix double default scanning for non-generic fns - Unmark 4 passing tests in test_type_params
1 parent d50cc5e commit 2ea604a

File tree

7 files changed

+183
-97
lines changed

7 files changed

+183
-97
lines changed

Lib/test/test_concurrent_futures/test_process_pool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test_traceback(self):
8585
self.assertIn('raise RuntimeError(123) # some comment',
8686
f1.getvalue())
8787

88-
@unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON flaky EOFError')
88+
@unittest.skip('TODO: RUSTPYTHON flaky EOFError')
8989
@hashlib_helper.requires_hashdigest('md5')
9090
def test_ressources_gced_in_workers(self):
9191
# Ensure that argument for a job are correctly gc-ed after the job

Lib/test/test_type_params.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ def func[A, B](a: dict[A, B]):
243243
A, B = func.__type_params__
244244
self.assertEqual(func.__annotations__["a"], dict[A, B])
245245

246-
@unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: the symbol 'list' must be present in the symbol table
247246
def test_function_access_02(self):
248247
code = """
249248
def func[A](a = list[A]()):
@@ -866,7 +865,6 @@ class Inner[T]:
866865
self.assertEqual(Outer.Inner._Inner__x, "inner")
867866
self.assertEqual(Outer._Outer__after, "after")
868867

869-
@unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name '_Derived__Base' is not defined
870868
def test_no_mangling_in_bases(self):
871869
ns = run_code("""
872870
class __Base:
@@ -880,7 +878,6 @@ class Derived[T](__Base, __kwarg=1):
880878
self.assertEqual(Derived.__bases__, (ns["__Base"], Generic))
881879
self.assertEqual(Derived.kwargs, {"__kwarg": 1})
882880

883-
@unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: the symbol '_Y__X' must be present in the symbol table
884881
def test_no_mangling_in_nested_scopes(self):
885882
ns = run_code("""
886883
from test.test_type_params import make_base
@@ -911,7 +908,6 @@ class Y[T: __X](
911908
base3 = Y.__bases__[3]
912909
self.assertEqual(list(base3.__arg__), [ns["__X"]])
913910

914-
@unittest.expectedFailure # TODO: RUSTPYTHON; SyntaxError: the symbol '_Foo__T' must be present in the symbol table
915911
def test_type_params_are_mangled(self):
916912
ns = run_code("""
917913
from test.test_type_params import make_base

crates/codegen/src/compile.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1852,7 +1852,8 @@ impl Compiler {
18521852
.code_stack
18531853
.last()
18541854
.and_then(|info| info.private.as_deref());
1855-
symboltable::mangle_name(private, name)
1855+
let mangled_names = self.current_symbol_table().mangled_names.as_ref();
1856+
symboltable::maybe_mangle_name(private, mangled_names, name)
18561857
}
18571858

18581859
// = compiler_nameop

crates/codegen/src/symboltable.rs

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7378
impl 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,19 @@ 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+
// Set class_name for mangling in class body
1300+
self.class_name = Some(name.to_string());
12641301
self.register_name("__module__", SymbolUsage::Assigned, *range)?;
12651302
self.register_name("__qualname__", SymbolUsage::Assigned, *range)?;
12661303
self.register_name("__doc__", SymbolUsage::Assigned, *range)?;
12671304
self.register_name("__class__", SymbolUsage::Assigned, *range)?;
12681305
self.scan_statements(body)?;
12691306
self.leave_scope();
12701307
self.in_conditional_block = saved_in_conditional;
1271-
self.class_name = prev_class;
1308+
// Bases/keywords are scanned in type_param scope (if generic)
1309+
// class_name is still set, so mangling works via mangled_names filtering
12721310
if let Some(arguments) = arguments {
12731311
self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
12741312
for keyword in &arguments.keywords {
@@ -1278,6 +1316,8 @@ impl SymbolTableBuilder {
12781316
if type_params.is_some() {
12791317
self.leave_scope();
12801318
}
1319+
// Restore class_name after all ClassDef processing
1320+
self.class_name = prev_class;
12811321
self.scan_decorators(decorator_list, ExpressionContext::Load)?;
12821322
self.register_ident(name, SymbolUsage::Assigned)?;
12831323
}
@@ -1506,6 +1546,7 @@ impl SymbolTableBuilder {
15061546
self.enter_type_param_block(
15071547
"TypeAlias",
15081548
self.line_index_start(type_params.range),
1549+
false,
15091550
)?;
15101551
self.scan_type_params(type_params)?;
15111552
}
@@ -1833,6 +1874,7 @@ impl SymbolTableBuilder {
18331874
self.line_index_start(expression.range()),
18341875
false, // lambdas have no return annotation
18351876
false, // lambdas are never async
1877+
false, // don't skip defaults
18361878
)?;
18371879
} else {
18381880
self.enter_scope(
@@ -2233,23 +2275,32 @@ impl SymbolTableBuilder {
22332275
Ok(())
22342276
}
22352277

2278+
/// Scan default parameter values (evaluated in the enclosing scope)
2279+
fn scan_parameter_defaults(&mut self, parameters: &ast::Parameters) -> SymbolTableResult {
2280+
for default in parameters
2281+
.posonlyargs
2282+
.iter()
2283+
.chain(parameters.args.iter())
2284+
.chain(parameters.kwonlyargs.iter())
2285+
.filter_map(|arg| arg.default.as_ref())
2286+
{
2287+
self.scan_expression(default, ExpressionContext::Load)?;
2288+
}
2289+
Ok(())
2290+
}
2291+
22362292
fn enter_scope_with_parameters(
22372293
&mut self,
22382294
name: &str,
22392295
parameters: &ast::Parameters,
22402296
line_number: u32,
22412297
has_return_annotation: bool,
22422298
is_async: bool,
2299+
skip_defaults: bool,
22432300
) -> 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?
2301+
// Evaluate eventual default parameters (unless already scanned before type_param_block):
2302+
if !skip_defaults {
2303+
self.scan_parameter_defaults(parameters)?;
22532304
}
22542305

22552306
// Annotations are scanned in outer scope:
@@ -2381,7 +2432,18 @@ impl SymbolTableBuilder {
23812432
let scope_depth = self.tables.len();
23822433
let table = self.tables.last_mut().unwrap();
23832434

2384-
let name = mangle_name(self.class_name.as_deref(), name);
2435+
// Add type param names to mangled_names set for selective mangling
2436+
if matches!(role, SymbolUsage::TypeParam)
2437+
&& let Some(ref mut set) = table.mangled_names
2438+
{
2439+
set.insert(name.to_owned());
2440+
}
2441+
2442+
let name = maybe_mangle_name(
2443+
self.class_name.as_deref(),
2444+
table.mangled_names.as_ref(),
2445+
name,
2446+
);
23852447
// Some checks for the symbol that present on this scope level:
23862448
let symbol = if let Some(symbol) = table.symbols.get_mut(name.as_ref()) {
23872449
let flags = &symbol.flags;
@@ -2582,3 +2644,19 @@ pub(crate) fn mangle_name<'a>(class_name: Option<&str>, name: &'a str) -> Cow<'a
25822644
ret.push_str(name);
25832645
ret.into()
25842646
}
2647+
2648+
/// Selective mangling for type parameter scopes around generic classes.
2649+
/// If `mangled_names` is Some, only mangle names that are in the set;
2650+
/// other names are left unmangled.
2651+
pub(crate) fn maybe_mangle_name<'a>(
2652+
class_name: Option<&str>,
2653+
mangled_names: Option<&IndexSet<String>>,
2654+
name: &'a str,
2655+
) -> Cow<'a, str> {
2656+
if let Some(set) = mangled_names
2657+
&& !set.contains(name)
2658+
{
2659+
return name.into();
2660+
}
2661+
mangle_name(class_name, name)
2662+
}

crates/vm/src/builtins/genericalias.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -720,24 +720,51 @@ impl crate::types::IterNext for PyGenericAliasIterator {
720720
}
721721
}
722722

723-
/// Creates a GenericAlias from type parameters, equivalent to CPython's _Py_subscript_generic
724-
/// This is used for PEP 695 classes to create Generic[T] from type parameters
723+
/// Creates a GenericAlias from type parameters, equivalent to _Py_subscript_generic.
724+
/// This is used for PEP 695 classes to create Generic[T] from type parameters.
725725
// _Py_subscript_generic
726726
pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult {
727-
// Get typing module and _GenericAlias
728727
let typing_module = vm.import("typing", 0)?;
729728
let generic_type = typing_module.get_attr("Generic", vm)?;
730-
731-
// Call typing._GenericAlias(Generic, type_params)
732729
let generic_alias_class = typing_module.get_attr("_GenericAlias", vm)?;
733730

734-
let args = if let Ok(tuple) = type_params.try_to_ref::<PyTuple>(vm) {
731+
let params = if let Ok(tuple) = type_params.try_to_ref::<PyTuple>(vm) {
735732
tuple.to_owned()
736733
} else {
737734
PyTuple::new_ref(vec![type_params], &vm.ctx)
738735
};
739736

740-
// Create _GenericAlias instance
737+
// Unpack TypeVarTuples: wrap each TypeVarTuple in Unpack[...]
738+
let mut new_args = Vec::with_capacity(params.len());
739+
let mut needs_unpack = false;
740+
for param in params.iter() {
741+
if param
742+
.downcast_ref::<crate::stdlib::typing::TypeVarTuple>()
743+
.is_some()
744+
{
745+
needs_unpack = true;
746+
break;
747+
}
748+
}
749+
750+
let args = if needs_unpack {
751+
let unpack_cls = typing_module.get_attr("Unpack", vm)?;
752+
for param in params.iter() {
753+
if param
754+
.downcast_ref::<crate::stdlib::typing::TypeVarTuple>()
755+
.is_some()
756+
{
757+
let unpacked = vm.call_method(&unpack_cls, "__getitem__", (param.clone(),))?;
758+
new_args.push(unpacked);
759+
} else {
760+
new_args.push(param.clone());
761+
}
762+
}
763+
PyTuple::new_ref(new_args, &vm.ctx)
764+
} else {
765+
params
766+
};
767+
741768
generic_alias_class.call((generic_type, args.to_pyobject(vm)), vm)
742769
}
743770

0 commit comments

Comments
 (0)