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
type.__type_params__
  • Loading branch information
youknowone committed Jul 1, 2025
commit be60fe9fd36c5aa4c2fdda9a55ce1edc6a8d03bd
2 changes: 0 additions & 2 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3246,8 +3246,6 @@ def test_classvar_module_level_import(self):
# won't exist on the instance.
self.assertNotIn('not_iv4', c.__dict__)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_text_annotations(self):
from test import dataclass_textanno

Expand Down
35 changes: 0 additions & 35 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1605,8 +1605,6 @@ class D(Generic[Unpack[Ts]]): pass
self.assertIs(D[T].__origin__, D)
self.assertIs(D[Unpack[Ts]].__origin__, D)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_type_hints_on_unpack_args(self):
Ts = TypeVarTuple('Ts')

Expand Down Expand Up @@ -1809,8 +1807,6 @@ class F(Generic[Unpack[Ts], T1, T2]): pass
F[int, str, float]
F[int, str, float, bool]

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_variadic_args_annotations_are_correct(self):
Ts = TypeVarTuple('Ts')

Expand Down Expand Up @@ -2494,7 +2490,6 @@ def test_var_substitution(self):
self.assertEqual(C5[int, str, float],
Callable[[typing.List[int], tuple[str, int], float], int])

@unittest.skip("TODO: RUSTPYTHON")
def test_type_subst_error(self):
Callable = self.Callable
P = ParamSpec('P')
Expand Down Expand Up @@ -3859,8 +3854,6 @@ def barfoo(x: AT): ...
def barfoo2(x: CT): ...
self.assertIs(get_type_hints(barfoo2, globals(), locals())['x'], CT)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_generic_pep585_forward_ref(self):
# See https://bugs.python.org/issue41370

Expand Down Expand Up @@ -5189,8 +5182,6 @@ def cmp(o1, o2):
self.assertIsNot(r1, r2)
self.assertRaises(RecursionError, cmp, r1, r2)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_union_forward_recursion(self):
ValueList = List['Value']
Value = Union[str, ValueList]
Expand Down Expand Up @@ -5239,8 +5230,6 @@ def foo(a: 'Callable[..., T]'):
self.assertEqual(get_type_hints(foo, globals(), locals()),
{'a': Callable[..., T]})

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

class C:
Expand Down Expand Up @@ -5323,8 +5312,6 @@ def foo(self, x: int): ...

self.assertEqual(get_type_hints(Child.foo), {'x': int})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_type_check_nested_types(self):
# See https://bugs.python.org/issue46571
class Other:
Expand Down Expand Up @@ -5409,8 +5396,6 @@ def test_no_type_check_TypeError(self):
# `TypeError: can't set attributes of built-in/extension type 'dict'`
no_type_check(dict)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_no_type_check_forward_ref_as_string(self):
class C:
foo: typing.ClassVar[int] = 7
Expand Down Expand Up @@ -5465,8 +5450,6 @@ def test_default_globals(self):
hints = get_type_hints(ns['C'].foo)
self.assertEqual(hints, {'a': ns['C'], 'return': ns['D']})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_final_forward_ref(self):
self.assertEqual(gth(Loop, globals())['attr'], Final[Loop])
self.assertNotEqual(gth(Loop, globals())['attr'], Final[int])
Expand Down Expand Up @@ -5832,8 +5815,6 @@ def test_get_type_hints_classes(self):
'my_inner_a2': mod_generics_cache.B.A,
'my_outer_a': mod_generics_cache.A})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_type_hints_classes_no_implicit_optional(self):
class WithNoneDefault:
field: int = None # most type-checkers won't be happy with it
Expand Down Expand Up @@ -5878,8 +5859,6 @@ class B: ...
b.__annotations__ = {'x': 'A'}
self.assertEqual(gth(b, locals()), {'x': A})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_type_hints_ClassVar(self):
self.assertEqual(gth(ann_module2.CV, ann_module2.__dict__),
{'var': typing.ClassVar[ann_module2.CV]})
Expand Down Expand Up @@ -6006,8 +5985,6 @@ def annotated_with_none_default(x: Annotated[int, 'data'] = None): ...
{'x': Annotated[int, 'data']},
)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_type_hints_classes_str_annotations(self):
class Foo:
y = str
Expand All @@ -6023,8 +6000,6 @@ class BadModule:
self.assertNotIn('bad', sys.modules)
self.assertEqual(get_type_hints(BadModule), {})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_get_type_hints_annotated_bad_module(self):
# See https://bugs.python.org/issue44468
class BadBase:
Expand All @@ -6035,8 +6010,6 @@ class BadType(BadBase):
self.assertNotIn('bad', sys.modules)
self.assertEqual(get_type_hints(BadType), {'foo': tuple, 'bar': list})

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_forward_ref_and_final(self):
# https://bugs.python.org/issue45166
hints = get_type_hints(ann_module5)
Expand Down Expand Up @@ -8274,8 +8247,6 @@ class C:
A.x = 5
self.assertEqual(C.x, 5)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_special_form_containment(self):
class C:
classvar: Annotated[ClassVar[int], "a decoration"] = 4
Expand All @@ -8284,8 +8255,6 @@ class C:
self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int])
self.assertEqual(get_type_hints(C, globals())['const'], Final[int])

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_special_forms_nesting(self):
# These are uncommon types and are to ensure runtime
# is lax on validation. See gh-89547 for more context.
Expand Down Expand Up @@ -8563,8 +8532,6 @@ def test_no_isinstance(self):
with self.assertRaises(TypeError):
isinstance(42, TypeAlias)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_stringized_usage(self):
class A:
a: "TypeAlias"
Expand Down Expand Up @@ -8652,8 +8619,6 @@ def test_args_kwargs(self):
self.assertEqual(repr(P.kwargs), "P.kwargs")


# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_stringized(self):
P = ParamSpec('P')
class C(Generic[P]):
Expand Down
15 changes: 14 additions & 1 deletion compiler/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2901,7 +2901,20 @@ impl Compiler<'_> {
} else {
let was_in_annotation = self.in_annotation;
self.in_annotation = true;
let result = self.compile_expression(annotation);

// Special handling for starred annotations (*Ts -> Unpack[Ts])
let result = match annotation {
Expr::Starred(ExprStarred { value, .. }) => {
// Following CPython's approach:
// *args: *Ts (where Ts is a TypeVarTuple).
// Do [annotation_value] = [*Ts].
self.compile_expression(value)?;
emit!(self, Instruction::UnpackSequence { size: 1 });
Ok(())
}
_ => self.compile_expression(annotation),
};

self.in_annotation = was_in_annotation;
result?;
}
Expand Down
42 changes: 42 additions & 0 deletions vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,48 @@ impl PyType {
.and_then(|doc| get_text_signature_from_internal_doc(&self.name(), doc))
.map(|signature| signature.to_string())
}

#[pygetset]
fn __type_params__(&self, vm: &VirtualMachine) -> PyTupleRef {
let attrs = self.attributes.read();
let key = identifier!(vm, __type_params__);
if let Some(params) = attrs.get(&key) {
if let Ok(tuple) = params.clone().downcast::<PyTuple>() {
return tuple;
}
}
// Return empty tuple if not found or not a tuple
vm.ctx.empty_tuple.clone()
}

#[pygetset(setter)]
fn set___type_params__(
&self,
value: PySetterValue<PyTupleRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
match value {
PySetterValue::Assign(ref val) => {
let key = identifier!(vm, __type_params__);
self.check_set_special_type_attr(val.as_ref(), key, vm)?;
let mut attrs = self.attributes.write();
attrs.insert(key, val.clone().into());
}
PySetterValue::Delete => {
// For delete, we still need to check if the type is immutable
if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
return Err(vm.new_type_error(format!(
"cannot delete '__type_params__' attribute of immutable type '{}'",
self.slot_name()
)));
}
let mut attrs = self.attributes.write();
let key = identifier!(vm, __type_params__);
attrs.shift_remove(&key);
}
}
Ok(())
}
Comment on lines +866 to +893

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding immutability check in the setter.

The setter correctly enforces tuple types and provides appropriate error messages for deletion attempts. However, it doesn't check if the type is immutable before allowing modifications.

Add immutability check consistent with other setters in this file:

 #[pygetset(setter)]
 fn set___type_params__(
     &self,
     value: PySetterValue<PyTupleRef>,
     vm: &VirtualMachine,
 ) -> PyResult<()> {
+    // Check if this is an immutable type
+    if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
+        return Err(vm.new_type_error(format!(
+            "cannot set '__type_params__' attribute of immutable type '{}'",
+            self.name()
+        )));
+    }
+
     match value {
         PySetterValue::Assign(value) => {
             let mut attrs = self.attributes.write();
             let key = vm.ctx.intern_str("__type_params__");
             attrs.insert(key, value.into());
         }
         PySetterValue::Delete => {
             return Err(vm.new_type_error("__type_params__ must be set to a tuple object"));
         }
     }
     Ok(())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[pygetset(setter)]
fn set___type_params__(
&self,
value: PySetterValue<PyTupleRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
match value {
PySetterValue::Assign(value) => {
let mut attrs = self.attributes.write();
let key = vm.ctx.intern_str("__type_params__");
attrs.insert(key, value.into());
}
PySetterValue::Delete => {
return Err(vm.new_type_error("__type_params__ must be set to a tuple object"));
}
}
Ok(())
}
#[pygetset(setter)]
fn set___type_params__(
&self,
value: PySetterValue<PyTupleRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
// Check if this is an immutable type
if self.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
return Err(vm.new_type_error(format!(
"cannot set '__type_params__' attribute of immutable type '{}'",
self.name()
)));
}
match value {
PySetterValue::Assign(value) => {
let mut attrs = self.attributes.write();
let key = vm.ctx.intern_str("__type_params__");
attrs.insert(key, value.into());
}
PySetterValue::Delete => {
return Err(vm.new_type_error("__type_params__ must be set to a tuple object"));
}
}
Ok(())
}
🤖 Prompt for AI Agents
In vm/src/builtins/type.rs around lines 866 to 883, the setter for
__type_params__ lacks a check for immutability before modifying the attribute.
To fix this, add a check at the start of the setter that verifies if the type is
immutable, similar to other setters in this file, and return an error if it is
immutable to prevent modification. This ensures consistency and enforces
immutability constraints properly.

}

impl Constructor for PyType {
Expand Down
1 change: 1 addition & 0 deletions vm/src/vm/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ declare_const_name! {
__sizeof__,
__truediv__,
__trunc__,
__type_params__,
__typing_subst__,
__typing_is_unpacked_typevartuple__,
__typing_prepare_subst__,
Expand Down