From 30f421bb305bbb1e65d26ef8506cae0d4041ba79 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 11 Jul 2025 15:05:25 +0900 Subject: [PATCH] typing TypeAlias --- Lib/test/test_typing.py | 2 - compiler/codegen/src/compile.rs | 62 +++++++++++----- compiler/core/src/bytecode.rs | 46 ++---------- vm/src/frame.rs | 126 ++++++++++++++++---------------- vm/src/stdlib/typing.rs | 21 ++++++ 5 files changed, 134 insertions(+), 123 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1c74e1adac..d0fe1b0188 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6649,8 +6649,6 @@ def manager(): self.assertIsInstance(cm, typing.ContextManager) self.assertNotIsInstance(42, typing.ContextManager) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_contextmanager_type_params(self): cm1 = typing.ContextManager[int] self.assertEqual(get_args(cm1), (int, bool | None)) diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index c6f01ecc82..d3d412e9c5 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -1067,33 +1067,41 @@ impl Compiler<'_> { // For PEP 695 syntax, we need to compile type_params first // so that they're available when compiling the value expression + // Push name first + self.emit_load_const(ConstantData::Str { + value: name_string.clone().into(), + }); + if let Some(type_params) = type_params { self.push_symbol_table(); - // Compile type params first to define T1, T2, etc. + // Compile type params and push to stack self.compile_type_params(type_params)?; - // Stack now has type_params tuple at top + // Stack now has [name, type_params_tuple] // Compile value expression (can now see T1, T2) self.compile_expression(value)?; - // Stack: [type_params_tuple, value] - - // We need [value, type_params_tuple] for TypeAlias instruction - emit!(self, Instruction::Rotate2); + // Stack: [name, type_params_tuple, value] self.pop_symbol_table(); } else { - // No type params - push value first, then None (not empty tuple) - self.compile_expression(value)?; // Push None for type_params (matching CPython) self.emit_load_const(ConstantData::None); + // Stack: [name, None] + + // Compile value expression + self.compile_expression(value)?; + // Stack: [name, None, value] } - // Push name last - self.emit_load_const(ConstantData::Str { - value: name_string.clone().into(), - }); - emit!(self, Instruction::TypeAlias); + // Build tuple of 3 elements and call intrinsic + emit!(self, Instruction::BuildTuple { size: 3 }); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeAlias + } + ); self.store_name(&name_string)?; } Stmt::IpyEscapeCommand(_) => todo!(), @@ -1246,12 +1254,22 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVarWithBound); + emit!( + self, + Instruction::CallIntrinsic2 { + func: bytecode::IntrinsicFunction2::TypeVarWithBound + } + ); } else { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVar); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeVar + } + ); } // Handle default value if present (PEP 695) @@ -1274,7 +1292,12 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::ParamSpec); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::ParamSpec + } + ); // Handle default value if present (PEP 695) if let Some(default_expr) = default { @@ -1296,7 +1319,12 @@ impl Compiler<'_> { self.emit_load_const(ConstantData::Str { value: name.as_str().into(), }); - emit!(self, Instruction::TypeVarTuple); + emit!( + self, + Instruction::CallIntrinsic1 { + func: bytecode::IntrinsicFunction1::TypeVarTuple + } + ); // Handle default value if present (PEP 695) if let Some(default_expr) = default { diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index a27174e859..cef332bfbc 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -382,27 +382,13 @@ op_arg_enum!( #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] pub enum IntrinsicFunction1 { - /// Import * special case - // ImportStar = 0, - /// Set stop iteration value - // StopAsyncIteration = 1, - /// Unary operators - // UnaryPositive = 2, - // UnaryNegative = 3, - // UnaryNot = 4, - // UnaryInvert = 5, - /// Exit init subclass - // ExitInitCheck = 6, - /// Create a new list from an iterator - // ListToTupleForCall = 7, /// Type parameter related - // TypeVar = 8, - // TypeVarTuple = 9, - // ParamSpec = 10, + TypeVar = 7, + ParamSpec = 8, + TypeVarTuple = 9, /// Generic subscript for PEP 695 SubscriptGeneric = 10, - // TypeAlias = 12, - // TypeParams = 13, + TypeAlias = 11, } ); @@ -412,8 +398,8 @@ op_arg_enum!( #[repr(u8)] pub enum IntrinsicFunction2 { // PrepReraiseS tar = 1, - // TypeVarWithBound = 2, - // TypeVarWithConstraints = 3, + TypeVarWithBound = 2, + TypeVarWithConstraint = 3, SetFunctionTypeParams = 4, /// Set default value for type parameter (PEP 695) SetTypeparamDefault = 5, @@ -668,16 +654,10 @@ pub enum Instruction { MatchKeys, MatchClass(Arg), ExtendedArg, - TypeVar, - TypeVarWithBound, - TypeVarWithConstraint, - TypeAlias, - TypeVarTuple, - ParamSpec, // If you add a new instruction here, be sure to keep LAST_INSTRUCTION updated } // This must be kept up to date to avoid marshaling errors -const LAST_INSTRUCTION: Instruction = Instruction::ParamSpec; +const LAST_INSTRUCTION: Instruction = Instruction::ExtendedArg; const _: () = assert!(mem::size_of::() == 1); impl From for u8 { @@ -1380,12 +1360,6 @@ impl Instruction { MatchKeys => -1, MatchClass(_) => -2, ExtendedArg => 0, - TypeVar => 0, - TypeVarWithBound => -1, - TypeVarWithConstraint => -1, - TypeAlias => -2, - ParamSpec => 0, - TypeVarTuple => 0, } } @@ -1565,12 +1539,6 @@ impl Instruction { MatchKeys => w!(MatchKeys), MatchClass(arg) => w!(MatchClass, arg), ExtendedArg => w!(ExtendedArg, Arg::::marker()), - TypeVar => w!(TypeVar), - TypeVarWithBound => w!(TypeVarWithBound), - TypeVarWithConstraint => w!(TypeVarWithConstraint), - TypeAlias => w!(TypeAlias), - ParamSpec => w!(ParamSpec), - TypeVarTuple => w!(TypeVarTuple), } } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6c9181c2c1..7c935b814b 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1255,71 +1255,6 @@ impl ExecutingFrame<'_> { *extend_arg = true; Ok(None) } - bytecode::Instruction::TypeVar => { - let type_name = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), vm.ctx.none()) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeVarWithBound => { - let type_name = self.pop_value(); - let bound = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), bound, vm.ctx.none()) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeVarWithConstraint => { - let type_name = self.pop_value(); - let constraint = self.pop_value(); - let type_var: PyObjectRef = - typing::TypeVar::new(vm, type_name.clone(), vm.ctx.none(), constraint) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var); - Ok(None) - } - bytecode::Instruction::TypeAlias => { - let name = self.pop_value(); - let type_params_obj = self.pop_value(); - - // CPython allows None or tuple for type_params - let type_params: PyTupleRef = if vm.is_none(&type_params_obj) { - // If None, use empty tuple (matching CPython's behavior) - vm.ctx.empty_tuple.clone() - } else { - type_params_obj - .downcast() - .map_err(|_| vm.new_type_error("Type params must be a tuple."))? - }; - - let value = self.pop_value(); - let type_alias = typing::TypeAliasType::new(name, type_params, value); - self.push_value(type_alias.into_ref(&vm.ctx).into()); - Ok(None) - } - bytecode::Instruction::ParamSpec => { - let param_spec_name = self.pop_value(); - let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone(), vm) - .into_ref(&vm.ctx) - .into(); - self.push_value(param_spec); - Ok(None) - } - bytecode::Instruction::TypeVarTuple => { - let type_var_tuple_name = self.pop_value(); - let type_var_tuple: PyObjectRef = - typing::TypeVarTuple::new(type_var_tuple_name.clone(), vm) - .into_ref(&vm.ctx) - .into(); - self.push_value(type_var_tuple); - Ok(None) - } bytecode::Instruction::MatchMapping => { // Pop the subject from stack let subject = self.pop_value(); @@ -2272,6 +2207,53 @@ impl ExecutingFrame<'_> { // Used for PEP 695: Generic[*type_params] crate::builtins::genericalias::subscript_generic(arg, vm) } + bytecode::IntrinsicFunction1::TypeVar => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg.clone(), vm.ctx.none(), vm.ctx.none()) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } + bytecode::IntrinsicFunction1::ParamSpec => { + let param_spec: PyObjectRef = typing::ParamSpec::new(arg.clone(), vm) + .into_ref(&vm.ctx) + .into(); + Ok(param_spec) + } + bytecode::IntrinsicFunction1::TypeVarTuple => { + let type_var_tuple: PyObjectRef = typing::TypeVarTuple::new(arg.clone(), vm) + .into_ref(&vm.ctx) + .into(); + Ok(type_var_tuple) + } + bytecode::IntrinsicFunction1::TypeAlias => { + // TypeAlias receives a tuple of (name, type_params, value) + let tuple: PyTupleRef = arg + .downcast() + .map_err(|_| vm.new_type_error("TypeAlias expects a tuple argument"))?; + + if tuple.len() != 3 { + return Err(vm.new_type_error(format!( + "TypeAlias expects exactly 3 arguments, got {}", + tuple.len() + ))); + } + + let name = tuple.as_slice()[0].clone(); + let type_params_obj = tuple.as_slice()[1].clone(); + let value = tuple.as_slice()[2].clone(); + + let type_params: PyTupleRef = if vm.is_none(&type_params_obj) { + vm.ctx.empty_tuple.clone() + } else { + type_params_obj + .downcast() + .map_err(|_| vm.new_type_error("Type params must be a tuple."))? + }; + + let type_alias = typing::TypeAliasType::new(name, type_params, value); + Ok(type_alias.into_ref(&vm.ctx).into()) + } } } @@ -2292,6 +2274,20 @@ impl ExecutingFrame<'_> { arg1.set_attr("__type_params__", arg2, vm)?; Ok(arg1) } + bytecode::IntrinsicFunction2::TypeVarWithBound => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg1.clone(), arg2, vm.ctx.none()) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } + bytecode::IntrinsicFunction2::TypeVarWithConstraint => { + let type_var: PyObjectRef = + typing::TypeVar::new(vm, arg1.clone(), vm.ctx.none(), arg2) + .into_ref(&vm.ctx) + .into(); + Ok(type_var) + } } } diff --git a/vm/src/stdlib/typing.rs b/vm/src/stdlib/typing.rs index 331206b214..77feee44c0 100644 --- a/vm/src/stdlib/typing.rs +++ b/vm/src/stdlib/typing.rs @@ -113,6 +113,27 @@ pub(crate) mod decl { value, } } + + #[pygetset] + fn __name__(&self) -> PyObjectRef { + self.name.clone() + } + + #[pygetset] + fn __value__(&self) -> PyObjectRef { + self.value.clone() + } + + #[pygetset] + fn __type_params__(&self) -> PyTupleRef { + self.type_params.clone() + } + + #[pymethod(name = "__repr__")] + fn repr(&self, vm: &VirtualMachine) -> PyResult { + let name = self.name.str(vm)?; + Ok(name.as_str().to_owned()) + } } // impl AsMapping for Generic {