From a6e5e11182399500855cd783a4ab892d7854dd49 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 01:45:25 +0900 Subject: [PATCH 1/6] Add cspell --- .cspell.dict/python-more.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 0404428324..531283e6a1 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -17,6 +17,7 @@ basicsize bdfl bigcharset bignum +bivariant breakpointhook cformat chunksize From 21b1830235a5cf953b1cf62953e2c6925a05d698 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 20:12:54 +0900 Subject: [PATCH 2/6] TypeVar --- Lib/test/test_typing.py | 4 ---- vm/src/stdlib/typing.rs | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1bf1fd0da5..5fc7a2a9d0 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -464,8 +464,6 @@ def test_union_unique(self): self.assertEqual(Union[X, int].__parameters__, (X,)) self.assertIs(Union[X, int].__origin__, Union) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_or(self): X = TypeVar('X') # use a string because str doesn't implement @@ -9719,8 +9717,6 @@ def test_constructor(self): with self.assertRaises(TypeError): type(NoDefault)(1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_repr(self): self.assertEqual(repr(NoDefault), 'typing.NoDefault') diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 0564d22b2f..ee3777e822 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -16,7 +16,8 @@ pub(crate) mod _typing { AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyGenericAlias, PyTupleRef, PyTypeRef, pystr::AsPyStr}, function::{FuncArgs, IntoFuncArgs}, - types::{Constructor, Representable}, + protocol::PyNumberMethods, + types::{AsNumber, Constructor, Representable}, }; pub(crate) fn _call_typing_func_object<'a>( @@ -59,7 +60,7 @@ pub(crate) mod _typing { contravariant: bool, infer_variance: bool, } - #[pyclass(flags(HAS_DICT), with(Constructor, Representable))] + #[pyclass(flags(HAS_DICT), with(AsNumber, Constructor, Representable))] impl TypeVar { #[pymethod(magic)] fn mro_entries(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -173,6 +174,18 @@ pub(crate) mod _typing { } } + 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; @@ -419,7 +432,7 @@ pub(crate) mod _typing { #[derive(Debug, PyPayload)] pub struct NoDefault; - #[pyclass(with(Constructor), flags(BASETYPE))] + #[pyclass(with(Constructor, Representable), flags(BASETYPE))] impl NoDefault { #[pymethod(magic)] fn reduce(&self, _vm: &VirtualMachine) -> String { From 22f83549c102a0e7d8e7f2ef661b4c5ed8428fd0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 20:13:07 +0900 Subject: [PATCH 3/6] ParamSpec --- Lib/test/test_typing.py | 10 - vm/src/frame.rs | 2 +- vm/src/stdlib/typing.rs | 395 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 374 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 5fc7a2a9d0..41f4e72115 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -655,7 +655,6 @@ class X[T]: ... self.assertFalse(T.has_default()) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec(self): P = ParamSpec('P', default=(str, int)) self.assertEqual(P.__default__, (str, int)) @@ -684,7 +683,6 @@ class X[**P]: ... self.assertFalse(P.has_default()) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_typevartuple(self): Ts = TypeVarTuple('Ts', default=Unpack[Tuple[str, int]]) self.assertEqual(Ts.__default__, Unpack[Tuple[str, int]]) @@ -1287,7 +1285,6 @@ class Gen[*Ts]: ... class TypeVarTupleTests(BaseTestCase): # TODO: RUSTPYTHON - @unittest.expectedFailure def test_name(self): Ts = TypeVarTuple('Ts') self.assertEqual(Ts.__name__, 'Ts') @@ -1295,7 +1292,6 @@ def test_name(self): self.assertEqual(Ts2.__name__, 'Ts2') # TODO: RUSTPYTHON - @unittest.expectedFailure def test_module(self): Ts = TypeVarTuple('Ts') self.assertEqual(Ts.__module__, __name__) @@ -2048,7 +2044,6 @@ class TypeVarTuplePicklingTests(BaseTestCase): # statements at the start of each test. # TODO: RUSTPYTHON - @unittest.expectedFailure @all_pickle_protocols def test_pickling_then_unpickling_results_in_same_identity(self, proto): global global_Ts1 # See explanation at start of class. @@ -4276,7 +4271,6 @@ class Node(Generic[T]): ... self.assertEqual(t, deepcopy(t)) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_immutability_by_copy_and_pickle(self): # Special forms like Union, Any, etc., generic aliases to containers like List, # Mapping, etc., and type variabcles are considered immutable by copy and pickle. @@ -8799,7 +8793,6 @@ def test_cannot_subscript(self): class ParamSpecTests(BaseTestCase): # TODO: RUSTPYTHON - @unittest.expectedFailure def test_basic_plain(self): P = ParamSpec('P') self.assertEqual(P, P) @@ -9008,7 +9001,6 @@ class Y(Generic[P, T]): self.assertEqual(B.__args__, ((int, str,), Tuple[bytes, float])) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_var_substitution(self): P = ParamSpec("P") subst = P.__typing_subst__ @@ -9020,7 +9012,6 @@ def test_var_substitution(self): self.assertEqual(subst(Concatenate[int, P]), Concatenate[int, P]) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bad_var_substitution(self): T = TypeVar('T') P = ParamSpec('P') @@ -9239,7 +9230,6 @@ def test_dir(self): self.assertIn(required_item, dir_items) # TODO: RUSTPYTHON - @unittest.expectedFailure def test_valid_uses(self): P = ParamSpec('P') T = TypeVar('T') diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2ffd033546..e55a99422a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1282,7 +1282,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()) + _typing::make_typevartuple(type_var_tuple_name.clone(), vm) .into_ref(&vm.ctx) .into(); self.push_value(type_var_tuple); diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index ee3777e822..ee143ebb29 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -338,7 +338,7 @@ pub(crate) mod _typing { } #[pyattr] - #[pyclass(name = "ParamSpec")] + #[pyclass(name = "ParamSpec", module = "typing")] #[derive(Debug, PyPayload)] #[allow(dead_code)] pub(crate) struct ParamSpec { @@ -351,13 +351,31 @@ pub(crate) mod _typing { infer_variance: bool, } - #[pyclass(flags(BASETYPE))] + #[pyclass(flags(HAS_DICT), with(AsNumber, Constructor))] impl ParamSpec { #[pygetset(magic)] 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(magic)] fn bound(&self, vm: &VirtualMachine) -> PyObjectRef { if let Some(bound) = self.bound.clone() { @@ -383,16 +401,19 @@ pub(crate) mod _typing { #[pygetset(magic)] fn default(&self, vm: &VirtualMachine) -> PyResult { - if let Some(default_value) = self.default_value.clone() { - return Ok(default_value); + 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 = vm.call_method(evaluate_default.as_ref(), "__call__", ())?; + let default_value = evaluate_default.call((), vm)?; return Ok(default_value); } - // TODO: this isn't up to spec - Ok(vm.ctx.none()) + // Return NoDefault singleton + Ok(vm.ctx.typing_no_default.clone().into()) } #[pygetset] @@ -400,7 +421,6 @@ pub(crate) mod _typing { if let Some(evaluate_default) = self.evaluate_default.clone() { return evaluate_default; } - // TODO: default_value case vm.ctx.none() } @@ -410,9 +430,153 @@ pub(crate) mod _typing { } #[pymethod] - fn has_default(&self) -> PyResult { - // TODO: fix - Ok(self.evaluate_default.is_some() || self.default_value.is_some()) + 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(magic)] + 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(magic)] + fn typing_prepare_subst( + zelf: crate::PyRef, + alias: PyObjectRef, + args: PyTupleRef, + 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)".to_owned(), + )); + } + } else if args.args.len() == 1 { + args.args[0].clone() + } else { + return Err( + vm.new_type_error("ParamSpec() takes at most 1 positional argument".to_owned()) + ); + }; + + 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.".to_owned()) + ); + } + + if infer_variance && (covariant || contravariant) { + return Err(vm.new_value_error( + "Variance cannot be specified with infer_variance".to_owned(), + )); + } + + // 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 the current frame + if let Some(frame) = vm.current_frame() { + if let Ok(module_name) = frame.globals.get_item("__name__", vm) { + // Set __module__ to None for modules that start with "<" + if let Ok(name_str) = module_name.str(vm) { + if name_str.as_str().starts_with('<') { + obj_ref.set_attr("__module__", vm.ctx.none(), vm)?; + } else { + obj_ref.set_attr("__module__", module_name, vm)?; + } + } else { + obj_ref.set_attr("__module__", module_name, vm)?; + } + } + } + + Ok(obj_ref) } } @@ -461,34 +625,221 @@ pub(crate) mod _typing { } #[pyattr] - #[pyclass(name = "TypeVarTuple")] + #[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(BASETYPE))] - impl TypeVarTuple {} + #[pyclass(flags(HAS_DICT), with(Constructor))] + impl TypeVarTuple { + #[pygetset(magic)] + fn name(&self) -> PyObjectRef { + self.name.clone() + } - pub(crate) fn make_typevartuple(name: PyObjectRef) -> TypeVarTuple { - TypeVarTuple { name } + #[pygetset(magic)] + 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(magic)] + fn reduce(&self) -> PyObjectRef { + self.name.clone() + } + } + + 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)".to_owned(), + )); + } + } else if args.args.len() == 1 { + args.args[0].clone() + } else { + return Err(vm.new_type_error( + "TypeVarTuple() takes at most 1 positional argument".to_owned(), + )); + }; + + 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 the current frame + if let Some(frame) = vm.current_frame() { + if let Ok(module_name) = frame.globals.get_item("__name__", vm) { + // Set __module__ to None for modules that start with "<" + if let Ok(name_str) = module_name.str(vm) { + if name_str.as_str().starts_with('<') { + obj_ref.set_attr("__module__", vm.ctx.none(), vm)?; + } else { + obj_ref.set_attr("__module__", module_name, vm)?; + } + } else { + obj_ref.set_attr("__module__", module_name, vm)?; + } + } + } + + Ok(obj_ref) + } + } + + 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")] #[derive(Debug, PyPayload)] #[allow(dead_code)] - pub(crate) struct ParamSpecArgs {} - #[pyclass(flags(BASETYPE))] - impl ParamSpecArgs {} + pub(crate) struct ParamSpecArgs { + __origin__: PyObjectRef, + } + #[pyclass(flags(BASETYPE), with(Constructor, Representable))] + impl ParamSpecArgs { + #[pygetset(magic)] + fn origin(&self) -> PyObjectRef { + self.__origin__.clone() + } + + #[pymethod(magic)] + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Check if other has __origin__ attribute + if let Ok(other_origin) = other.get_attr("__origin__", vm) { + return Ok(self.__origin__.is(&other_origin)); + } + Ok(false) + } + } + + 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!("{}.args", name.str(vm)?)); + } + Ok(format!("{:?}.args", zelf.__origin__)) + } + } #[pyattr] #[pyclass(name = "ParamSpecKwargs")] #[derive(Debug, PyPayload)] #[allow(dead_code)] - pub(crate) struct ParamSpecKwargs {} - #[pyclass(flags(BASETYPE))] - impl ParamSpecKwargs {} + pub(crate) struct ParamSpecKwargs { + __origin__: PyObjectRef, + } + #[pyclass(flags(BASETYPE), with(Constructor, Representable))] + impl ParamSpecKwargs { + #[pygetset(magic)] + fn origin(&self) -> PyObjectRef { + self.__origin__.clone() + } + + #[pymethod(magic)] + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // Check if other has __origin__ attribute + if let Ok(other_origin) = other.get_attr("__origin__", vm) { + return Ok(self.__origin__.is(&other_origin)); + } + Ok(false) + } + } + + 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!("{}.kwargs", name.str(vm)?)); + } + Ok(format!("{:?}.kwargs", zelf.__origin__)) + } + } #[pyattr] #[pyclass(name)] From 23aa9e0015847cda1e02ac5830759918c59cd854 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 21:10:38 +0900 Subject: [PATCH 4/6] refactor --- vm/src/stdlib/typing.rs | 84 +++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index ee143ebb29..4181a1cce2 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -302,17 +302,7 @@ pub(crate) mod _typing { let obj = typevar.into_ref_with_type(vm, cls)?; let obj_ref: PyObjectRef = obj.into(); - - // Set __module__ from the current frame - if let Some(frame) = vm.current_frame() { - if let Ok(module_name) = frame.globals.get_item("__name__", vm) { - // Don't set __module__ if it's None - if !vm.is_none(&module_name) { - obj_ref.set_attr("__module__", module_name, vm)?; - } - } - } - + set_module_from_caller(&obj_ref, vm)?; Ok(obj_ref) } } @@ -559,23 +549,7 @@ pub(crate) mod _typing { let obj = paramspec.into_ref_with_type(vm, cls)?; let obj_ref: PyObjectRef = obj.into(); - - // Set __module__ from the current frame - if let Some(frame) = vm.current_frame() { - if let Ok(module_name) = frame.globals.get_item("__name__", vm) { - // Set __module__ to None for modules that start with "<" - if let Ok(name_str) = module_name.str(vm) { - if name_str.as_str().starts_with('<') { - obj_ref.set_attr("__module__", vm.ctx.none(), vm)?; - } else { - obj_ref.set_attr("__module__", module_name, vm)?; - } - } else { - obj_ref.set_attr("__module__", module_name, vm)?; - } - } - } - + set_module_from_caller(&obj_ref, vm)?; Ok(obj_ref) } } @@ -722,23 +696,7 @@ pub(crate) mod _typing { let obj = typevartuple.into_ref_with_type(vm, cls)?; let obj_ref: PyObjectRef = obj.into(); - - // Set __module__ from the current frame - if let Some(frame) = vm.current_frame() { - if let Ok(module_name) = frame.globals.get_item("__name__", vm) { - // Set __module__ to None for modules that start with "<" - if let Ok(name_str) = module_name.str(vm) { - if name_str.as_str().starts_with('<') { - obj_ref.set_attr("__module__", vm.ctx.none(), vm)?; - } else { - obj_ref.set_attr("__module__", module_name, vm)?; - } - } else { - obj_ref.set_attr("__module__", module_name, vm)?; - } - } - } - + set_module_from_caller(&obj_ref, vm)?; Ok(obj_ref) } } @@ -893,4 +851,40 @@ pub(crate) mod _typing { // &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 + 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)?; + } + Ok(()) + } } From af7f1797d26d587e2e64b139c40f24c8dcc27ba5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 21:50:19 +0900 Subject: [PATCH 5/6] TypeVarTuple repr --- vm/src/stdlib/typing.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 4181a1cce2..34f64e0563 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -607,7 +607,7 @@ pub(crate) mod _typing { default_value: parking_lot::Mutex, evaluate_default: PyObjectRef, } - #[pyclass(flags(HAS_DICT), with(Constructor))] + #[pyclass(flags(HAS_DICT), with(Constructor, Representable))] impl TypeVarTuple { #[pygetset(magic)] fn name(&self) -> PyObjectRef { @@ -701,6 +701,14 @@ pub(crate) mod _typing { } } + impl Representable for TypeVarTuple { + #[inline(always)] + fn repr_str(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { + let name = zelf.name.str(vm)?; + Ok(format!("*{}", name)) + } + } + pub(crate) fn make_typevartuple(name: PyObjectRef, vm: &VirtualMachine) -> TypeVarTuple { TypeVarTuple { name, From d6aeb8fa0a2acb5d2f29004def420f003d0f623e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 25 Jun 2025 22:36:03 +0900 Subject: [PATCH 6/6] Remove expectedFailure --- Lib/test/test_types.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 2d6f15e9d9..08b799fc75 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -742,8 +742,6 @@ def test_instancecheck_and_subclasscheck(self): self.assertTrue(issubclass(dict, x)) self.assertFalse(issubclass(list, x)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_instancecheck_and_subclasscheck_order(self): T = typing.TypeVar('T') @@ -790,8 +788,6 @@ def __subclasscheck__(cls, sub): self.assertTrue(issubclass(int, x)) self.assertRaises(ZeroDivisionError, issubclass, list, x) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_or_type_operator_with_TypeVar(self): TV = typing.TypeVar('T') assert TV | str == typing.Union[TV, str] @@ -799,8 +795,6 @@ def test_or_type_operator_with_TypeVar(self): self.assertIs((int | TV)[int], int) self.assertIs((TV | int)[int], int) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_args(self): def check(arg, expected): clear_typing_caches() @@ -893,16 +887,12 @@ def test_union_copy(self): self.assertEqual(copied.__args__, orig.__args__) self.assertEqual(copied.__parameters__, orig.__parameters__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_union_parameter_substitution_errors(self): T = typing.TypeVar("T") x = int | T with self.assertRaises(TypeError): x[int, str] - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_or_type_operator_with_forward(self): T = typing.TypeVar('T') ForwardAfter = T | 'Forward'