From 7a6a0fb2998764de1d09f1524e1ce77384b96f2e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 3 Jul 2025 11:17:21 +0900 Subject: [PATCH 1/3] remove future __classs_getitem__ --- vm/src/builtins/bytearray.rs | 3 ++- vm/src/builtins/bytes.rs | 3 ++- vm/src/builtins/memory.rs | 3 ++- vm/src/builtins/range.rs | 11 ++++++----- vm/src/builtins/slice.rs | 11 ++++++----- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs index 0a13c04dc8..6b899ef81f 100644 --- a/vm/src/builtins/bytearray.rs +++ b/vm/src/builtins/bytearray.rs @@ -572,7 +572,8 @@ impl PyByteArray { self.borrow_buf_mut().reverse(); } - #[pyclassmethod] + // TODO: Uncomment when Python adds __class_getitem__ to bytearray + // #[pyclassmethod] fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) } diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs index 8045081dc7..67f910bef7 100644 --- a/vm/src/builtins/bytes.rs +++ b/vm/src/builtins/bytes.rs @@ -483,7 +483,8 @@ impl PyBytes { PyTuple::new_ref(param, &vm.ctx) } - #[pyclassmethod] + // TODO: Uncomment when Python adds __class_getitem__ to bytes + // #[pyclassmethod] fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) } diff --git a/vm/src/builtins/memory.rs b/vm/src/builtins/memory.rs index d2c77d2652..9bf22c6b35 100644 --- a/vm/src/builtins/memory.rs +++ b/vm/src/builtins/memory.rs @@ -550,7 +550,8 @@ impl Py { Representable ))] impl PyMemoryView { - #[pyclassmethod] + // TODO: Uncomment when Python adds __class_getitem__ to memoryview + // #[pyclassmethod] fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { PyGenericAlias::from_args(cls, args, vm) } diff --git a/vm/src/builtins/range.rs b/vm/src/builtins/range.rs index 3f698de654..ac9c35a081 100644 --- a/vm/src/builtins/range.rs +++ b/vm/src/builtins/range.rs @@ -184,11 +184,6 @@ pub fn init(context: &Context) { Representable ))] impl PyRange { - #[pyclassmethod] - fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { - PyGenericAlias::from_args(cls, args, vm) - } - fn new(cls: PyTypeRef, stop: ArgIndex, vm: &VirtualMachine) -> PyResult> { PyRange { start: vm.ctx.new_pyref(0), @@ -328,6 +323,12 @@ impl PyRange { Ok(range.into()) } + + // TODO: Uncomment when Python adds __class_getitem__ to range + // #[pyclassmethod] + fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } } #[pyclass] diff --git a/vm/src/builtins/slice.rs b/vm/src/builtins/slice.rs index 02e066bbac..42607f4bb2 100644 --- a/vm/src/builtins/slice.rs +++ b/vm/src/builtins/slice.rs @@ -30,11 +30,6 @@ impl PyPayload for PySlice { #[pyclass(with(Comparable, Representable, Hashable))] impl PySlice { - #[pyclassmethod] - fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { - PyGenericAlias::from_args(cls, args, vm) - } - #[pygetset] fn start(&self, vm: &VirtualMachine) -> PyObjectRef { self.start.clone().to_pyobject(vm) @@ -200,6 +195,12 @@ impl PySlice { (zelf.start.clone(), zelf.stop.clone(), zelf.step.clone()), )) } + + // TODO: Uncomment when Python adds __class_getitem__ to slice + // #[pyclassmethod] + fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::from_args(cls, args, vm) + } } impl Hashable for PySlice { From 5db7ccc022849ebb2a97506990efa515ddda6730 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 1 Jul 2025 22:38:02 +0900 Subject: [PATCH 2/3] __type_params__ in __build_class__ --- Lib/test/test_typing.py | 10 --------- vm/src/stdlib/builtins.rs | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 8dd3e4f600..f11cf6a667 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -656,8 +656,6 @@ class A(Generic[P]): ... P_default = ParamSpec('P_default', default=...) self.assertIs(P_default.__default__, ...) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_paramspec_none(self): U = ParamSpec('U') U_None = ParamSpec('U_None', default=None) @@ -756,8 +754,6 @@ class A(Generic[T, P, U]): ... self.assertEqual(A[float, [range]].__args__, (float, (range,), float)) self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_typevartuple_none(self): U = TypeVarTuple('U') U_None = TypeVarTuple('U_None', default=None) @@ -3893,8 +3889,6 @@ def f(x: X): ... {'x': list[list[ForwardRef('X')]]} ) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep695_generic_class_with_future_annotations(self): original_globals = dict(ann_module695.__dict__) @@ -3913,8 +3907,6 @@ def test_pep695_generic_class_with_future_annotations_and_local_shadowing(self): hints_for_B = get_type_hints(ann_module695.B) self.assertEqual(hints_for_B, {"x": int, "y": str, "z": bytes}) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_pep695_generic_class_with_future_annotations_name_clash_with_global_vars(self): hints_for_C = get_type_hints(ann_module695.C) self.assertEqual( @@ -6114,8 +6106,6 @@ class C(Generic[T]): pass self.assertIs(get_origin((*tuple[*Ts],)[0]), tuple) self.assertIs(get_origin(Unpack[Tuple[Unpack[Ts]]]), Unpack) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_args(self): T = TypeVar('T') class C(Generic[T]): pass diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 7d3afe04d9..917db89a3a 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -943,9 +943,52 @@ mod builtins { )?; } + // Check if we need to set __parameters__ for PEP 695 classes + let function_obj: PyObjectRef = function.clone().into(); + let has_type_params = + if let Ok(type_params) = function_obj.get_attr(identifier!(vm, __type_params__), vm) { + if let Ok(type_params_tuple) = type_params.downcast::() { + !type_params_tuple.is_empty() + } else { + false + } + } else { + false + }; + + let needs_parameters = if has_type_params { + if let Ok(bases_tuple) = bases.clone().downcast::() { + bases_tuple.iter().any(|base| { + if let Ok(base_name) = base.get_attr(identifier!(vm, __name__), vm) { + if let Ok(name_str) = base_name.downcast::() { + let name = name_str.as_str(); + return name == "Protocol" || name == "Generic"; + } + } + false + }) + } else { + false + } + } else { + false + }; + let args = FuncArgs::new(vec![name_obj.into(), bases, namespace.into()], kwargs); let class = metaclass.call(args, vm)?; + // Set __type_params__ on the class if the function has type params + if has_type_params { + if let Ok(type_params) = function_obj.get_attr(identifier!(vm, __type_params__), vm) { + class.set_attr(identifier!(vm, __type_params__), type_params.clone(), vm)?; + + if needs_parameters { + // Set __parameters__ to the same value as __type_params__ + class.set_attr(identifier!(vm, __parameters__), type_params, vm)?; + } + } + } + if let Some(ref classcell) = classcell { let classcell = classcell.get().ok_or_else(|| { vm.new_type_error(format!( From 15263f9fa4b0a8fb49aa5d55e5db21b325208a22 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 1 Jul 2025 23:58:38 +0900 Subject: [PATCH 3/3] retry --- Lib/test/test_typing.py | 2 + compiler/codegen/src/compile.rs | 25 +++++++++-- compiler/codegen/src/symboltable.rs | 1 + vm/src/stdlib/builtins.rs | 68 +++++++++++++---------------- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f11cf6a667..a4f2e897b1 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6106,6 +6106,8 @@ class C(Generic[T]): pass self.assertIs(get_origin((*tuple[*Ts],)[0]), tuple) self.assertIs(get_origin(Unpack[Tuple[Unpack[Ts]]]), Unpack) + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_get_args(self): T = TypeVar('T') class C(Generic[T]): pass diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 13b90a612c..2bdc5de393 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -1212,6 +1212,7 @@ impl Compiler<'_> { /// Store each type parameter so it is accessible to the current scope, and leave a tuple of /// all the type parameters on the stack. fn compile_type_params(&mut self, type_params: &TypeParams) -> CompileResult<()> { + // First, compile each type parameter and store it for type_param in &type_params.type_params { match type_param { TypeParam::TypeVar(TypeParamTypeVar { name, bound, .. }) => { @@ -1664,8 +1665,12 @@ impl Compiler<'_> { let qualified_name = self.qualified_path.join("."); // If there are type params, we need to push a special symbol table just for them - if type_params.is_some() { + if let Some(type_params) = type_params { self.push_symbol_table(); + // Compile type parameters and store as .type_params + self.compile_type_params(type_params)?; + let dot_type_params = self.name(".type_params"); + emit!(self, Instruction::StoreLocal(dot_type_params)); } self.push_output(bytecode::CodeFlags::empty(), 0, 0, 0, name.to_owned()); @@ -1688,6 +1693,18 @@ impl Compiler<'_> { if Self::find_ann(body) { emit!(self, Instruction::SetupAnnotation); } + + // Set __type_params__ from .type_params if we have type parameters (PEP 695) + if type_params.is_some() { + // Load .type_params from enclosing scope + let dot_type_params = self.name(".type_params"); + emit!(self, Instruction::LoadNameAny(dot_type_params)); + + // Store as __type_params__ + let dunder_type_params = self.name("__type_params__"); + emit!(self, Instruction::StoreLocal(dunder_type_params)); + } + self.compile_statements(body)?; let classcell_idx = self @@ -1721,8 +1738,10 @@ impl Compiler<'_> { let mut func_flags = bytecode::MakeFunctionFlags::empty(); // Prepare generic type parameters: - if let Some(type_params) = type_params { - self.compile_type_params(type_params)?; + if type_params.is_some() { + // Load .type_params from the type params scope + let dot_type_params = self.name(".type_params"); + emit!(self, Instruction::LoadNameAny(dot_type_params)); func_flags |= bytecode::MakeFunctionFlags::TYPE_PARAMS; } diff --git a/compiler/codegen/src/symboltable.rs b/compiler/codegen/src/symboltable.rs index 4f42b3996f..385abfd72f 100644 --- a/compiler/codegen/src/symboltable.rs +++ b/compiler/codegen/src/symboltable.rs @@ -1267,6 +1267,7 @@ impl SymbolTableBuilder<'_> { } fn scan_type_params(&mut self, type_params: &TypeParams) -> SymbolTableResult { + // First register all type parameters for type_param in &type_params.type_params { match type_param { TypeParam::TypeVar(TypeParamTypeVar { diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 917db89a3a..6988085d21 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -932,6 +932,23 @@ mod builtins { )) })?; + // For PEP 695 classes, set .type_params in namespace before calling the function + if let Ok(type_params) = function + .as_object() + .get_attr(identifier!(vm, __type_params__), vm) + { + if let Some(type_params_tuple) = type_params.downcast_ref::() { + if !type_params_tuple.is_empty() { + // Set .type_params in namespace so the compiler-generated code can use it + namespace.as_object().set_item( + vm.ctx.intern_str(".type_params"), + type_params, + vm, + )?; + } + } + } + let classcell = function.invoke_with_locals(().into(), Some(namespace.clone()), vm)?; let classcell = >::try_from_object(vm, classcell)?; @@ -943,48 +960,23 @@ mod builtins { )?; } - // Check if we need to set __parameters__ for PEP 695 classes - let function_obj: PyObjectRef = function.clone().into(); - let has_type_params = - if let Ok(type_params) = function_obj.get_attr(identifier!(vm, __type_params__), vm) { - if let Ok(type_params_tuple) = type_params.downcast::() { - !type_params_tuple.is_empty() - } else { - false - } - } else { - false - }; - - let needs_parameters = if has_type_params { - if let Ok(bases_tuple) = bases.clone().downcast::() { - bases_tuple.iter().any(|base| { - if let Ok(base_name) = base.get_attr(identifier!(vm, __name__), vm) { - if let Ok(name_str) = base_name.downcast::() { - let name = name_str.as_str(); - return name == "Protocol" || name == "Generic"; - } - } - false - }) - } else { - false - } - } else { - false - }; + // Remove .type_params from namespace before creating the class + namespace + .as_object() + .del_item(vm.ctx.intern_str(".type_params"), vm) + .ok(); let args = FuncArgs::new(vec![name_obj.into(), bases, namespace.into()], kwargs); let class = metaclass.call(args, vm)?; - // Set __type_params__ on the class if the function has type params - if has_type_params { - if let Ok(type_params) = function_obj.get_attr(identifier!(vm, __type_params__), vm) { - class.set_attr(identifier!(vm, __type_params__), type_params.clone(), vm)?; - - if needs_parameters { - // Set __parameters__ to the same value as __type_params__ - class.set_attr(identifier!(vm, __parameters__), type_params, vm)?; + // For PEP 695 classes, set __type_params__ on the class from the function + if let Ok(type_params) = function + .as_object() + .get_attr(identifier!(vm, __type_params__), vm) + { + if let Some(type_params_tuple) = type_params.downcast_ref::() { + if !type_params_tuple.is_empty() { + class.set_attr(identifier!(vm, __type_params__), type_params, vm)?; } } }