Visitar URL original
typing __parameters__ __type_params__ by youknowone · Pull Request #5909 · RustPython/RustPython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fix __parameters__, __type_params__
  • Loading branch information
youknowone committed Jul 7, 2025
commit bd54e537fd057b5131afe17f54f7b0c537d7f05b
2 changes: 1 addition & 1 deletion Lib/test/test_module/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def test_annotations_getset_raises(self):
del foo.__annotations__

def test_annotations_are_created_correctly(self):
ann_module4 = import_helper.import_fresh_module('test.ann_module4')
ann_module4 = import_helper.import_fresh_module('test.typinganndata.ann_module4')
self.assertTrue("__annotations__" in ann_module4.__dict__)
del ann_module4.__annotations__
self.assertFalse("__annotations__" in ann_module4.__dict__)
Expand Down
17 changes: 0 additions & 17 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,8 +629,6 @@ def test_typevar(self):
class A(Generic[T]): ...
Alias = Optional[T]

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_typevar_none(self):
U = TypeVar('U')
U_None = TypeVar('U_None', default=None)
Expand Down Expand Up @@ -1210,8 +1208,6 @@ def foo(**kwargs: Unpack[Movie]): ...
self.assertEqual(repr(foo.__annotations__['kwargs']),
f"typing.Unpack[{__name__}.Movie]")

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_builtin_tuple(self):
Ts = TypeVarTuple("Ts")

Expand Down Expand Up @@ -2636,11 +2632,6 @@ 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):
Expand Down Expand Up @@ -3608,8 +3599,6 @@ class C(B[int]):
c.bar = 'abc'
self.assertEqual(c.__dict__, {'bar': 'abc'})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_setattr_exceptions(self):
class Immutable[T]:
def __setattr__(self, key, value):
Expand Down Expand Up @@ -3753,8 +3742,6 @@ class Meta(type): ...
self.assertEqual(Union[T, int][Meta], Union[Meta, int])
self.assertEqual(Callable[..., Meta].__args__, (Ellipsis, Meta))

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_generic_hashes(self):
class A(Generic[T]):
...
Expand Down Expand Up @@ -4302,8 +4289,6 @@ class B(Generic[T]):
self.assertEqual(A[T], A[T])
self.assertNotEqual(A[T], B[T])

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_multiple_inheritance(self):

class A(Generic[T, VT]):
Expand Down Expand Up @@ -7597,8 +7582,6 @@ class A[T](TypedDict):
self.assertEqual(A[str].__parameters__, ())
self.assertEqual(A[str].__args__, (str,))

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_generic_inheritance(self):
class A(TypedDict, Generic[T]):
a: T
Expand Down
101 changes: 87 additions & 14 deletions compiler/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,38 +1215,83 @@ impl Compiler<'_> {
// First, compile each type parameter and store it
for type_param in &type_params.type_params {
match type_param {
TypeParam::TypeVar(TypeParamTypeVar { name, bound, .. }) => {
TypeParam::TypeVar(TypeParamTypeVar {
name,
bound,
default,
..
}) => {
if let Some(expr) = &bound {
self.compile_expression(expr)?;
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVarWithBound);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
} else {
Comment thread
youknowone marked this conversation as resolved.
// self.store_name(type_name.as_str())?;
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVar);
emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}

// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;

emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}

emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
TypeParam::ParamSpec(TypeParamParamSpec { name, .. }) => {
TypeParam::ParamSpec(TypeParamParamSpec { name, default, .. }) => {
Comment thread
youknowone marked this conversation as resolved.
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::ParamSpec);

// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;

emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}

emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, .. }) => {
TypeParam::TypeVarTuple(TypeParamTypeVarTuple { name, default, .. }) => {
Comment thread
youknowone marked this conversation as resolved.
self.emit_load_const(ConstantData::Str {
value: name.as_str().into(),
});
emit!(self, Instruction::TypeVarTuple);

// Handle default value if present (PEP 695)
if let Some(default_expr) = default {
// Compile the default expression
self.compile_expression(default_expr)?;

// Handle starred expression (*default)
emit!(
self,
Instruction::CallIntrinsic2 {
func: bytecode::IntrinsicFunction2::SetTypeparamDefault
}
);
}

emit!(self, Instruction::Duplicate);
self.store_name(name.as_ref())?;
}
Expand Down Expand Up @@ -1759,13 +1804,41 @@ impl Compiler<'_> {

self.emit_load_const(ConstantData::Str { value: name.into() });

// Call the __build_class__ builtin
let call = if let Some(arguments) = arguments {
self.compile_call_inner(2, arguments)?
// For PEP 695 classes: handle Generic base creation
if type_params.is_some() {
if let Some(arguments) = arguments {
// Has explicit bases - use them as is, don't add Generic
// CPython doesn't add Generic when explicit bases are present
let call = self.compile_call_inner(2, arguments)?;
self.compile_normal_call(call);
} else {
// No explicit bases, add Generic[*type_params] as the only base
// Stack currently: [function, class_name]

// Load .type_params for creating Generic base
let dot_type_params = self.name(".type_params");
emit!(self, Instruction::LoadNameAny(dot_type_params));

// Call INTRINSIC_SUBSCRIPT_GENERIC to create Generic[*type_params]
emit!(
self,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::SubscriptGeneric
}
);

// Call __build_class__ with 3 positional args: function, class_name, Generic[T]
emit!(self, Instruction::CallFunctionPositional { nargs: 3 });
}
} else {
CallType::Positional { nargs: 2 }
};
self.compile_normal_call(call);
// No type params, normal compilation
let call = if let Some(arguments) = arguments {
self.compile_call_inner(2, arguments)?
} else {
CallType::Positional { nargs: 2 }
};
self.compile_normal_call(call);
}

