From b4f4ffa4a38ef5b78eff0ae00d5198055f93c54c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 26 Jun 2025 21:38:52 +0900 Subject: [PATCH 01/10] genericalias --- Lib/test/test_dataclasses.py | 2 - Lib/test/test_genericalias.py | 4 - Lib/test/test_types.py | 2 - vm/src/builtins/genericalias.rs | 157 +++++++++++++++++++++++--------- vm/src/vm/context.rs | 1 + 5 files changed, 116 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 597eab2a8a..954229edeb 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1906,8 +1906,6 @@ def new_method(self): c = Alias(10, 1.0) self.assertEqual(c.new_method(), 1.0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_dynamic(self): T = TypeVar('T') diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 633893fec6..46852473a6 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -173,8 +173,6 @@ def test_exposed_type(self): self.assertEqual(a.__args__, (int,)) self.assertEqual(a.__parameters__, ()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_parameters(self): from typing import List, Dict, Callable D0 = dict[str, int] @@ -214,8 +212,6 @@ def test_parameters(self): self.assertEqual(L5.__args__, (Callable[[K, V], K],)) self.assertEqual(L5.__parameters__, (K, V)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_parameter_chaining(self): from typing import List, Dict, Union, Callable self.assertEqual(list[T][int], list[int]) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 08b799fc75..59dc9814fb 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -825,8 +825,6 @@ def check(arg, expected): check(x | None, (x, type(None))) check(None | x, (type(None), x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_parameter_chaining(self): T = typing.TypeVar("T") S = typing.TypeVar("S") diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index 5ace9c4996..2e446d0aba 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -212,34 +212,49 @@ impl PyGenericAlias { } } -pub(crate) fn is_typevar(obj: &PyObjectRef, vm: &VirtualMachine) -> bool { - let class = obj.class(); - "TypeVar" == &*class.slot_name() - && class - .get_attr(identifier!(vm, __module__)) - .and_then(|o| o.downcast_ref::().map(|s| s.as_str() == "typing")) - .unwrap_or(false) -} - pub(crate) fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyTupleRef { let mut parameters: Vec = Vec::with_capacity(args.len()); + let mut iparam = 0; + for arg in args { - if is_typevar(arg, vm) { - if !parameters.iter().any(|param| param.is(arg)) { - parameters.push(arg.clone()); + // We don't want __parameters__ descriptor of a bare Python class. + if arg.class().is(vm.ctx.types.type_type) { + continue; + } + + // Check for __typing_subst__ attribute (like CPython) + if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() { + // Use tuple_add equivalent logic + if tuple_index_vec(¶meters, arg).is_none() { + if iparam >= parameters.len() { + parameters.resize(iparam + 1, vm.ctx.none()); + } + parameters[iparam] = arg.clone(); + iparam += 1; } - } else if let Ok(obj) = arg.get_attr(identifier!(vm, __parameters__), vm) { - if let Ok(sub_params) = obj.try_to_ref::(vm) { + } else if let Ok(subparams) = arg.get_attr(identifier!(vm, __parameters__), vm) { + if let Ok(sub_params) = subparams.try_to_ref::(vm) { + let len2 = sub_params.len(); + // Resize if needed + if iparam + len2 > parameters.len() { + parameters.resize(iparam + len2, vm.ctx.none()); + } for sub_param in sub_params { - if !parameters.iter().any(|param| param.is(sub_param)) { - parameters.push(sub_param.clone()); + // Use tuple_add equivalent logic + if tuple_index_vec(¶meters[..iparam], sub_param).is_none() { + if iparam >= parameters.len() { + parameters.resize(iparam + 1, vm.ctx.none()); + } + parameters[iparam] = sub_param.clone(); + iparam += 1; } } } } } - parameters.shrink_to_fit(); + // Resize to actual size + parameters.truncate(iparam); PyTuple::new_ref(parameters, &vm.ctx) } @@ -248,6 +263,11 @@ fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option { tuple.iter().position(|element| element.is(item)) } +#[inline] +fn tuple_index_vec(vec: &[PyObjectRef], item: &PyObjectRef) -> Option { + vec.iter().position(|element| element.is(item)) +} + fn subs_tvars( obj: PyObjectRef, params: &PyTupleRef, @@ -261,16 +281,32 @@ fn subs_tvars( .ok() .filter(|sub_params| !sub_params.is_empty()) .map(|sub_params| { - let sub_args = sub_params - .iter() - .map(|arg| { - if let Some(idx) = tuple_index(params, arg) { - arg_items[idx].clone() - } else { - arg.clone() + let mut sub_args = Vec::new(); + + for arg in sub_params.iter() { + if let Some(idx) = tuple_index(params, arg) { + let param = ¶ms[idx]; + let substituted_arg = &arg_items[idx]; + + // Check if this is a TypeVarTuple (has tp_iter) + if param.class().slots.iter.load().is_some() + && substituted_arg.try_to_ref::(vm).is_ok() + { + // TypeVarTuple case - extend with tuple elements + if let Ok(tuple) = substituted_arg.try_to_ref::(vm) { + for elem in tuple.iter() { + sub_args.push(elem.clone()); + } + continue; + } } - }) - .collect::>(); + + sub_args.push(substituted_arg.clone()); + } else { + sub_args.push(arg.clone()); + } + } + let sub_args: PyObjectRef = PyTuple::new_ref(sub_args, &vm.ctx).into(); obj.get_item(&*sub_args, vm) }) @@ -278,6 +314,7 @@ fn subs_tvars( .unwrap_or(Ok(obj)) } +// _Py_subs_parameters pub fn subs_parameters PyResult>( repr: F, args: PyTupleRef, @@ -297,26 +334,62 @@ pub fn subs_parameters PyResult>( }; let num_items = arg_items.len(); - if num_params != num_items { - let plural = if num_items > num_params { - "many" - } else { - "few" - }; - return Err(vm.new_type_error(format!("Too {} arguments for {}", plural, repr(vm)?))); + + // Check if we need to apply default values + if num_items < num_params { + // Count how many parameters have defaults + let mut params_with_defaults = 0; + for param in parameters.iter().rev() { + if let Ok(has_default) = vm.call_method(param, "has_default", ()) { + if has_default.try_to_bool(vm)? { + params_with_defaults += 1; + } else { + break; // No more defaults from this point backwards + } + } else { + break; + } + } + + let min_required = num_params - params_with_defaults; + if num_items < min_required { + return Err(vm.new_type_error(format!( + "Too few arguments for {}; actual {}, expected at least {}", + repr(vm)?, + num_items, + min_required + ))); + } + } else if num_items > num_params { + return Err(vm.new_type_error(format!( + "Too many arguments for {}; actual {}, expected {}", + repr(vm)?, + num_items, + num_params + ))); } - let new_args = args - .iter() - .map(|arg| { - if is_typevar(arg, vm) { - let idx = tuple_index(¶meters, arg).unwrap(); - Ok(arg_items[idx].clone()) + let mut new_args = Vec::new(); + + for arg in args.iter() { + // Check for __typing_subst__ attribute directly (like CPython) + if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) { + let idx = tuple_index(¶meters, arg).unwrap(); + if idx < num_items { + // Call __typing_subst__ with the argument + let substituted = subst.call((arg_items[idx].clone(),), vm)?; + new_args.push(substituted); } else { - subs_tvars(arg.clone(), ¶meters, arg_items, vm) + // CPython doesn't support default values in this context + return Err(vm.new_type_error(format!( + "No argument provided for parameter at index {}", + idx + ))); } - }) - .collect::>>()?; + } else { + new_args.push(subs_tvars(arg.clone(), ¶meters, arg_items, vm)?); + } + } Ok(PyTuple::new_ref(new_args, &vm.ctx)) } diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index a89bc6cd74..924c528209 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -223,6 +223,7 @@ declare_const_name! { __sizeof__, __truediv__, __trunc__, + __typing_subst__, __xor__, // common names From 5fd352591cd86a4807ece9c673c214cb2457de23 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 26 Jun 2025 21:38:52 +0900 Subject: [PATCH 02/10] fixgenericalias --- Lib/test/test_typing.py | 9 +++++---- vm/src/builtins/genericalias.rs | 15 +++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c13b154ac8..7b7e6248c5 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2547,8 +2547,6 @@ def test_pickle(self): del T_pickle, P_pickle, TS_pickle # cleaning up global state - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_var_substitution(self): Callable = self.Callable fullname = f"{Callable.__module__}.Callable" @@ -2720,6 +2718,11 @@ def test_errors(self): class TypingCallableTests(BaseCallableTests, BaseTestCase): Callable = typing.Callable + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_var_substitution(self): + super().test_var_substitution() + def test_consistency(self): # bpo-42195 # Testing collections.abc.Callable's consistency with typing.Callable @@ -8792,8 +8795,6 @@ def test_basic_with_exec(self): self.assertEqual(P.__name__, 'P') self.assertIs(P.__module__, None) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T') diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index 2e446d0aba..ac227942d8 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -225,7 +225,7 @@ pub(crate) fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyTupl // Check for __typing_subst__ attribute (like CPython) if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() { // Use tuple_add equivalent logic - if tuple_index_vec(¶meters, arg).is_none() { + if tuple_index(¶meters, arg).is_none() { if iparam >= parameters.len() { parameters.resize(iparam + 1, vm.ctx.none()); } @@ -241,7 +241,7 @@ pub(crate) fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyTupl } for sub_param in sub_params { // Use tuple_add equivalent logic - if tuple_index_vec(¶meters[..iparam], sub_param).is_none() { + if tuple_index(¶meters[..iparam], sub_param).is_none() { if iparam >= parameters.len() { parameters.resize(iparam + 1, vm.ctx.none()); } @@ -259,12 +259,7 @@ pub(crate) fn make_parameters(args: &Py, vm: &VirtualMachine) -> PyTupl } #[inline] -fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option { - tuple.iter().position(|element| element.is(item)) -} - -#[inline] -fn tuple_index_vec(vec: &[PyObjectRef], item: &PyObjectRef) -> Option { +fn tuple_index(vec: &[PyObjectRef], item: &PyObjectRef) -> Option { vec.iter().position(|element| element.is(item)) } @@ -284,7 +279,7 @@ fn subs_tvars( let mut sub_args = Vec::new(); for arg in sub_params.iter() { - if let Some(idx) = tuple_index(params, arg) { + if let Some(idx) = tuple_index(params.as_slice(), arg) { let param = ¶ms[idx]; let substituted_arg = &arg_items[idx]; @@ -374,7 +369,7 @@ pub fn subs_parameters PyResult>( for arg in args.iter() { // Check for __typing_subst__ attribute directly (like CPython) if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) { - let idx = tuple_index(¶meters, arg).unwrap(); + let idx = tuple_index(parameters.as_slice(), arg).unwrap(); if idx < num_items { // Call __typing_subst__ with the argument let substituted = subst.call((arg_items[idx].clone(),), vm)?; From 6f4c9fc24c1ead3c8ec19932488da4ef84abadd0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 26 Jun 2025 23:49:02 +0900 Subject: [PATCH 03/10] fix test --- Lib/test/test_typing.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7b7e6248c5..d94c154bb8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1241,9 +1241,8 @@ def foo(**kwargs: Unpack[Movie]): ... def test_builtin_tuple(self): Ts = TypeVarTuple("Ts") - # TODO: RUSTPYTHON - # class Old(Generic[*Ts]): ... - # class New[*Ts]: ... + class Old(Generic[*Ts]): ... + class New[*Ts]: ... PartOld = Old[int, *Ts] self.assertEqual(PartOld[str].__args__, (int, str)) From 433f24e60d80d61fbf58d788fd184b9f20733760 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 26 Jun 2025 23:40:07 +0900 Subject: [PATCH 04/10] Fix attr_exceptions list --- Lib/test/test_typing.py | 29 ----------------------------- vm/src/builtins/genericalias.rs | 7 ++++++- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d94c154bb8..2fc73f8d92 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2220,8 +2220,6 @@ class B(metaclass=UnhashableMeta): ... with self.assertRaises(TypeError): hash(union3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(Union), 'typing.Union') u = Union[Employee, int] @@ -2389,8 +2387,6 @@ def test_tuple_instance_type_error(self): isinstance((0, 0), Tuple[int, int]) self.assertIsInstance((0, 0), Tuple) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(Tuple), 'typing.Tuple') self.assertEqual(repr(Tuple[()]), 'typing.Tuple[()]') @@ -2472,8 +2468,6 @@ def f(): with self.assertRaises(TypeError): isinstance(None, Callable[[], Any]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): Callable = self.Callable fullname = f'{Callable.__module__}.Callable' @@ -2717,11 +2711,6 @@ def test_errors(self): class TypingCallableTests(BaseCallableTests, BaseTestCase): Callable = typing.Callable - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_var_substitution(self): - super().test_var_substitution() - def test_consistency(self): # bpo-42195 # Testing collections.abc.Callable's consistency with typing.Callable @@ -3554,8 +3543,6 @@ def __init__(self, x: T) -> None: class GenericTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basics(self): X = SimpleMapping[str, Any] self.assertEqual(X.__parameters__, ()) @@ -3602,8 +3589,6 @@ class D(Generic[T]): pass with self.assertRaises(TypeError): D[()] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_subclass_checks(self): for typ in [list[int], List[int], tuple[int, str], Tuple[int, str], @@ -4515,8 +4500,6 @@ class A(typing.Sized, list[int]): ... (A, collections.abc.Sized, Generic, list, object), ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multiple_inheritance_with_genericalias_2(self): T = TypeVar("T") @@ -4840,8 +4823,6 @@ def test_basics(self): with self.assertRaises(TypeError): Optional[Final[int]] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(Final), 'typing.Final') cv = Final[int] @@ -6500,8 +6481,6 @@ def test_frozenset(self): def test_dict(self): self.assertIsSubclass(dict, typing.Dict) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_dict_subscribe(self): K = TypeVar('K') V = TypeVar('V') @@ -6706,8 +6685,6 @@ def test_no_async_generator_instantiation(self): with self.assertRaises(TypeError): typing.AsyncGenerator[int, int]() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_subclassing(self): class MMA(typing.MutableMapping): @@ -7736,8 +7713,6 @@ class ChildWithInlineAndOptional(Untotal, Inline): class Wrong(*bases): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_is_typeddict(self): self.assertIs(is_typeddict(Point2D), True) self.assertIs(is_typeddict(Union[str, int]), False) @@ -9270,8 +9245,6 @@ def foo(arg) -> TypeGuard[int]: ... with self.assertRaises(TypeError): TypeGuard[int, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(TypeGuard), 'typing.TypeGuard') cv = TypeGuard[int] @@ -9322,8 +9295,6 @@ def foo(arg) -> TypeIs[int]: ... with self.assertRaises(TypeError): TypeIs[int, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(TypeIs), 'typing.TypeIs') cv = TypeIs[int] diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index ac227942d8..b7e9fdccca 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -17,10 +17,15 @@ use crate::{ }; use std::fmt; -static ATTR_EXCEPTIONS: [&str; 8] = [ +// attr_exceptions +static ATTR_EXCEPTIONS: [&str; 12] = [ + "__class__", + "__bases__", "__origin__", "__args__", + "__unpacked__", "__parameters__", + "__typing_unpacked_tuple_args__", "__mro_entries__", "__reduce_ex__", // needed so we don't look up object.__reduce_ex__ "__reduce__", From eb7afdfb8caf76391fe1534f2985a7efc7b7daf4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 00:39:45 +0900 Subject: [PATCH 05/10] unmark --- Lib/test/test_functools.py | 4 ---- Lib/test/test_genericalias.py | 2 -- 2 files changed, 6 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 5c43522c9e..427165846d 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2897,8 +2897,6 @@ def _(arg: int | None): self.assertEqual(types_union(1), "types.UnionType") self.assertEqual(types_union(None), "types.UnionType") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_register_genericalias(self): @functools.singledispatch def f(arg): @@ -2918,8 +2916,6 @@ def f(arg): self.assertEqual(f(""), "default") self.assertEqual(f(b""), "default") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_register_genericalias_decorator(self): @functools.singledispatch def f(arg): diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 46852473a6..560b96f7e3 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -303,8 +303,6 @@ def test_union(self): self.assertEqual(a.__args__, (list[int], list[str])) self.assertEqual(a.__parameters__, ()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_generic(self): a = typing.Union[list[T], tuple[T, ...]] self.assertEqual(a.__args__, (list[T], tuple[T, ...])) From 6161d13a846b0601a2f1c47fa3e405aae7b4144d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 01:06:05 +0900 Subject: [PATCH 06/10] Fix init --- Lib/test/test_typing.py | 58 +-------------------------------------- vm/src/protocol/object.rs | 2 ++ vm/src/stdlib/typing.rs | 40 +++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 60 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2fc73f8d92..adbd449976 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -538,8 +538,6 @@ def test_var_substitution(self): self.assertEqual(subst(int|str), int|str) self.assertEqual(subst(Union[int, str]), Union[int, str]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad_var_substitution(self): T = TypeVar('T') bad_args = ( @@ -736,8 +734,6 @@ def test_allow_default_after_non_default_in_alias(self): a4 = Callable[*Ts, T] self.assertEqual(a4.__args__, (*Ts, T)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec_specialization(self): T = TypeVar("T") P = ParamSpec('P', default=[str, int]) @@ -785,8 +781,6 @@ class X[**Ts]: ... self.assertIs(Ts.__default__, NoDefault) self.assertFalse(Ts.has_default()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_no_default_after_non_default(self): DefaultStrT = TypeVar('DefaultStrT', default=str) T = TypeVar('T') @@ -796,8 +790,6 @@ def test_no_default_after_non_default(self): ): Test = Generic[DefaultStrT, T] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_need_more_params(self): DefaultStrT = TypeVar('DefaultStrT', default=str) T = TypeVar('T') @@ -1638,7 +1630,7 @@ class B(Generic[Unpack[Ts]]): pass def test_variadic_class_origin_is_correct(self): Ts = TypeVarTuple('Ts') - # class C(Generic[*Ts]): pass + class C(Generic[*Ts]): pass self.assertIs(C[int].__origin__, C) self.assertIs(C[T].__origin__, C) self.assertIs(C[Unpack[Ts]].__origin__, C) @@ -1766,8 +1758,6 @@ def test_callable_args_are_correct(self): self.assertEqual(s.__args__, (*Ts1, *Ts2)) self.assertEqual(u.__args__, (Unpack[Ts1], Unpack[Ts2])) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_variadic_class_with_duplicate_typevartuples_fails(self): Ts1 = TypeVarTuple('Ts1') Ts2 = TypeVarTuple('Ts2') @@ -1815,15 +1805,11 @@ def test_type_concatenation_in_tuple_argument_list_succeeds(self): Tuple[int, Unpack[Ts], str] Tuple[int, bool, Unpack[Ts], float, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_variadic_class_definition_using_packed_typevartuple_fails(self): Ts = TypeVarTuple('Ts') with self.assertRaises(TypeError): class C(Generic[Ts]): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_variadic_class_definition_using_concrete_types_fails(self): Ts = TypeVarTuple('Ts') with self.assertRaises(TypeError): @@ -3193,8 +3179,6 @@ def meth2(self): self.assertIsInstance(C(), P) self.assertIsSubclass(C, P) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_protocols_issubclass(self): T = TypeVar('T') @@ -3562,8 +3546,6 @@ def test_basics(self): T = TypeVar("T") self.assertEqual(List[list[T] | float].__parameters__, (T,)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_errors(self): T = TypeVar('T') S = TypeVar('S') @@ -3605,8 +3587,6 @@ def test_generic_subclass_checks(self): # but, not when the right arg is also a generic: self.assertRaises(TypeError, isinstance, typ, typ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_init(self): T = TypeVar('T') S = TypeVar('S') @@ -3727,8 +3707,6 @@ def __setattr__(self, key, value): # returned by the `Immutable[int]()` call self.assertIsInstance(Immutable[int](), Immutable) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_subscripted_generics_as_proxies(self): T = TypeVar('T') class C(Generic[T]): @@ -3838,8 +3816,6 @@ class C(List[int]): ... self.assertTrue(naive_list_base_check([1, 2, 3], C)) self.assertFalse(naive_list_base_check(['a', 'b'], C)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multi_subscr_base(self): T = TypeVar('T') U = TypeVar('U') @@ -4096,8 +4072,6 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self): set(results.generic_func.__type_params__) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... class T2(Tuple[T, ...]): ... @@ -4135,8 +4109,6 @@ def test_fail_with_bare_union(self): with self.assertRaises(TypeError): List[ClassVar[int]] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_fail_with_bare_generic(self): T = TypeVar('T') with self.assertRaises(TypeError): @@ -4341,8 +4313,6 @@ class D(Generic[T]): with self.assertRaises(AttributeError): d_int.foobar = 'no' - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_errors(self): with self.assertRaises(TypeError): B = SimpleMapping[XK, Any] @@ -4596,8 +4566,6 @@ def foo(x: T): foo(42) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_implicit_any(self): T = TypeVar('T') @@ -5121,8 +5089,6 @@ class NoTypeCheck_WithFunction: class ForwardRefTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basics(self): class Node(Generic[T]): @@ -5387,8 +5353,6 @@ class CF: with self.assertRaises(TypeError): get_type_hints(CF, globals()), - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_syntax_error(self): with self.assertRaises(SyntaxError): @@ -6103,8 +6067,6 @@ def with_union(x: int | list[Annotated[str, 'meta']]): ... {'x': int | list[Annotated[str, 'meta']]}, ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_type_hints_annotated_refs(self): Const = Annotated[T, "Const"] @@ -7103,8 +7065,6 @@ class B(NamedTuple): class C(NamedTuple, B): y: str - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic(self): class X(NamedTuple, Generic[T]): x: T @@ -7859,8 +7819,6 @@ class Point3D(Point2DGeneric[T], Generic[T, KT]): class Point3D(Point2DGeneric[T], Generic[KT]): c: KT - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_implicit_any_inheritance(self): class A(TypedDict, Generic[T]): a: T @@ -8142,8 +8100,6 @@ def test_no_isinstance(self): class IOTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_io(self): def stuff(a: IO) -> AnyStr: @@ -8152,8 +8108,6 @@ def stuff(a: IO) -> AnyStr: a = stuff.__annotations__['a'] self.assertEqual(a.__parameters__, (AnyStr,)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_textio(self): def stuff(a: TextIO) -> str: @@ -8162,8 +8116,6 @@ def stuff(a: TextIO) -> str: a = stuff.__annotations__['a'] self.assertEqual(a.__parameters__, ()) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_binaryio(self): def stuff(a: BinaryIO) -> bytes: @@ -8483,8 +8435,6 @@ def test_too_few_type_args(self): with self.assertRaisesRegex(TypeError, 'at least two arguments'): Annotated[int] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pickle(self): samples = [typing.Any, typing.Union[int, str], typing.Optional[str], Tuple[int, ...], @@ -8822,8 +8772,6 @@ def foo(self, *args: "P.args", **kwargs: "P.kwargs"): gth(C.foo, globals(), locals()), {"args": P.args, "kwargs": P.kwargs} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_user_generics(self): T = TypeVar("T") P = ParamSpec("P") @@ -8878,8 +8826,6 @@ class Z(Generic[P]): with self.assertRaisesRegex(TypeError, "many arguments for"): Z[P_2, bool] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_multiple_paramspecs_in_user_generics(self): P = ParamSpec("P") P2 = ParamSpec("P2") @@ -9524,8 +9470,6 @@ def test_special_attrs2(self): loaded = pickle.loads(s) self.assertIs(SpecialAttrsP, loaded) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_genericalias_dir(self): class Foo(Generic[T]): def bar(self): diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index e938a876b1..15794f694e 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -104,6 +104,8 @@ impl PyObject { self.get_attr(attr_name, vm).map(|o| !vm.is_none(&o)) } + /// Get an attribute by name. + /// `attr_name` can be a `&str`, `String`, or `PyStrRef`. pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult { let attr_name = attr_name.as_pystr(&vm.ctx); self.get_attr_inner(attr_name, vm) diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 33427387f7..131b50a222 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -14,7 +14,7 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { pub(crate) mod decl { use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyGenericAlias, PyTupleRef, PyTypeRef, pystr::AsPyStr}, + builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, protocol::PyNumberMethods, types::{AsNumber, Comparable, Constructor, PyComparisonOp, Representable}, @@ -920,6 +920,30 @@ pub(crate) mod decl { } } + /// Helper function to call typing module functions with cls as first argument + /// Similar to CPython's call_typing_args_kwargs + fn call_typing_args_kwargs( + name: &'static str, + cls: PyTypeRef, + args: FuncArgs, + vm: &VirtualMachine, + ) -> PyResult { + let typing = vm.import("typing", 0)?; + let func = typing.get_attr(name, vm)?; + + // Prepare arguments: (cls, *args) + let mut call_args = vec![cls.into()]; + call_args.extend(args.args); + + // Call with prepared args and original kwargs + let func_args = FuncArgs { + args: call_args, + kwargs: args.kwargs, + }; + + func.call(func_args, vm) + } + #[pyattr] #[pyclass(name)] #[derive(Debug, PyPayload)] @@ -930,8 +954,18 @@ pub(crate) mod decl { #[pyclass(flags(BASETYPE))] impl Generic { #[pyclassmethod(magic)] - fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { - PyGenericAlias::new(cls, args, vm) + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Convert single arg to FuncArgs + let func_args = FuncArgs { + args: vec![args], + kwargs: Default::default(), + }; + call_typing_args_kwargs("_generic_class_getitem", cls, func_args, vm) + } + + #[pyclassmethod(magic)] + fn init_subclass(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + call_typing_args_kwargs("_generic_init_subclass", cls, args, vm) } } From af2d36c1d396fd884cefef503101c30ce1abab3a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 01:24:34 +0900 Subject: [PATCH 07/10] impl TypeVarTuple iter --- Lib/test/test_typing.py | 26 -------------------------- vm/src/stdlib/typing.rs | 20 +++++++++++++++++--- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index adbd449976..0235abc5d1 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -715,8 +715,6 @@ class X(Generic[*Ts, T]): ... with self.assertRaises(TypeError): class Y(Generic[*Ts_default, T]): ... - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_allow_default_after_non_default_in_alias(self): T_default = TypeVar('T_default', default=int) T = TypeVar('T') @@ -1298,8 +1296,6 @@ def test_cannot_call_instance(self): with self.assertRaises(TypeError): Ts() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_unpacked_typevartuple_is_equal_to_itself(self): Ts = TypeVarTuple('Ts') self.assertEqual((*Ts,)[0], (*Ts,)[0]) @@ -1310,8 +1306,6 @@ def test_parameterised_tuple_is_equal_to_itself(self): # self.assertEqual(tuple[*Ts], tuple[*Ts]) self.assertEqual(Tuple[Unpack[Ts]], Tuple[Unpack[Ts]]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def tests_tuple_arg_ordering_matters(self): Ts1 = TypeVarTuple('Ts1') Ts2 = TypeVarTuple('Ts2') @@ -1673,8 +1667,6 @@ def func2(*args: '*tuple[int, str]'): pass # {'args': Unpack[CustomVariadic[int, str]]}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_tuple_args_are_correct(self): Ts = TypeVarTuple('Ts') @@ -1695,8 +1687,6 @@ def test_tuple_args_are_correct(self): self.assertEqual(tuple[*Ts, int].__args__, (*Ts, int)) self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_callable_args_are_correct(self): Ts = TypeVarTuple('Ts') Ts1 = TypeVarTuple('Ts1') @@ -1772,8 +1762,6 @@ class E(Generic[*Ts1, *Ts2, *Ts1]): pass with self.assertRaises(TypeError): class F(Generic[Unpack[Ts1], Unpack[Ts2], Unpack[Ts1]]): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_type_concatenation_in_variadic_class_argument_list_succeeds(self): Ts = TypeVarTuple('Ts') class C(Generic[Unpack[Ts]]): pass @@ -1790,8 +1778,6 @@ class C(Generic[Unpack[Ts]]): pass C[int, bool, *Ts, float, str] C[int, bool, Unpack[Ts], float, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_type_concatenation_in_tuple_argument_list_succeeds(self): Ts = TypeVarTuple('Ts') @@ -1817,8 +1803,6 @@ class F(Generic[*Ts, int]): pass with self.assertRaises(TypeError): class E(Generic[Unpack[Ts], int]): pass - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_variadic_class_with_2_typevars_accepts_2_or_more_args(self): Ts = TypeVarTuple('Ts') T1 = TypeVar('T1') @@ -2022,8 +2006,6 @@ def test_pickling_then_unpickling_results_in_same_identity(self, proto): global_Ts2 = pickle.loads(pickle.dumps(global_Ts1, proto)) self.assertIs(global_Ts1, global_Ts2) - # TODO: RUSTPYTHON - @unittest.expectedFailure @all_pickle_protocols def test_pickling_then_unpickling_unpacked_results_in_same_identity(self, proto): global global_Ts # See explanation at start of class. @@ -2037,8 +2019,6 @@ def test_pickling_then_unpickling_unpacked_results_in_same_identity(self, proto) unpacked4 = pickle.loads(pickle.dumps(unpacked3, proto)) self.assertIs(unpacked3, unpacked4) - # TODO: RUSTPYTHON - @unittest.expectedFailure @all_pickle_protocols def test_pickling_then_unpickling_tuple_with_typevartuple_equality( self, proto @@ -2499,8 +2479,6 @@ def test_weakref(self): alias = Callable[[int, str], float] self.assertEqual(weakref.ref(alias)(), alias) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pickle(self): global T_pickle, P_pickle, TS_pickle # needed for pickling Callable = self.Callable @@ -4009,8 +3987,6 @@ def test_pep695_generic_class_with_future_annotations_name_clash_with_global_var set(ann_module695.C.__type_params__) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep_695_generic_function_with_future_annotations(self): hints_for_generic_function = get_type_hints(ann_module695.generic_function) func_t_params = ann_module695.generic_function.__type_params__ @@ -6212,8 +6188,6 @@ def h(x: collections.abc.Callable[P, int]): ... class GetUtilitiesTestCase(TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_origin(self): T = TypeVar('T') Ts = TypeVarTuple('Ts') diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 131b50a222..58935b48b4 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -13,11 +13,11 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { #[pymodule(name = "_typing")] pub(crate) mod decl { use crate::{ - AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, protocol::PyNumberMethods, - types::{AsNumber, Comparable, Constructor, PyComparisonOp, Representable}, + types::{AsNumber, Comparable, Constructor, Iterable, PyComparisonOp, Representable}, }; pub(crate) fn _call_typing_func_object<'a>( @@ -607,7 +607,7 @@ pub(crate) mod decl { default_value: parking_lot::Mutex, evaluate_default: PyObjectRef, } - #[pyclass(flags(HAS_DICT), with(Constructor, Representable))] + #[pyclass(flags(HAS_DICT), with(Constructor, Representable, Iterable))] impl TypeVarTuple { #[pygetset(magic)] fn name(&self) -> PyObjectRef { @@ -667,6 +667,20 @@ pub(crate) mod decl { } } + impl Iterable for TypeVarTuple { + fn iter(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // When unpacking TypeVarTuple with *, return [Unpack[self]] + // This is how CPython handles Generic[*Ts] + let typing = vm.import("typing", 0)?; + let unpack = typing.get_attr("Unpack", vm)?; + let zelf_obj: PyObjectRef = zelf.into(); + let unpacked = vm.call_method(&unpack, "__getitem__", (zelf_obj,))?; + let list = vm.ctx.new_list(vec![unpacked]); + let list_obj: PyObjectRef = list.into(); + vm.call_method(&list_obj, "__iter__", ()) + } + } + impl Constructor for TypeVarTuple { type Args = FuncArgs; From d16062fd75cab94b0bac67d597e9561b1d214e89 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 01:35:15 +0900 Subject: [PATCH 08/10] Update _collections_abc.py to Python 3.13 --- Lib/_collections_abc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 601107d2d8..aebe9c8b64 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -85,6 +85,10 @@ def _f(): pass dict_items = type({}.items()) ## misc ## mappingproxy = type(type.__dict__) +def _get_framelocalsproxy(): + return type(sys._getframe().f_locals) +framelocalsproxy = _get_framelocalsproxy() +del _get_framelocalsproxy generator = type((lambda: (yield))()) ## coroutine ## async def _coro(): pass @@ -836,6 +840,7 @@ def __eq__(self, other): __reversed__ = None Mapping.register(mappingproxy) +Mapping.register(framelocalsproxy) class MappingView(Sized): @@ -973,7 +978,7 @@ def clear(self): def update(self, other=(), /, **kwds): ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. - If E present and has a .keys() method, does: for k in E: D[k] = E[k] + If E present and has a .keys() method, does: for k in E.keys(): D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v ''' From bdee951eb88fe559db4498df9c060d9a6e8a6590 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 01:44:32 +0900 Subject: [PATCH 09/10] genericalias.__unpack__ --- Lib/test/test_exception_group.py | 2 ++ Lib/test/test_typing.py | 15 --------------- vm/src/builtins/genericalias.rs | 7 +++++++ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index 9d156a160c..d0d81490df 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -15,6 +15,8 @@ def test_exception_is_not_generic_type(self): with self.assertRaisesRegex(TypeError, 'Exception'): Exception[OSError] + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_exception_group_is_generic_type(self): E = OSError self.assertIsInstance(ExceptionGroup[E], types.GenericAlias) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 0235abc5d1..cc67258954 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2446,7 +2446,6 @@ def test_repr(self): ct3 = Callable[[str, float], list[int]] self.assertEqual(repr(ct3), f'{fullname}[[str, float], list[int]]') - @unittest.skip("TODO: RUSTPYTHON") def test_callable_with_ellipsis(self): Callable = self.Callable def foo(a: Callable[..., T]): @@ -3889,8 +3888,6 @@ def test_extended_generic_rules_repr(self): self.assertEqual(repr(Callable[[], List[T]][int]).replace('typing.', ''), 'Callable[[], List[int]]') - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_generic_forward_ref(self): def foobar(x: List[List['CC']]): ... def foobar2(x: list[list[ForwardRef('CC')]]): ... @@ -5195,8 +5192,6 @@ def test_forward_repr(self): self.assertEqual(repr(List[ForwardRef('int', module='mod')]), "typing.List[ForwardRef('int', module='mod')]") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_forward(self): def foo(a: Union['T']): @@ -5211,8 +5206,6 @@ def foo(a: tuple[ForwardRef('T')] | int): self.assertEqual(get_type_hints(foo, globals(), locals()), {'a': tuple[T] | int}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_tuple_forward(self): def foo(a: Tuple['T']): @@ -5550,8 +5543,6 @@ def test_or(self): class InternalsTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_deprecation_for_no_type_params_passed_to__evaluate(self): with self.assertWarnsRegex( DeprecationWarning, @@ -5967,8 +5958,6 @@ def test_get_type_hints_wrapped_decoratored_func(self): self.assertEqual(gth(ForRefExample.func), expects) self.assertEqual(gth(ForRefExample.nested), expects) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_type_hints_annotated(self): def foobar(x: List['X']): ... X = Annotated[int, (1, 10)] @@ -6032,8 +6021,6 @@ def barfoo4(x: BA3): ... {"x": typing.Annotated[int | float, "const"]} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_type_hints_annotated_in_union(self): # bpo-46603 def with_union(x: int | list[Annotated[str, 'meta']]): ... @@ -6172,8 +6159,6 @@ def test_get_type_hints_typeddict(self): "year": NotRequired[Annotated[int, 2000]] }) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_type_hints_collections_abc_callable(self): # https://github.com/python/cpython/issues/91621 P = ParamSpec('P') diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index b7e9fdccca..2ae5877751 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -38,6 +38,7 @@ pub struct PyGenericAlias { origin: PyTypeRef, args: PyTupleRef, parameters: PyTupleRef, + starred: bool, // for __unpacked__ attribute } impl fmt::Debug for PyGenericAlias { @@ -92,6 +93,7 @@ impl PyGenericAlias { origin, args, parameters, + starred: false, // default to false, will be set to true for Unpack[...] } } @@ -156,6 +158,11 @@ impl PyGenericAlias { self.origin.clone().into() } + #[pygetset(magic)] + fn unpacked(&self) -> bool { + self.starred + } + #[pymethod(magic)] fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let new_args = subs_parameters( From bd133fd1cac536a8fff536526975a8440e934544 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 02:29:37 +0900 Subject: [PATCH 10/10] fixup --- Lib/test/test_functools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 427165846d..32b442b0c0 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2930,8 +2930,6 @@ def f(arg): with self.assertRaisesRegex(TypeError, "Invalid first argument to "): f.register(typing.List[int] | str) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_register_genericalias_annotation(self): @functools.singledispatch def f(arg):