From f016716c3147cdfeaa477ee74eeafd11291aa767 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 1 Jul 2025 02:35:35 +0900 Subject: [PATCH 1/3] Iterable for PyGenericAlias --- Lib/test/test_typing.py | 4 ---- vm/src/builtins/genericalias.rs | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 606fa1be6eb..cd9d6b87a44 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -680,8 +680,6 @@ def test_typevartuple(self): class A(Generic[Unpack[Ts]]): ... Alias = Optional[Unpack[Ts]] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_typevartuple_specialization(self): T = TypeVar("T") Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) @@ -1049,8 +1047,6 @@ class C(Generic[T1, T2]): pass eval(expected_str) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_three_parameters(self): T1 = TypeVar('T1') T2 = TypeVar('T2') diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index bf10dcdc24e..89eab2d202a 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -12,8 +12,8 @@ use crate::{ function::{FuncArgs, PyComparisonValue}, protocol::{PyMappingMethods, PyNumberMethods}, types::{ - AsMapping, AsNumber, Callable, Comparable, Constructor, GetAttr, Hashable, PyComparisonOp, - Representable, + AsMapping, AsNumber, Callable, Comparable, Constructor, GetAttr, Hashable, Iterable, + PyComparisonOp, Representable, }, }; use std::fmt; @@ -78,6 +78,7 @@ impl Constructor for PyGenericAlias { Constructor, GetAttr, Hashable, + Iterable, Representable ), flags(BASETYPE) @@ -490,6 +491,13 @@ impl Representable for PyGenericAlias { } } +impl Iterable for PyGenericAlias { + fn iter(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + // Return an iterator over the args tuple + Ok(zelf.args.clone().to_pyobject(vm).get_iter(vm)?.into()) + } +} + pub fn init(context: &Context) { let generic_alias_type = &context.types.generic_alias_type; PyGenericAlias::extend_class(context, generic_alias_type); From 685e4132a24ef0817d7869ae81f07d220b4f0340 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 1 Jul 2025 02:35:27 +0900 Subject: [PATCH 2/3] GenericAlias works --- Lib/test/test_typing.py | 31 ++------ vm/src/builtins/genericalias.rs | 133 +++++++++++++++++++++++++------- vm/src/builtins/union.rs | 11 +-- vm/src/stdlib/typing.rs | 51 +++++++++++- vm/src/vm/context.rs | 2 + 5 files changed, 166 insertions(+), 62 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index cd9d6b87a44..e390f2440b0 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -689,8 +689,6 @@ class A(Generic[T, Unpack[Ts]]): ... self.assertEqual(A[float, range].__args__, (float, range)) self.assertEqual(A[float, *tuple[int, ...]].__args__, (float, *tuple[int, ...])) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_typevar_and_typevartuple_specialization(self): T = TypeVar("T") U = TypeVar("U", default=float) @@ -738,8 +736,6 @@ class A(Generic[T, P]): ... self.assertEqual(A[float].__args__, (float, (str, int))) self.assertEqual(A[float, [range]].__args__, (float, (range,))) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_typevar_and_paramspec_specialization(self): T = TypeVar("T") U = TypeVar("U", default=float) @@ -750,8 +746,6 @@ class A(Generic[T, U, P]): ... self.assertEqual(A[float, int].__args__, (float, int, (str, int))) self.assertEqual(A[float, int, [range]].__args__, (float, int, (range,))) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec_and_typevar_specialization(self): T = TypeVar("T") P = ParamSpec('P', default=[str, int]) @@ -2539,8 +2533,6 @@ def __call__(self): self.assertIs(a().__class__, C1) self.assertEqual(a().__orig_class__, C1[[int], T]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec(self): Callable = self.Callable fullname = f"{Callable.__module__}.Callable" @@ -2575,8 +2567,6 @@ def test_paramspec(self): self.assertEqual(repr(C2), f"{fullname}[~P, int]") self.assertEqual(repr(C2[int, str]), f"{fullname}[[int, str], int]") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_concatenate(self): Callable = self.Callable fullname = f"{Callable.__module__}.Callable" @@ -2604,8 +2594,6 @@ def test_concatenate(self): Callable[Concatenate[int, str, P2], int]) self.assertEqual(C[...], Callable[Concatenate[int, ...], int]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_nested_paramspec(self): # Since Callable has some special treatment, we want to be sure # that substituion works correctly, see gh-103054 @@ -2648,8 +2636,6 @@ class My(Generic[P, T]): self.assertEqual(C4[bool, bytes, float], My[[Callable[[int, bool, bytes, str], float], float], float]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_errors(self): Callable = self.Callable alias = Callable[[int, str], float] @@ -2678,6 +2664,11 @@ def test_consistency(self): class CollectionsCallableTests(BaseCallableTests, BaseTestCase): Callable = collections.abc.Callable + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_errors(self): + super().test_errors() + class LiteralTests(BaseTestCase): def test_basics(self): @@ -4627,8 +4618,6 @@ class Base(Generic[T_co]): class Sub(Base, Generic[T]): ... - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_parameter_detection(self): self.assertEqual(List[T].__parameters__, (T,)) self.assertEqual(List[List[T]].__parameters__, (T,)) @@ -4646,8 +4635,6 @@ class A: # C version of GenericAlias self.assertEqual(list[A()].__parameters__, (T,)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_non_generic_subscript(self): T = TypeVar('T') class G(Generic[T]): @@ -8854,8 +8841,6 @@ def test_bad_var_substitution(self): with self.assertRaises(TypeError): collections.abc.Callable[P, T][arg, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_type_var_subst_for_other_type_vars(self): T = TypeVar('T') T2 = TypeVar('T2') @@ -8977,8 +8962,6 @@ class PandT(Generic[P, T]): self.assertEqual(C3.__args__, ((int, *Ts), T)) self.assertEqual(C3[str, bool, bytes], PandT[[int, str, bool], bytes]) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec_in_nested_generics(self): # Although ParamSpec should not be found in __parameters__ of most # generics, they probably should be found when nested in @@ -8997,8 +8980,6 @@ def test_paramspec_in_nested_generics(self): self.assertEqual(G2[[int, str], float], list[C]) self.assertEqual(G3[[int, str], float], list[C] | int) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec_gets_copied(self): # bpo-46581 P = ParamSpec('P') @@ -9086,8 +9067,6 @@ def test_invalid_uses(self): ): Concatenate[int] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_var_substitution(self): T = TypeVar('T') P = ParamSpec('P') diff --git a/vm/src/builtins/genericalias.rs b/vm/src/builtins/genericalias.rs index 89eab2d202a..779bf125503 100644 --- a/vm/src/builtins/genericalias.rs +++ b/vm/src/builtins/genericalias.rs @@ -167,17 +167,17 @@ impl PyGenericAlias { } #[pymethod] - fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn __getitem__(zelf: PyRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let new_args = subs_parameters( - |vm| self.repr(vm), - self.args.clone(), - self.parameters.clone(), + zelf.to_owned().into(), + zelf.args.clone(), + zelf.parameters.clone(), needle, vm, )?; Ok( - PyGenericAlias::new(self.origin.clone(), new_args.to_pyobject(vm), vm) + PyGenericAlias::new(zelf.origin.clone(), new_args.to_pyobject(vm), vm) .into_pyobject(vm), ) } @@ -278,6 +278,18 @@ fn tuple_index(vec: &[PyObjectRef], item: &PyObjectRef) -> Option { vec.iter().position(|element| element.is(item)) } +fn is_unpacked_typevartuple(arg: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + if arg.class().is(vm.ctx.types.type_type) { + return Ok(false); + } + + if let Ok(attr) = arg.get_attr(identifier!(vm, __typing_is_unpacked_typevartuple__), vm) { + attr.try_to_bool(vm) + } else { + Ok(false) + } +} + fn subs_tvars( obj: PyObjectRef, params: &PyTupleRef, @@ -325,8 +337,8 @@ fn subs_tvars( } // _Py_subs_parameters -pub fn subs_parameters PyResult>( - repr: F, +pub fn subs_parameters( + alias: PyObjectRef, // The GenericAlias object itself args: PyTupleRef, parameters: PyTupleRef, needle: PyObjectRef, @@ -334,13 +346,31 @@ pub fn subs_parameters PyResult>( ) -> PyResult { let num_params = parameters.len(); if num_params == 0 { - return Err(vm.new_type_error(format!("There are no type variables left in {}", repr(vm)?))); + return Err(vm.new_type_error(format!("{} is not a generic class", alias.repr(vm)?))); + } + + // Handle __typing_prepare_subst__ for each parameter + // Following CPython: each prepare function transforms the args + let mut prepared_args = needle.clone(); + + // Ensure args is a tuple + if prepared_args.try_to_ref::(vm).is_err() { + prepared_args = PyTuple::new_ref(vec![prepared_args], &vm.ctx).into(); } - let items = needle.try_to_ref::(vm); + for param in parameters.iter() { + if let Ok(prepare) = param.get_attr(identifier!(vm, __typing_prepare_subst__), vm) { + if !prepare.is(&vm.ctx.none) { + // Call prepare(cls, args) where cls is the GenericAlias + prepared_args = prepare.call((alias.clone(), prepared_args), vm)?; + } + } + } + + let items = prepared_args.try_to_ref::(vm); let arg_items = match items { Ok(tuple) => tuple.as_slice(), - Err(_) => std::slice::from_ref(&needle), + Err(_) => std::slice::from_ref(&prepared_args), }; let num_items = arg_items.len(); @@ -363,40 +393,82 @@ pub fn subs_parameters PyResult>( let min_required = num_params - params_with_defaults; if num_items < min_required { + let repr_str = alias.repr(vm)?; return Err(vm.new_type_error(format!( - "Too few arguments for {}; actual {}, expected at least {}", - repr(vm)?, - num_items, - min_required + "Too few arguments for {repr_str}; actual {num_items}, expected at least {min_required}" ))); } } else if num_items > num_params { + let repr_str = alias.repr(vm)?; return Err(vm.new_type_error(format!( - "Too many arguments for {}; actual {}, expected {}", - repr(vm)?, - num_items, - num_params + "Too many arguments for {repr_str}; actual {num_items}, expected {num_params}" ))); } - let mut new_args = Vec::new(); + let mut new_args = Vec::with_capacity(args.len()); for arg in args.iter() { + // Skip bare Python classes + if arg.class().is(vm.ctx.types.type_type) { + new_args.push(arg.clone()); + continue; + } + + // Check if this is an unpacked TypeVarTuple + let unpack = is_unpacked_typevartuple(arg, vm)?; + // Check for __typing_subst__ attribute directly (like CPython) if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm) { - 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)?; - new_args.push(substituted); + if let Some(idx) = tuple_index(parameters.as_slice(), arg) { + if idx < num_items { + // Call __typing_subst__ with the argument + let substituted = subst.call((arg_items[idx].clone(),), vm)?; + + if unpack { + // Unpack the tuple if it's a TypeVarTuple + if let Ok(tuple) = substituted.try_to_ref::(vm) { + for elem in tuple.iter() { + new_args.push(elem.clone()); + } + } else { + new_args.push(substituted); + } + } else { + new_args.push(substituted); + } + } else { + // Use default value if available + if let Ok(default_val) = vm.call_method(arg, "__default__", ()) { + if !default_val.is(&vm.ctx.typing_no_default) { + new_args.push(default_val); + } else { + return Err(vm.new_type_error(format!( + "No argument provided for parameter at index {idx}" + ))); + } + } else { + return Err(vm.new_type_error(format!( + "No argument provided for parameter at index {idx}" + ))); + } + } } else { - // CPython doesn't support default values in this context - return Err( - vm.new_type_error(format!("No argument provided for parameter at index {idx}")) - ); + new_args.push(arg.clone()); } } else { - new_args.push(subs_tvars(arg.clone(), ¶meters, arg_items, vm)?); + let subst_arg = subs_tvars(arg.clone(), ¶meters, arg_items, vm)?; + if unpack { + // Unpack the tuple if it's a TypeVarTuple + if let Ok(tuple) = subst_arg.try_to_ref::(vm) { + for elem in tuple.iter() { + new_args.push(elem.clone()); + } + } else { + new_args.push(subst_arg); + } + } else { + new_args.push(subst_arg); + } } } @@ -407,7 +479,8 @@ impl AsMapping for PyGenericAlias { fn as_mapping() -> &'static PyMappingMethods { static AS_MAPPING: LazyLock = LazyLock::new(|| PyMappingMethods { subscript: atomic_func!(|mapping, needle, vm| { - PyGenericAlias::mapping_downcast(mapping).__getitem__(needle.to_owned(), vm) + let zelf = PyGenericAlias::mapping_downcast(mapping); + PyGenericAlias::__getitem__(zelf.to_owned(), needle.to_owned(), vm) }), ..PyMappingMethods::NOT_IMPLEMENTED }); diff --git a/vm/src/builtins/union.rs b/vm/src/builtins/union.rs index b8e34a7268f..f1662901a5b 100644 --- a/vm/src/builtins/union.rs +++ b/vm/src/builtins/union.rs @@ -206,11 +206,11 @@ pub fn make_union(args: &Py, vm: &VirtualMachine) -> PyObjectRef { } impl PyUnion { - fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn getitem(zelf: PyRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let new_args = genericalias::subs_parameters( - |vm| self.repr(vm), - self.args.clone(), - self.parameters.clone(), + zelf.to_owned().into(), + zelf.args.clone(), + zelf.parameters.clone(), needle, vm, )?; @@ -232,7 +232,8 @@ impl AsMapping for PyUnion { fn as_mapping() -> &'static PyMappingMethods { static AS_MAPPING: LazyLock = LazyLock::new(|| PyMappingMethods { subscript: atomic_func!(|mapping, needle, vm| { - PyUnion::mapping_downcast(mapping).getitem(needle.to_owned(), vm) + let zelf = PyUnion::mapping_downcast(mapping); + PyUnion::getitem(zelf.to_owned(), needle.to_owned(), vm) }), ..PyMappingMethods::NOT_IMPLEMENTED }); diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 5bbae8ae9f9..68f2d3e09e5 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -168,6 +168,55 @@ pub(crate) mod decl { // Check if default_value is not NoDefault !default_value.is(&vm.ctx.typing_no_default) } + + #[pymethod] + fn __typing_prepare_subst__( + zelf: crate::PyRef, + alias: PyObjectRef, + args: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // Convert args to tuple if needed + let args_tuple = + if let Ok(tuple) = args.try_to_ref::(vm) { + tuple + } else { + return Ok(args); + }; + + // Get alias.__parameters__ + let parameters = alias.get_attr(identifier!(vm, __parameters__), vm)?; + let params_tuple: PyTupleRef = parameters.try_into_value(vm)?; + + // Find our index in parameters + let self_obj: PyObjectRef = zelf.to_owned().into(); + let param_index = params_tuple.iter().position(|p| p.is(&self_obj)); + + if let Some(index) = param_index { + // Check if we have enough arguments + if args_tuple.len() <= index && zelf.has_default(vm) { + // Need to add default value + let mut new_args: Vec = args_tuple.iter().cloned().collect(); + + // Add default value at the correct position + while new_args.len() <= index { + // For the current parameter, add its default + if new_args.len() == index { + let default_val = zelf.__default__(vm)?; + new_args.push(default_val); + } else { + // This shouldn't happen in well-formed code + break; + } + } + + return Ok(rustpython_vm::builtins::PyTuple::new_ref(new_args, &vm.ctx).into()); + } + } + + // No changes needed + Ok(args) + } } impl Representable for TypeVar { @@ -456,7 +505,7 @@ pub(crate) mod decl { fn __typing_prepare_subst__( zelf: crate::PyRef, alias: PyObjectRef, - args: PyTupleRef, + args: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { let self_obj: PyObjectRef = zelf.into(); diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index 78a5b0c3d9d..1b0830d3503 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -224,6 +224,8 @@ declare_const_name! { __truediv__, __trunc__, __typing_subst__, + __typing_is_unpacked_typevartuple__, + __typing_prepare_subst__, __xor__, // common names From 021b8ebfdb819faa2ca623d179fedc12c12d6a11 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 1 Jul 2025 03:25:44 +0900 Subject: [PATCH 3/3] typevar.rs --- vm/src/frame.rs | 10 +- vm/src/stdlib/mod.rs | 1 + vm/src/stdlib/typevar.rs | 950 ++++++++++++++++++++++++++++++++++++++ vm/src/stdlib/typing.rs | 955 +-------------------------------------- vm/src/vm/context.rs | 1 + 5 files changed, 975 insertions(+), 942 deletions(-) create mode 100644 vm/src/stdlib/typevar.rs diff --git a/vm/src/frame.rs b/vm/src/frame.rs index afea6b30103..41d80b454f4 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1241,7 +1241,7 @@ impl ExecutingFrame<'_> { bytecode::Instruction::TypeVar => { let type_name = self.pop_value(); let type_var: PyObjectRef = - typing::make_typevar(vm, type_name.clone(), vm.ctx.none(), vm.ctx.none()) + typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), vm.ctx.none()) .into_ref(&vm.ctx) .into(); self.push_value(type_var); @@ -1251,7 +1251,7 @@ impl ExecutingFrame<'_> { let type_name = self.pop_value(); let bound = self.pop_value(); let type_var: PyObjectRef = - typing::make_typevar(vm, type_name.clone(), bound, vm.ctx.none()) + typing::TypeVar::new(vm, type_name.clone(), bound, vm.ctx.none()) .into_ref(&vm.ctx) .into(); self.push_value(type_var); @@ -1261,7 +1261,7 @@ impl ExecutingFrame<'_> { let type_name = self.pop_value(); let constraint = self.pop_value(); let type_var: PyObjectRef = - typing::make_typevar(vm, type_name.clone(), vm.ctx.none(), constraint) + typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), constraint) .into_ref(&vm.ctx) .into(); self.push_value(type_var); @@ -1288,7 +1288,7 @@ impl ExecutingFrame<'_> { } bytecode::Instruction::ParamSpec => { let param_spec_name = self.pop_value(); - let param_spec: PyObjectRef = typing::make_paramspec(param_spec_name.clone()) + let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone()) .into_ref(&vm.ctx) .into(); self.push_value(param_spec); @@ -1297,7 +1297,7 @@ impl ExecutingFrame<'_> { bytecode::Instruction::TypeVarTuple => { let type_var_tuple_name = self.pop_value(); let type_var_tuple: PyObjectRef = - typing::make_typevartuple(type_var_tuple_name.clone(), vm) + typing::TypeVarTuple::new(type_var_tuple_name.clone(), vm) .into_ref(&vm.ctx) .into(); self.push_value(type_var_tuple); diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 34ee564044f..c2f17fd00b0 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -22,6 +22,7 @@ mod sysconfigdata; #[cfg(feature = "threading")] pub mod thread; pub mod time; +mod typevar; pub mod typing; pub mod warnings; mod weakref; diff --git a/vm/src/stdlib/typevar.rs b/vm/src/stdlib/typevar.rs new file mode 100644 index 00000000000..9b225b07936 --- /dev/null +++ b/vm/src/stdlib/typevar.rs @@ -0,0 +1,950 @@ +// cspell:ignore typevarobject funcobj +use crate::{ + AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, + function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, + protocol::PyNumberMethods, + types::{AsNumber, Comparable, Constructor, Iterable, PyComparisonOp, Representable}, +}; + +pub(crate) fn _call_typing_func_object<'a>( + vm: &VirtualMachine, + func_name: impl AsPyStr<'a>, + args: impl IntoFuncArgs, +) -> PyResult { + let module = vm.import("typing", 0)?; + let func = module.get_attr(func_name.as_pystr(&vm.ctx), vm)?; + func.call(args, vm) +} + +fn type_check(arg: PyObjectRef, msg: &str, vm: &VirtualMachine) -> PyResult { + // Calling typing.py here leads to bootstrapping problems + if vm.is_none(&arg) { + return Ok(arg.class().to_owned().into()); + } + let message_str: PyObjectRef = vm.ctx.new_str(msg).into(); + _call_typing_func_object(vm, "_type_check", (arg, message_str)) +} + +/// Get the module of the caller frame, similar to CPython's caller() function. +/// Returns the module name or None if not found. +/// +/// Note: CPython's implementation (in typevarobject.c) gets the module from the +/// frame's function object using PyFunction_GetModule(f->f_funcobj). However, +/// RustPython's Frame doesn't store a reference to the function object, so we +/// get the module name from the frame's globals dictionary instead. +fn caller(vm: &VirtualMachine) -> Option { + let frame = vm.current_frame()?; + + // In RustPython, we get the module name from frame's globals + // This is similar to CPython's sys._getframe().f_globals.get('__name__') + frame.globals.get_item("__name__", vm).ok() +} + +/// Set __module__ attribute for an object based on the caller's module. +/// This follows CPython's behavior for TypeVar and similar objects. +fn set_module_from_caller(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + // Note: CPython gets module from frame->f_funcobj, but RustPython's Frame + // architecture is different - we use globals['__name__'] instead + if let Some(module_name) = caller(vm) { + // Special handling for certain module names + if let Ok(name_str) = module_name.str(vm) { + let name = name_str.as_str(); + // CPython sets __module__ to None for builtins and <...> modules + // Also set to None for exec contexts (no __name__ in globals means exec) + if name == "builtins" || name.starts_with('<') { + // Don't set __module__ attribute at all (CPython behavior) + // This allows the typing module to handle it + return Ok(()); + } + } + obj.set_attr("__module__", module_name, vm)?; + } else { + // If no module name is found (e.g., in exec context), set __module__ to None + obj.set_attr("__module__", vm.ctx.none(), vm)?; + } + Ok(()) +} + +#[pyclass(name = "TypeVar", module = "typing")] +#[derive(Debug, PyPayload)] +#[allow(dead_code)] +pub struct TypeVar { + name: PyObjectRef, // TODO PyStrRef? + bound: parking_lot::Mutex, + evaluate_bound: PyObjectRef, + constraints: parking_lot::Mutex, + evaluate_constraints: PyObjectRef, + default_value: parking_lot::Mutex, + evaluate_default: PyObjectRef, + covariant: bool, + contravariant: bool, + infer_variance: bool, +} +#[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))] +impl TypeVar { + #[pymethod] + fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot subclass an instance of TypeVar")) + } + + #[pygetset] + fn __name__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pygetset] + fn __constraints__(&self, vm: &VirtualMachine) -> PyResult { + let mut constraints = self.constraints.lock(); + if !vm.is_none(&constraints) { + return Ok(constraints.clone()); + } + let r = if !vm.is_none(&self.evaluate_constraints) { + *constraints = self.evaluate_constraints.call((), vm)?; + constraints.clone() + } else { + vm.ctx.empty_tuple.clone().into() + }; + Ok(r) + } + + #[pygetset] + fn __bound__(&self, vm: &VirtualMachine) -> PyResult { + let mut bound = self.bound.lock(); + if !vm.is_none(&bound) { + return Ok(bound.clone()); + } + let r = if !vm.is_none(&self.evaluate_bound) { + *bound = self.evaluate_bound.call((), vm)?; + bound.clone() + } else { + vm.ctx.none() + }; + Ok(r) + } + + #[pygetset] + fn __covariant__(&self) -> bool { + self.covariant + } + + #[pygetset] + fn __contravariant__(&self) -> bool { + self.contravariant + } + + #[pygetset] + fn __infer_variance__(&self) -> bool { + self.infer_variance + } + + #[pygetset] + fn __default__(&self, vm: &VirtualMachine) -> PyResult { + let mut default_value = self.default_value.lock(); + // Check if default_value is NoDefault (not just None) + if !default_value.is(&vm.ctx.typing_no_default) { + return Ok(default_value.clone()); + } + if !vm.is_none(&self.evaluate_default) { + *default_value = self.evaluate_default.call((), vm)?; + Ok(default_value.clone()) + } else { + // Return NoDefault singleton + Ok(vm.ctx.typing_no_default.clone().into()) + } + } + + #[pymethod] + fn __typing_subst__( + zelf: crate::PyRef, + arg: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + _call_typing_func_object(vm, "_typevar_subst", (self_obj, arg)) + } + + #[pymethod] + fn __reduce__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pymethod] + fn has_default(&self, vm: &VirtualMachine) -> bool { + if !vm.is_none(&self.evaluate_default) { + return true; + } + let default_value = self.default_value.lock(); + // Check if default_value is not NoDefault + !default_value.is(&vm.ctx.typing_no_default) + } + + #[pymethod] + fn __typing_prepare_subst__( + zelf: crate::PyRef, + alias: PyObjectRef, + args: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // Convert args to tuple if needed + let args_tuple = if let Ok(tuple) = args.try_to_ref::(vm) + { + tuple + } else { + return Ok(args); + }; + + // Get alias.__parameters__ + let parameters = alias.get_attr(identifier!(vm, __parameters__), vm)?; + let params_tuple: PyTupleRef = parameters.try_into_value(vm)?; + + // Find our index in parameters + let self_obj: PyObjectRef = zelf.to_owned().into(); + let param_index = params_tuple.iter().position(|p| p.is(&self_obj)); + + if let Some(index) = param_index { + // Check if we have enough arguments + if args_tuple.len() <= index && zelf.has_default(vm) { + // Need to add default value + let mut new_args: Vec = args_tuple.iter().cloned().collect(); + + // Add default value at the correct position + while new_args.len() <= index { + // For the current parameter, add its default + if new_args.len() == index { + let default_val = zelf.__default__(vm)?; + new_args.push(default_val); + } else { + // This shouldn't happen in well-formed code + break; + } + } + + return Ok(rustpython_vm::builtins::PyTuple::new_ref(new_args, &vm.ctx).into()); + } + } + + // No changes needed + Ok(args) + } +} + +impl Representable for TypeVar { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + let name = zelf.name.str(vm)?; + let repr = if zelf.covariant { + format!("+{name}") + } else if zelf.contravariant { + format!("-{name}") + } else { + format!("~{name}") + }; + Ok(repr) + } +} + +impl AsNumber for TypeVar { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + or: Some(|a, b, vm| { + _call_typing_func_object(vm, "_make_union", (a.to_owned(), b.to_owned())) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + +impl Constructor for TypeVar { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let mut kwargs = args.kwargs; + // Parse arguments manually + let (name, constraints) = if args.args.is_empty() { + // Check if name is provided as keyword argument + if let Some(name) = kwargs.swap_remove("name") { + (name, vec![]) + } else { + return Err( + vm.new_type_error("TypeVar() missing required argument: 'name' (pos 1)") + ); + } + } else if args.args.len() == 1 { + (args.args[0].clone(), vec![]) + } else { + let name = args.args[0].clone(); + let constraints = args.args[1..].to_vec(); + (name, constraints) + }; + + let bound = kwargs.swap_remove("bound"); + let covariant = kwargs + .swap_remove("covariant") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let contravariant = kwargs + .swap_remove("contravariant") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let infer_variance = kwargs + .swap_remove("infer_variance") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let default = kwargs.swap_remove("default"); + + // Check for unexpected keyword arguments + if !kwargs.is_empty() { + let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); + return Err(vm.new_type_error(format!( + "TypeVar() got unexpected keyword argument(s): {}", + unexpected_keys.join(", ") + ))); + } + + // Check for invalid combinations + if covariant && contravariant { + return Err(vm.new_value_error("Bivariant type variables are not supported.")); + } + + if infer_variance && (covariant || contravariant) { + return Err(vm.new_value_error("Variance cannot be specified with infer_variance")); + } + + // Handle constraints and bound + let (constraints_obj, evaluate_constraints) = if !constraints.is_empty() { + // Check for single constraint + if constraints.len() == 1 { + return Err(vm.new_type_error("A single constraint is not allowed")); + } + if bound.is_some() { + return Err(vm.new_type_error("Constraints cannot be used with bound")); + } + let constraints_tuple = vm.ctx.new_tuple(constraints); + (constraints_tuple.clone().into(), constraints_tuple.into()) + } else { + (vm.ctx.none(), vm.ctx.none()) + }; + + // Handle bound + let (bound_obj, evaluate_bound) = if let Some(bound) = bound { + if vm.is_none(&bound) { + (vm.ctx.none(), vm.ctx.none()) + } else { + // Type check the bound + let bound = type_check(bound, "Bound must be a type.", vm)?; + (bound, vm.ctx.none()) + } + } else { + (vm.ctx.none(), vm.ctx.none()) + }; + + // Handle default value + let (default_value, evaluate_default) = if let Some(default) = default { + (default, vm.ctx.none()) + } else { + // If no default provided, use NoDefault singleton + (vm.ctx.typing_no_default.clone().into(), vm.ctx.none()) + }; + + let typevar = TypeVar { + name, + bound: parking_lot::Mutex::new(bound_obj), + evaluate_bound, + constraints: parking_lot::Mutex::new(constraints_obj), + evaluate_constraints, + default_value: parking_lot::Mutex::new(default_value), + evaluate_default, + covariant, + contravariant, + infer_variance, + }; + + let obj = typevar.into_ref_with_type(vm, cls)?; + let obj_ref: PyObjectRef = obj.into(); + set_module_from_caller(&obj_ref, vm)?; + Ok(obj_ref) + } +} + +impl TypeVar { + pub fn new( + vm: &VirtualMachine, + name: PyObjectRef, + evaluate_bound: PyObjectRef, + evaluate_constraints: PyObjectRef, + ) -> Self { + Self { + name, + bound: parking_lot::Mutex::new(vm.ctx.none()), + evaluate_bound, + constraints: parking_lot::Mutex::new(vm.ctx.none()), + evaluate_constraints, + default_value: parking_lot::Mutex::new(vm.ctx.none()), + evaluate_default: vm.ctx.none(), + covariant: false, + contravariant: false, + infer_variance: false, + } + } +} + +#[pyclass(name = "ParamSpec", module = "typing")] +#[derive(Debug, PyPayload)] +#[allow(dead_code)] +pub struct ParamSpec { + name: PyObjectRef, + bound: Option, + default_value: Option, + evaluate_default: Option, + covariant: bool, + contravariant: bool, + infer_variance: bool, +} + +#[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))] +impl ParamSpec { + #[pymethod] + fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot subclass an instance of ParamSpec")) + } + + #[pygetset] + fn __name__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pygetset] + fn args(zelf: crate::PyRef, vm: &VirtualMachine) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + let psa = ParamSpecArgs { + __origin__: self_obj, + }; + Ok(psa.into_ref(&vm.ctx).into()) + } + + #[pygetset] + fn kwargs(zelf: crate::PyRef, vm: &VirtualMachine) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + let psk = ParamSpecKwargs { + __origin__: self_obj, + }; + Ok(psk.into_ref(&vm.ctx).into()) + } + + #[pygetset] + fn __bound__(&self, vm: &VirtualMachine) -> PyObjectRef { + if let Some(bound) = self.bound.clone() { + return bound; + } + vm.ctx.none() + } + + #[pygetset] + fn __covariant__(&self) -> bool { + self.covariant + } + + #[pygetset] + fn __contravariant__(&self) -> bool { + self.contravariant + } + + #[pygetset] + fn __infer_variance__(&self) -> bool { + self.infer_variance + } + + #[pygetset] + fn __default__(&self, vm: &VirtualMachine) -> PyResult { + if let Some(ref default_value) = self.default_value { + // Check if default_value is NoDefault (not just None) + if !default_value.is(&vm.ctx.typing_no_default) { + return Ok(default_value.clone()); + } + } + // handle evaluate_default + if let Some(evaluate_default) = self.evaluate_default.clone() { + let default_value = evaluate_default.call((), vm)?; + return Ok(default_value); + } + // Return NoDefault singleton + Ok(vm.ctx.typing_no_default.clone().into()) + } + + #[pygetset] + fn evaluate_default(&self, vm: &VirtualMachine) -> PyObjectRef { + if let Some(evaluate_default) = self.evaluate_default.clone() { + return evaluate_default; + } + vm.ctx.none() + } + + #[pymethod] + fn __reduce__(&self) -> PyResult { + Ok(self.name.clone()) + } + + #[pymethod] + fn has_default(&self, vm: &VirtualMachine) -> bool { + if self.evaluate_default.is_some() { + return true; + } + if let Some(ref default_value) = self.default_value { + // Check if default_value is not NoDefault + !default_value.is(&vm.ctx.typing_no_default) + } else { + false + } + } + + #[pymethod] + fn __typing_subst__( + zelf: crate::PyRef, + arg: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + _call_typing_func_object(vm, "_paramspec_subst", (self_obj, arg)) + } + + #[pymethod] + fn __typing_prepare_subst__( + zelf: crate::PyRef, + alias: PyObjectRef, + args: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + _call_typing_func_object(vm, "_paramspec_prepare_subst", (self_obj, alias, args)) + } +} + +impl AsNumber for ParamSpec { + fn as_number() -> &'static PyNumberMethods { + static AS_NUMBER: PyNumberMethods = PyNumberMethods { + or: Some(|a, b, vm| { + _call_typing_func_object(vm, "_make_union", (a.to_owned(), b.to_owned())) + }), + ..PyNumberMethods::NOT_IMPLEMENTED + }; + &AS_NUMBER + } +} + +impl Constructor for ParamSpec { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let mut kwargs = args.kwargs; + // Parse arguments manually + let name = if args.args.is_empty() { + // Check if name is provided as keyword argument + if let Some(name) = kwargs.swap_remove("name") { + name + } else { + return Err( + vm.new_type_error("ParamSpec() missing required argument: 'name' (pos 1)") + ); + } + } else if args.args.len() == 1 { + args.args[0].clone() + } else { + return Err(vm.new_type_error("ParamSpec() takes at most 1 positional argument")); + }; + + let bound = kwargs.swap_remove("bound"); + let covariant = kwargs + .swap_remove("covariant") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let contravariant = kwargs + .swap_remove("contravariant") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let infer_variance = kwargs + .swap_remove("infer_variance") + .map(|v| v.try_to_bool(vm)) + .transpose()? + .unwrap_or(false); + let default = kwargs.swap_remove("default"); + + // Check for unexpected keyword arguments + if !kwargs.is_empty() { + let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); + return Err(vm.new_type_error(format!( + "ParamSpec() got unexpected keyword argument(s): {}", + unexpected_keys.join(", ") + ))); + } + + // Check for invalid combinations + if covariant && contravariant { + return Err(vm.new_value_error("Bivariant type variables are not supported.")); + } + + if infer_variance && (covariant || contravariant) { + return Err(vm.new_value_error("Variance cannot be specified with infer_variance")); + } + + // Handle default value + let default_value = if let Some(default) = default { + Some(default) + } else { + // If no default provided, use NoDefault singleton + Some(vm.ctx.typing_no_default.clone().into()) + }; + + let paramspec = ParamSpec { + name, + bound, + default_value, + evaluate_default: None, + covariant, + contravariant, + infer_variance, + }; + + let obj = paramspec.into_ref_with_type(vm, cls)?; + let obj_ref: PyObjectRef = obj.into(); + set_module_from_caller(&obj_ref, vm)?; + Ok(obj_ref) + } +} + +impl Representable for ParamSpec { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + let name = zelf.__name__().str(vm)?; + Ok(format!("~{name}")) + } +} + +impl ParamSpec { + pub fn new(name: PyObjectRef) -> Self { + Self { + name, + bound: None, + default_value: None, + evaluate_default: None, + covariant: false, + contravariant: false, + infer_variance: false, + } + } +} + +#[pyclass(name = "TypeVarTuple", module = "typing")] +#[derive(Debug, PyPayload)] +#[allow(dead_code)] +pub struct TypeVarTuple { + name: PyObjectRef, + default_value: parking_lot::Mutex, + evaluate_default: PyObjectRef, +} +#[pyclass(flags(HAS_DICT), with(Constructor, Representable, Iterable))] +impl TypeVarTuple { + #[pygetset] + fn __name__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pygetset] + fn __default__(&self, vm: &VirtualMachine) -> PyResult { + let mut default_value = self.default_value.lock(); + // Check if default_value is NoDefault (not just None) + if !default_value.is(&vm.ctx.typing_no_default) { + return Ok(default_value.clone()); + } + if !vm.is_none(&self.evaluate_default) { + *default_value = self.evaluate_default.call((), vm)?; + Ok(default_value.clone()) + } else { + // Return NoDefault singleton + Ok(vm.ctx.typing_no_default.clone().into()) + } + } + + #[pymethod] + fn has_default(&self, vm: &VirtualMachine) -> bool { + if !vm.is_none(&self.evaluate_default) { + return true; + } + let default_value = self.default_value.lock(); + // Check if default_value is not NoDefault + !default_value.is(&vm.ctx.typing_no_default) + } + + #[pymethod] + fn __reduce__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pymethod] + fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot subclass an instance of TypeVarTuple")) + } + + #[pymethod] + fn __typing_subst__(&self, _arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Substitution of bare TypeVarTuple is not supported")) + } + + #[pymethod] + fn __typing_prepare_subst__( + zelf: crate::PyRef, + alias: PyObjectRef, + args: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let self_obj: PyObjectRef = zelf.into(); + _call_typing_func_object(vm, "_typevartuple_prepare_subst", (self_obj, alias, args)) + } +} + +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; + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let mut kwargs = args.kwargs; + // Parse arguments manually + let name = if args.args.is_empty() { + // Check if name is provided as keyword argument + if let Some(name) = kwargs.swap_remove("name") { + name + } else { + return Err( + vm.new_type_error("TypeVarTuple() missing required argument: 'name' (pos 1)") + ); + } + } else if args.args.len() == 1 { + args.args[0].clone() + } else { + return Err(vm.new_type_error("TypeVarTuple() takes at most 1 positional argument")); + }; + + let default = kwargs.swap_remove("default"); + + // Check for unexpected keyword arguments + if !kwargs.is_empty() { + let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); + return Err(vm.new_type_error(format!( + "TypeVarTuple() got unexpected keyword argument(s): {}", + unexpected_keys.join(", ") + ))); + } + + // Handle default value + let (default_value, evaluate_default) = if let Some(default) = default { + (default, vm.ctx.none()) + } else { + // If no default provided, use NoDefault singleton + (vm.ctx.typing_no_default.clone().into(), vm.ctx.none()) + }; + + let typevartuple = TypeVarTuple { + name, + default_value: parking_lot::Mutex::new(default_value), + evaluate_default, + }; + + let obj = typevartuple.into_ref_with_type(vm, cls)?; + let obj_ref: PyObjectRef = obj.into(); + set_module_from_caller(&obj_ref, vm)?; + Ok(obj_ref) + } +} + +impl Representable for TypeVarTuple { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + let name = zelf.name.str(vm)?; + Ok(name.to_string()) + } +} + +impl TypeVarTuple { + pub fn new(name: PyObjectRef, vm: &VirtualMachine) -> Self { + Self { + name, + default_value: parking_lot::Mutex::new(vm.ctx.typing_no_default.clone().into()), + evaluate_default: vm.ctx.none(), + } + } +} + +#[pyclass(name = "ParamSpecArgs", module = "typing")] +#[derive(Debug, PyPayload)] +#[allow(dead_code)] +pub struct ParamSpecArgs { + __origin__: PyObjectRef, +} +#[pyclass(with(Constructor, Representable, Comparable))] +impl ParamSpecArgs { + #[pymethod] + fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot subclass an instance of ParamSpecArgs")) + } + + #[pygetset] + fn __origin__(&self) -> PyObjectRef { + self.__origin__.clone() + } +} + +impl Constructor for ParamSpecArgs { + type Args = (PyObjectRef,); + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let origin = args.0; + let psa = ParamSpecArgs { __origin__: origin }; + psa.into_ref_with_type(vm, cls).map(Into::into) + } +} + +impl Representable for ParamSpecArgs { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + // Check if origin is a ParamSpec + if let Ok(name) = zelf.__origin__.get_attr("__name__", vm) { + return Ok(format!("{name}.args", name = name.str(vm)?)); + } + Ok(format!("{:?}.args", zelf.__origin__)) + } +} + +impl Comparable for ParamSpecArgs { + fn cmp( + zelf: &crate::Py, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult { + fn eq( + zelf: &crate::Py, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // Check if other has __origin__ attribute + if let Ok(other_origin) = other.get_attr("__origin__", vm) { + return Ok(zelf.__origin__.is(&other_origin)); + } + Ok(false) + } + match op { + PyComparisonOp::Eq => { + if let Ok(result) = eq(zelf, other.to_owned(), vm) { + Ok(result.into()) + } else { + Ok(PyComparisonValue::NotImplemented) + } + } + PyComparisonOp::Ne => { + if let Ok(result) = eq(zelf, other.to_owned(), vm) { + Ok((!result).into()) + } else { + Ok(PyComparisonValue::NotImplemented) + } + } + _ => Ok(PyComparisonValue::NotImplemented), + } + } +} + +#[pyclass(name = "ParamSpecKwargs", module = "typing")] +#[derive(Debug, PyPayload)] +#[allow(dead_code)] +pub struct ParamSpecKwargs { + __origin__: PyObjectRef, +} +#[pyclass(with(Constructor, Representable, Comparable))] +impl ParamSpecKwargs { + #[pymethod] + fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot subclass an instance of ParamSpecKwargs")) + } + + #[pygetset] + fn __origin__(&self) -> PyObjectRef { + self.__origin__.clone() + } +} + +impl Constructor for ParamSpecKwargs { + type Args = (PyObjectRef,); + + fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { + let origin = args.0; + let psa = ParamSpecKwargs { __origin__: origin }; + psa.into_ref_with_type(vm, cls).map(Into::into) + } +} + +impl Representable for ParamSpecKwargs { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + // Check if origin is a ParamSpec + if let Ok(name) = zelf.__origin__.get_attr("__name__", vm) { + return Ok(format!("{name}.kwargs", name = name.str(vm)?)); + } + Ok(format!("{:?}.kwargs", zelf.__origin__)) + } +} + +impl Comparable for ParamSpecKwargs { + fn cmp( + zelf: &crate::Py, + other: &PyObject, + op: PyComparisonOp, + vm: &VirtualMachine, + ) -> PyResult { + fn eq( + zelf: &crate::Py, + other: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // Check if other has __origin__ attribute + if let Ok(other_origin) = other.get_attr("__origin__", vm) { + return Ok(zelf.__origin__.is(&other_origin)); + } + Ok(false) + } + match op { + PyComparisonOp::Eq => { + if let Ok(result) = eq(zelf, other.to_owned(), vm) { + Ok(result.into()) + } else { + Ok(PyComparisonValue::NotImplemented) + } + } + PyComparisonOp::Ne => { + if let Ok(result) = eq(zelf, other.to_owned(), vm) { + Ok((!result).into()) + } else { + Ok(PyComparisonValue::NotImplemented) + } + } + _ => Ok(PyComparisonValue::NotImplemented), + } + } +} diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 68f2d3e09e5..5eeca8852b1 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -1,12 +1,25 @@ // cspell:ignore typevarobject funcobj -use crate::{PyRef, VirtualMachine, stdlib::PyModule}; +use crate::{PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule}; -pub(crate) use decl::*; +pub use crate::stdlib::typevar::{ + ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple, +}; +pub use decl::*; pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = decl::make_module(vm); + TypeVar::make_class(&vm.ctx); + ParamSpec::make_class(&vm.ctx); + TypeVarTuple::make_class(&vm.ctx); + ParamSpecArgs::make_class(&vm.ctx); + ParamSpecKwargs::make_class(&vm.ctx); extend_module!(vm, &module, { "NoDefault" => vm.ctx.typing_no_default.clone(), + "TypeVar" => TypeVar::class(&vm.ctx).to_owned(), + "ParamSpec" => ParamSpec::class(&vm.ctx).to_owned(), + "TypeVarTuple" => TypeVarTuple::class(&vm.ctx).to_owned(), + "ParamSpecArgs" => ParamSpecArgs::class(&vm.ctx).to_owned(), + "ParamSpecKwargs" => ParamSpecKwargs::class(&vm.ctx).to_owned(), }); module } @@ -14,11 +27,10 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { #[pymodule(name = "_typing")] pub(crate) mod decl { use crate::{ - AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyTupleRef, PyTypeRef, pystr::AsPyStr}, - function::{FuncArgs, IntoFuncArgs, PyComparisonValue}, - protocol::PyNumberMethods, - types::{AsNumber, Comparable, Constructor, Iterable, PyComparisonOp, Representable}, + function::{FuncArgs, IntoFuncArgs}, + types::{Constructor, Representable}, }; pub(crate) fn _call_typing_func_object<'a>( @@ -31,15 +43,6 @@ pub(crate) mod decl { func.call(args, vm) } - fn type_check(arg: PyObjectRef, msg: &str, vm: &VirtualMachine) -> PyResult { - // Calling typing.py here leads to bootstrapping problems - if vm.is_none(&arg) { - return Ok(arg.class().to_owned().into()); - } - let message_str: PyObjectRef = vm.ctx.new_str(msg).into(); - _call_typing_func_object(vm, "_type_check", (arg, message_str)) - } - #[pyfunction] pub(crate) fn _idfunc(args: FuncArgs, _vm: &VirtualMachine) -> PyObjectRef { args.args[0].clone() @@ -55,578 +58,6 @@ pub(crate) mod decl { Ok(func) } - #[pyattr] - #[pyclass(name = "TypeVar", module = "typing")] - #[derive(Debug, PyPayload)] - #[allow(dead_code)] - pub(crate) struct TypeVar { - name: PyObjectRef, // TODO PyStrRef? - bound: parking_lot::Mutex, - evaluate_bound: PyObjectRef, - constraints: parking_lot::Mutex, - evaluate_constraints: PyObjectRef, - default_value: parking_lot::Mutex, - evaluate_default: PyObjectRef, - covariant: bool, - contravariant: bool, - infer_variance: bool, - } - #[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))] - impl TypeVar { - #[pymethod] - fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot subclass an instance of TypeVar")) - } - - #[pygetset] - fn __name__(&self) -> PyObjectRef { - self.name.clone() - } - - #[pygetset] - fn __constraints__(&self, vm: &VirtualMachine) -> PyResult { - let mut constraints = self.constraints.lock(); - if !vm.is_none(&constraints) { - return Ok(constraints.clone()); - } - let r = if !vm.is_none(&self.evaluate_constraints) { - *constraints = self.evaluate_constraints.call((), vm)?; - constraints.clone() - } else { - vm.ctx.empty_tuple.clone().into() - }; - Ok(r) - } - - #[pygetset] - fn __bound__(&self, vm: &VirtualMachine) -> PyResult { - let mut bound = self.bound.lock(); - if !vm.is_none(&bound) { - return Ok(bound.clone()); - } - let r = if !vm.is_none(&self.evaluate_bound) { - *bound = self.evaluate_bound.call((), vm)?; - bound.clone() - } else { - vm.ctx.none() - }; - Ok(r) - } - - #[pygetset] - fn __covariant__(&self) -> bool { - self.covariant - } - - #[pygetset] - fn __contravariant__(&self) -> bool { - self.contravariant - } - - #[pygetset] - fn __infer_variance__(&self) -> bool { - self.infer_variance - } - - #[pygetset] - fn __default__(&self, vm: &VirtualMachine) -> PyResult { - let mut default_value = self.default_value.lock(); - // Check if default_value is NoDefault (not just None) - if !default_value.is(&vm.ctx.typing_no_default) { - return Ok(default_value.clone()); - } - if !vm.is_none(&self.evaluate_default) { - *default_value = self.evaluate_default.call((), vm)?; - Ok(default_value.clone()) - } else { - // Return NoDefault singleton - Ok(vm.ctx.typing_no_default.clone().into()) - } - } - - #[pymethod] - fn __typing_subst__( - zelf: crate::PyRef, - arg: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - _call_typing_func_object(vm, "_typevar_subst", (self_obj, arg)) - } - - #[pymethod] - fn __reduce__(&self) -> PyObjectRef { - self.name.clone() - } - - #[pymethod] - fn has_default(&self, vm: &VirtualMachine) -> bool { - if !vm.is_none(&self.evaluate_default) { - return true; - } - let default_value = self.default_value.lock(); - // Check if default_value is not NoDefault - !default_value.is(&vm.ctx.typing_no_default) - } - - #[pymethod] - fn __typing_prepare_subst__( - zelf: crate::PyRef, - alias: PyObjectRef, - args: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - // Convert args to tuple if needed - let args_tuple = - if let Ok(tuple) = args.try_to_ref::(vm) { - tuple - } else { - return Ok(args); - }; - - // Get alias.__parameters__ - let parameters = alias.get_attr(identifier!(vm, __parameters__), vm)?; - let params_tuple: PyTupleRef = parameters.try_into_value(vm)?; - - // Find our index in parameters - let self_obj: PyObjectRef = zelf.to_owned().into(); - let param_index = params_tuple.iter().position(|p| p.is(&self_obj)); - - if let Some(index) = param_index { - // Check if we have enough arguments - if args_tuple.len() <= index && zelf.has_default(vm) { - // Need to add default value - let mut new_args: Vec = args_tuple.iter().cloned().collect(); - - // Add default value at the correct position - while new_args.len() <= index { - // For the current parameter, add its default - if new_args.len() == index { - let default_val = zelf.__default__(vm)?; - new_args.push(default_val); - } else { - // This shouldn't happen in well-formed code - break; - } - } - - return Ok(rustpython_vm::builtins::PyTuple::new_ref(new_args, &vm.ctx).into()); - } - } - - // No changes needed - Ok(args) - } - } - - impl Representable for TypeVar { - #[inline(always)] - fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - let name = zelf.name.str(vm)?; - let repr = if zelf.covariant { - format!("+{name}") - } else if zelf.contravariant { - format!("-{name}") - } else { - format!("~{name}") - }; - Ok(repr) - } - } - - impl AsNumber for TypeVar { - fn as_number() -> &'static PyNumberMethods { - static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| { - _call_typing_func_object(vm, "_make_union", (a.to_owned(), b.to_owned())) - }), - ..PyNumberMethods::NOT_IMPLEMENTED - }; - &AS_NUMBER - } - } - - impl Constructor for TypeVar { - type Args = FuncArgs; - - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let mut kwargs = args.kwargs; - // Parse arguments manually - let (name, constraints) = if args.args.is_empty() { - // Check if name is provided as keyword argument - if let Some(name) = kwargs.swap_remove("name") { - (name, vec![]) - } else { - return Err( - vm.new_type_error("TypeVar() missing required argument: 'name' (pos 1)") - ); - } - } else if args.args.len() == 1 { - (args.args[0].clone(), vec![]) - } else { - let name = args.args[0].clone(); - let constraints = args.args[1..].to_vec(); - (name, constraints) - }; - - let bound = kwargs.swap_remove("bound"); - let covariant = kwargs - .swap_remove("covariant") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let contravariant = kwargs - .swap_remove("contravariant") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let infer_variance = kwargs - .swap_remove("infer_variance") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let default = kwargs.swap_remove("default"); - - // Check for unexpected keyword arguments - if !kwargs.is_empty() { - let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); - return Err(vm.new_type_error(format!( - "TypeVar() got unexpected keyword argument(s): {}", - unexpected_keys.join(", ") - ))); - } - - // Check for invalid combinations - if covariant && contravariant { - return Err(vm.new_value_error("Bivariant type variables are not supported.")); - } - - if infer_variance && (covariant || contravariant) { - return Err(vm.new_value_error("Variance cannot be specified with infer_variance")); - } - - // Handle constraints and bound - let (constraints_obj, evaluate_constraints) = if !constraints.is_empty() { - // Check for single constraint - if constraints.len() == 1 { - return Err(vm.new_type_error("A single constraint is not allowed")); - } - if bound.is_some() { - return Err(vm.new_type_error("Constraints cannot be used with bound")); - } - let constraints_tuple = vm.ctx.new_tuple(constraints); - (constraints_tuple.clone().into(), constraints_tuple.into()) - } else { - (vm.ctx.none(), vm.ctx.none()) - }; - - // Handle bound - let (bound_obj, evaluate_bound) = if let Some(bound) = bound { - if vm.is_none(&bound) { - (vm.ctx.none(), vm.ctx.none()) - } else { - // Type check the bound - let bound = type_check(bound, "Bound must be a type.", vm)?; - (bound, vm.ctx.none()) - } - } else { - (vm.ctx.none(), vm.ctx.none()) - }; - - // Handle default value - let (default_value, evaluate_default) = if let Some(default) = default { - (default, vm.ctx.none()) - } else { - // If no default provided, use NoDefault singleton - (vm.ctx.typing_no_default.clone().into(), vm.ctx.none()) - }; - - let typevar = TypeVar { - name, - bound: parking_lot::Mutex::new(bound_obj), - evaluate_bound, - constraints: parking_lot::Mutex::new(constraints_obj), - evaluate_constraints, - default_value: parking_lot::Mutex::new(default_value), - evaluate_default, - covariant, - contravariant, - infer_variance, - }; - - let obj = typevar.into_ref_with_type(vm, cls)?; - let obj_ref: PyObjectRef = obj.into(); - set_module_from_caller(&obj_ref, vm)?; - Ok(obj_ref) - } - } - - pub(crate) fn make_typevar( - vm: &VirtualMachine, - name: PyObjectRef, - evaluate_bound: PyObjectRef, - evaluate_constraints: PyObjectRef, - ) -> TypeVar { - TypeVar { - name, - bound: parking_lot::Mutex::new(vm.ctx.none()), - evaluate_bound, - constraints: parking_lot::Mutex::new(vm.ctx.none()), - evaluate_constraints, - default_value: parking_lot::Mutex::new(vm.ctx.none()), - evaluate_default: vm.ctx.none(), - covariant: false, - contravariant: false, - infer_variance: false, - } - } - - #[pyattr] - #[pyclass(name = "ParamSpec", module = "typing")] - #[derive(Debug, PyPayload)] - #[allow(dead_code)] - pub(crate) struct ParamSpec { - name: PyObjectRef, - bound: Option, - default_value: Option, - evaluate_default: Option, - covariant: bool, - contravariant: bool, - infer_variance: bool, - } - - #[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))] - impl ParamSpec { - #[pymethod] - fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot subclass an instance of ParamSpec")) - } - - #[pygetset] - fn __name__(&self) -> PyObjectRef { - self.name.clone() - } - - #[pygetset] - fn args(zelf: crate::PyRef, vm: &VirtualMachine) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - let psa = ParamSpecArgs { - __origin__: self_obj, - }; - Ok(psa.into_ref(&vm.ctx).into()) - } - - #[pygetset] - fn kwargs(zelf: crate::PyRef, vm: &VirtualMachine) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - let psk = ParamSpecKwargs { - __origin__: self_obj, - }; - Ok(psk.into_ref(&vm.ctx).into()) - } - - #[pygetset] - fn __bound__(&self, vm: &VirtualMachine) -> PyObjectRef { - if let Some(bound) = self.bound.clone() { - return bound; - } - vm.ctx.none() - } - - #[pygetset] - fn __covariant__(&self) -> bool { - self.covariant - } - - #[pygetset] - fn __contravariant__(&self) -> bool { - self.contravariant - } - - #[pygetset] - fn __infer_variance__(&self) -> bool { - self.infer_variance - } - - #[pygetset] - fn __default__(&self, vm: &VirtualMachine) -> PyResult { - if let Some(ref default_value) = self.default_value { - // Check if default_value is NoDefault (not just None) - if !default_value.is(&vm.ctx.typing_no_default) { - return Ok(default_value.clone()); - } - } - // handle evaluate_default - if let Some(evaluate_default) = self.evaluate_default.clone() { - let default_value = evaluate_default.call((), vm)?; - return Ok(default_value); - } - // Return NoDefault singleton - Ok(vm.ctx.typing_no_default.clone().into()) - } - - #[pygetset] - fn evaluate_default(&self, vm: &VirtualMachine) -> PyObjectRef { - if let Some(evaluate_default) = self.evaluate_default.clone() { - return evaluate_default; - } - vm.ctx.none() - } - - #[pymethod] - fn __reduce__(&self) -> PyResult { - Ok(self.name.clone()) - } - - #[pymethod] - fn has_default(&self, vm: &VirtualMachine) -> bool { - if self.evaluate_default.is_some() { - return true; - } - if let Some(ref default_value) = self.default_value { - // Check if default_value is not NoDefault - !default_value.is(&vm.ctx.typing_no_default) - } else { - false - } - } - - #[pymethod] - fn __typing_subst__( - zelf: crate::PyRef, - arg: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - _call_typing_func_object(vm, "_paramspec_subst", (self_obj, arg)) - } - - #[pymethod] - fn __typing_prepare_subst__( - zelf: crate::PyRef, - alias: PyObjectRef, - args: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - _call_typing_func_object(vm, "_paramspec_prepare_subst", (self_obj, alias, args)) - } - } - - impl AsNumber for ParamSpec { - fn as_number() -> &'static PyNumberMethods { - static AS_NUMBER: PyNumberMethods = PyNumberMethods { - or: Some(|a, b, vm| { - _call_typing_func_object(vm, "_make_union", (a.to_owned(), b.to_owned())) - }), - ..PyNumberMethods::NOT_IMPLEMENTED - }; - &AS_NUMBER - } - } - - impl Constructor for ParamSpec { - type Args = FuncArgs; - - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let mut kwargs = args.kwargs; - // Parse arguments manually - let name = if args.args.is_empty() { - // Check if name is provided as keyword argument - if let Some(name) = kwargs.swap_remove("name") { - name - } else { - return Err( - vm.new_type_error("ParamSpec() missing required argument: 'name' (pos 1)") - ); - } - } else if args.args.len() == 1 { - args.args[0].clone() - } else { - return Err(vm.new_type_error("ParamSpec() takes at most 1 positional argument")); - }; - - let bound = kwargs.swap_remove("bound"); - let covariant = kwargs - .swap_remove("covariant") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let contravariant = kwargs - .swap_remove("contravariant") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let infer_variance = kwargs - .swap_remove("infer_variance") - .map(|v| v.try_to_bool(vm)) - .transpose()? - .unwrap_or(false); - let default = kwargs.swap_remove("default"); - - // Check for unexpected keyword arguments - if !kwargs.is_empty() { - let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); - return Err(vm.new_type_error(format!( - "ParamSpec() got unexpected keyword argument(s): {}", - unexpected_keys.join(", ") - ))); - } - - // Check for invalid combinations - if covariant && contravariant { - return Err(vm.new_value_error("Bivariant type variables are not supported.")); - } - - if infer_variance && (covariant || contravariant) { - return Err(vm.new_value_error("Variance cannot be specified with infer_variance")); - } - - // Handle default value - let default_value = if let Some(default) = default { - Some(default) - } else { - // If no default provided, use NoDefault singleton - Some(vm.ctx.typing_no_default.clone().into()) - }; - - let paramspec = ParamSpec { - name, - bound, - default_value, - evaluate_default: None, - covariant, - contravariant, - infer_variance, - }; - - let obj = paramspec.into_ref_with_type(vm, cls)?; - let obj_ref: PyObjectRef = obj.into(); - set_module_from_caller(&obj_ref, vm)?; - Ok(obj_ref) - } - } - - impl Representable for ParamSpec { - #[inline(always)] - fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - let name = zelf.__name__().str(vm)?; - Ok(format!("~{name}")) - } - } - - pub(crate) fn make_paramspec(name: PyObjectRef) -> ParamSpec { - ParamSpec { - name, - bound: None, - default_value: None, - evaluate_default: None, - covariant: false, - contravariant: false, - infer_variance: false, - } - } - #[pyclass(no_attr, name = "NoDefaultType", module = "typing")] #[derive(Debug, PyPayload)] pub struct NoDefault; @@ -659,316 +90,6 @@ pub(crate) mod decl { } } - #[pyattr] - #[pyclass(name = "TypeVarTuple", module = "typing")] - #[derive(Debug, PyPayload)] - #[allow(dead_code)] - pub(crate) struct TypeVarTuple { - name: PyObjectRef, - default_value: parking_lot::Mutex, - evaluate_default: PyObjectRef, - } - #[pyclass(flags(HAS_DICT), with(Constructor, Representable, Iterable))] - impl TypeVarTuple { - #[pygetset] - fn __name__(&self) -> PyObjectRef { - self.name.clone() - } - - #[pygetset] - fn __default__(&self, vm: &VirtualMachine) -> PyResult { - let mut default_value = self.default_value.lock(); - // Check if default_value is NoDefault (not just None) - if !default_value.is(&vm.ctx.typing_no_default) { - return Ok(default_value.clone()); - } - if !vm.is_none(&self.evaluate_default) { - *default_value = self.evaluate_default.call((), vm)?; - Ok(default_value.clone()) - } else { - // Return NoDefault singleton - Ok(vm.ctx.typing_no_default.clone().into()) - } - } - - #[pymethod] - fn has_default(&self, vm: &VirtualMachine) -> bool { - if !vm.is_none(&self.evaluate_default) { - return true; - } - let default_value = self.default_value.lock(); - // Check if default_value is not NoDefault - !default_value.is(&vm.ctx.typing_no_default) - } - - #[pymethod] - fn __reduce__(&self) -> PyObjectRef { - self.name.clone() - } - - #[pymethod] - fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot subclass an instance of TypeVarTuple")) - } - - #[pymethod] - fn __typing_subst__(&self, _arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Substitution of bare TypeVarTuple is not supported")) - } - - #[pymethod] - fn __typing_prepare_subst__( - zelf: crate::PyRef, - alias: PyObjectRef, - args: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - let self_obj: PyObjectRef = zelf.into(); - _call_typing_func_object(vm, "_typevartuple_prepare_subst", (self_obj, alias, args)) - } - } - - 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; - - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let mut kwargs = args.kwargs; - // Parse arguments manually - let name = if args.args.is_empty() { - // Check if name is provided as keyword argument - if let Some(name) = kwargs.swap_remove("name") { - name - } else { - return Err(vm.new_type_error( - "TypeVarTuple() missing required argument: 'name' (pos 1)", - )); - } - } else if args.args.len() == 1 { - args.args[0].clone() - } else { - return Err(vm.new_type_error("TypeVarTuple() takes at most 1 positional argument")); - }; - - let default = kwargs.swap_remove("default"); - - // Check for unexpected keyword arguments - if !kwargs.is_empty() { - let unexpected_keys: Vec = kwargs.keys().map(|s| s.to_string()).collect(); - return Err(vm.new_type_error(format!( - "TypeVarTuple() got unexpected keyword argument(s): {}", - unexpected_keys.join(", ") - ))); - } - - // Handle default value - let (default_value, evaluate_default) = if let Some(default) = default { - (default, vm.ctx.none()) - } else { - // If no default provided, use NoDefault singleton - (vm.ctx.typing_no_default.clone().into(), vm.ctx.none()) - }; - - let typevartuple = TypeVarTuple { - name, - default_value: parking_lot::Mutex::new(default_value), - evaluate_default, - }; - - let obj = typevartuple.into_ref_with_type(vm, cls)?; - let obj_ref: PyObjectRef = obj.into(); - set_module_from_caller(&obj_ref, vm)?; - Ok(obj_ref) - } - } - - impl Representable for TypeVarTuple { - #[inline(always)] - fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - let name = zelf.name.str(vm)?; - Ok(name.to_string()) - } - } - - pub(crate) fn make_typevartuple(name: PyObjectRef, vm: &VirtualMachine) -> TypeVarTuple { - TypeVarTuple { - name, - default_value: parking_lot::Mutex::new(vm.ctx.typing_no_default.clone().into()), - evaluate_default: vm.ctx.none(), - } - } - - #[pyattr] - #[pyclass(name = "ParamSpecArgs", module = "typing")] - #[derive(Debug, PyPayload)] - #[allow(dead_code)] - pub(crate) struct ParamSpecArgs { - __origin__: PyObjectRef, - } - #[pyclass(with(Constructor, Representable, Comparable))] - impl ParamSpecArgs { - #[pymethod] - fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot subclass an instance of ParamSpecArgs")) - } - - #[pygetset] - fn __origin__(&self) -> PyObjectRef { - self.__origin__.clone() - } - } - - impl Constructor for ParamSpecArgs { - type Args = (PyObjectRef,); - - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let origin = args.0; - let psa = ParamSpecArgs { __origin__: origin }; - psa.into_ref_with_type(vm, cls).map(Into::into) - } - } - - impl Representable for ParamSpecArgs { - #[inline(always)] - fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - // Check if origin is a ParamSpec - if let Ok(name) = zelf.__origin__.get_attr("__name__", vm) { - return Ok(format!("{name}.args", name = name.str(vm)?)); - } - Ok(format!("{:?}.args", zelf.__origin__)) - } - } - - impl Comparable for ParamSpecArgs { - fn cmp( - zelf: &crate::Py, - other: &PyObject, - op: PyComparisonOp, - vm: &VirtualMachine, - ) -> PyResult { - fn eq( - zelf: &crate::Py, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - // Check if other has __origin__ attribute - if let Ok(other_origin) = other.get_attr("__origin__", vm) { - return Ok(zelf.__origin__.is(&other_origin)); - } - Ok(false) - } - match op { - PyComparisonOp::Eq => { - if let Ok(result) = eq(zelf, other.to_owned(), vm) { - Ok(result.into()) - } else { - Ok(PyComparisonValue::NotImplemented) - } - } - PyComparisonOp::Ne => { - if let Ok(result) = eq(zelf, other.to_owned(), vm) { - Ok((!result).into()) - } else { - Ok(PyComparisonValue::NotImplemented) - } - } - _ => Ok(PyComparisonValue::NotImplemented), - } - } - } - - #[pyattr] - #[pyclass(name = "ParamSpecKwargs", module = "typing")] - #[derive(Debug, PyPayload)] - #[allow(dead_code)] - pub(crate) struct ParamSpecKwargs { - __origin__: PyObjectRef, - } - #[pyclass(with(Constructor, Representable, Comparable))] - impl ParamSpecKwargs { - #[pymethod] - fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("Cannot subclass an instance of ParamSpecKwargs")) - } - - #[pygetset] - fn __origin__(&self) -> PyObjectRef { - self.__origin__.clone() - } - } - - impl Constructor for ParamSpecKwargs { - type Args = (PyObjectRef,); - - fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult { - let origin = args.0; - let psa = ParamSpecKwargs { __origin__: origin }; - psa.into_ref_with_type(vm, cls).map(Into::into) - } - } - - impl Representable for ParamSpecKwargs { - #[inline(always)] - fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - // Check if origin is a ParamSpec - if let Ok(name) = zelf.__origin__.get_attr("__name__", vm) { - return Ok(format!("{name}.kwargs", name = name.str(vm)?)); - } - Ok(format!("{:?}.kwargs", zelf.__origin__)) - } - } - - impl Comparable for ParamSpecKwargs { - fn cmp( - zelf: &crate::Py, - other: &PyObject, - op: PyComparisonOp, - vm: &VirtualMachine, - ) -> PyResult { - fn eq( - zelf: &crate::Py, - other: PyObjectRef, - vm: &VirtualMachine, - ) -> PyResult { - // Check if other has __origin__ attribute - if let Ok(other_origin) = other.get_attr("__origin__", vm) { - return Ok(zelf.__origin__.is(&other_origin)); - } - Ok(false) - } - match op { - PyComparisonOp::Eq => { - if let Ok(result) = eq(zelf, other.to_owned(), vm) { - Ok(result.into()) - } else { - Ok(PyComparisonValue::NotImplemented) - } - } - PyComparisonOp::Ne => { - if let Ok(result) = eq(zelf, other.to_owned(), vm) { - Ok((!result).into()) - } else { - Ok(PyComparisonValue::NotImplemented) - } - } - _ => Ok(PyComparisonValue::NotImplemented), - } - } - } - #[pyattr] #[pyclass(name)] #[derive(Debug, PyPayload)] @@ -1055,44 +176,4 @@ pub(crate) mod decl { // &AS_MAPPING // } // } - - /// Get the module of the caller frame, similar to CPython's caller() function. - /// Returns the module name or None if not found. - /// - /// Note: CPython's implementation (in typevarobject.c) gets the module from the - /// frame's function object using PyFunction_GetModule(f->f_funcobj). However, - /// RustPython's Frame doesn't store a reference to the function object, so we - /// get the module name from the frame's globals dictionary instead. - fn caller(vm: &VirtualMachine) -> Option { - let frame = vm.current_frame()?; - - // In RustPython, we get the module name from frame's globals - // This is similar to CPython's sys._getframe().f_globals.get('__name__') - frame.globals.get_item("__name__", vm).ok() - } - - /// Set __module__ attribute for an object based on the caller's module. - /// This follows CPython's behavior for TypeVar and similar objects. - fn set_module_from_caller(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - // Note: CPython gets module from frame->f_funcobj, but RustPython's Frame - // architecture is different - we use globals['__name__'] instead - if let Some(module_name) = caller(vm) { - // Special handling for certain module names - if let Ok(name_str) = module_name.str(vm) { - let name = name_str.as_str(); - // CPython sets __module__ to None for builtins and <...> modules - // Also set to None for exec contexts (no __name__ in globals means exec) - if name == "builtins" || name.starts_with('<') { - // Don't set __module__ attribute at all (CPython behavior) - // This allows the typing module to handle it - return Ok(()); - } - } - obj.set_attr("__module__", module_name, vm)?; - } else { - // If no module name is found (e.g., in exec context), set __module__ to None - obj.set_attr("__module__", vm.ctx.none(), vm)?; - } - Ok(()) - } } diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index 1b0830d3503..f77abc5fa6d 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -226,6 +226,7 @@ declare_const_name! { __typing_subst__, __typing_is_unpacked_typevartuple__, __typing_prepare_subst__, + __typing_unpacked_tuple_args__, __xor__, // common names