// Pop the special type params symbol table
if type_params.is_some() {
Expand Down
53 changes: 53 additions & 0 deletions compiler/core/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,49 @@ op_arg_enum!(
}
);

op_arg_enum!(
/// Intrinsic function for CALL_INTRINSIC_1
#[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,
/// Generic subscript for PEP 695
SubscriptGeneric = 10,
// TypeAlias = 12,
// TypeParams = 13,
}
);

op_arg_enum!(
/// Intrinsic function for CALL_INTRINSIC_2
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum IntrinsicFunction2 {
// PrepReraiseS tar = 1,
// TypeVarWithBound = 2,
// TypeVarWithConstraints = 3,
// SetFunctionTypeParams = 4,
/// Set default value for type parameter (PEP 695)
SetTypeparamDefault = 5,
}
);

pub type NameIdx = u32;

/// A Single bytecode instruction.
Expand Down Expand Up @@ -454,6 +497,12 @@ pub enum Instruction {
Duplicate2,
GetIter,
GetLen,
CallIntrinsic1 {
func: Arg<IntrinsicFunction1>,
},
CallIntrinsic2 {
func: Arg<IntrinsicFunction2>,
},
Continue {
target: Arg<Label>,
},
Expand Down Expand Up @@ -1235,6 +1284,8 @@ impl Instruction {
Duplicate2 => 2,
GetIter => 0,
GetLen => 1,
CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1
CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1
Continue { .. } => 0,
Break { .. } => 0,
Jump { .. } => 0,
Expand Down Expand Up @@ -1444,6 +1495,8 @@ impl Instruction {
GetIter => w!(GetIter),
// GET_LEN
GetLen => w!(GetLen),
CallIntrinsic1 { func } => w!(CallIntrinsic1, ?func),
CallIntrinsic2 { func } => w!(CallIntrinsic2, ?func),
Continue { target } => w!(Continue, target),
Break { target } => w!(Break, target),
Jump { target } => w!(Jump, target),
Expand Down
19 changes: 19 additions & 0 deletions vm/src/builtins/genericalias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,25 @@ impl Iterable for PyGenericAlias {
}
}

/// Creates a GenericAlias from type parameters, equivalent to CPython's _Py_subscript_generic
/// This is used for PEP 695 classes to create Generic[T] from type parameters
// _Py_subscript_generic
pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// Get typing.Generic type
let typing_module = vm.import("typing", 0)?;
let generic_type = typing_module.get_attr("Generic", vm)?;
let generic_type = PyTypeRef::try_from_object(vm, generic_type)?;

// Create GenericAlias: Generic[type_params]
let args = if let Ok(tuple) = type_params.try_to_ref::<PyTuple>(vm) {
tuple.to_owned()
} else {
PyTuple::new_ref(vec![type_params], &vm.ctx)
};

Ok(PyGenericAlias::new(generic_type, args, false, vm).into_pyobject(vm))
}

pub fn init(context: &Context) {
let generic_alias_type = &context.types.generic_alias_type;
PyGenericAlias::extend_class(context, generic_alias_type);
Expand Down
43 changes: 42 additions & 1 deletion vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,19 @@ impl ExecutingFrame<'_> {
self.push_value(vm.ctx.new_int(len).into());
Ok(None)
}
bytecode::Instruction::CallIntrinsic1 { func } => {
let value = self.pop_value();
let result = self.call_intrinsic_1(func.get(arg), value, vm)?;
self.push_value(result);
Ok(None)
}
bytecode::Instruction::CallIntrinsic2 { func } => {
let value2 = self.pop_value();
let value1 = self.pop_value();
let result = self.call_intrinsic_2(func.get(arg), value1, value2, vm)?;
self.push_value(result);
Ok(None)
}
bytecode::Instruction::GetAwaitable => {
let awaited_obj = self.pop_value();
let awaitable = if awaited_obj.payload_is::<PyCoroutine>() {
Expand Down Expand Up @@ -1288,7 +1301,7 @@ impl ExecutingFrame<'_> {
}
bytecode::Instruction::ParamSpec => {
let param_spec_name = self.pop_value();
let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone())
let param_spec: PyObjectRef = typing::ParamSpec::new(param_spec_name.clone(), vm)
.into_ref(&vm.ctx)
.into();
self.push_value(param_spec);
Expand Down Expand Up @@ -2244,6 +2257,34 @@ impl ExecutingFrame<'_> {
}
}

fn call_intrinsic_1(
&mut self,
func: bytecode::IntrinsicFunction1,
arg: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
match func {
bytecode::IntrinsicFunction1::SubscriptGeneric => {
// Used for PEP 695: Generic[*type_params]
crate::builtins::genericalias::subscript_generic(arg, vm)
}
}
}

fn call_intrinsic_2(
&mut self,
func: bytecode::IntrinsicFunction2,
arg1: PyObjectRef,
arg2: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult {
match func {
bytecode::IntrinsicFunction2::SetTypeparamDefault => {
crate::stdlib::typing::set_typeparam_default(arg1, arg2, vm)
}
}
}

fn pop_multiple(&mut self, count: usize) -> crate::common::boxvec::Drain<'_, PyObjectRef> {
let stack_len = self.state.stack.len();
self.state.stack.drain(stack_len - count..)
Expand Down
Loading