diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 821710a7fa3286..1257b529038afb 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -18,6 +18,7 @@ import sys import traceback import types +import typing import unittest import warnings from contextlib import ExitStack @@ -2485,6 +2486,17 @@ def test_type_qualname(self): A.__qualname__ = b'B' self.assertEqual(A.__qualname__, 'D.E') + def test_type_typeparams(self): + class A[T]: + pass + T, = A.__type_params__ + self.assertIsInstance(T, typing.TypeVar) + A.__type_params__ = "whatever" + self.assertEqual(A.__type_params__, "whatever") + with self.assertRaises(TypeError): + del A.__type_params__ + self.assertEqual(A.__type_params__, "whatever") + def test_type_doc(self): for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None: A = type('A', (), {'__doc__': doc}) diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index d4f5de573f51d2..7b7b6122c028e5 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -816,10 +816,11 @@ def test_typeparams_dunder_class_03(self): class ClassA[A](): pass ClassA.__type_params__ = () + params = ClassA.__type_params__ """ - with self.assertRaisesRegex(AttributeError, "attribute '__type_params__' of 'type' objects is not writable"): - run_code(code) + ns = run_code(code) + self.assertEqual(ns["params"], ()) def test_typeparams_dunder_function_01(self): def outer[A, B](): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 450c85967dd75a..6459fa3eb96a60 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -6810,6 +6810,19 @@ class Y(Generic[T], NamedTuple): with self.assertRaises(TypeError): G[int, str] + def test_generic_pep695(self): + class X[T](NamedTuple): + x: T + T, = X.__type_params__ + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(X.__bases__, (tuple, Generic)) + self.assertEqual(X.__orig_bases__, (NamedTuple, Generic[T])) + self.assertEqual(X.__mro__, (X, tuple, Generic, object)) + self.assertEqual(X.__parameters__, (T,)) + self.assertEqual(X[str].__args__, (str,)) + self.assertEqual(X[str].__parameters__, ()) + def test_non_generic_subscript(self): # For backward compatibility, subscription works # on arbitrary NamedTuple types. @@ -7220,6 +7233,20 @@ class FooBarGeneric(BarGeneric[int]): {'a': typing.Optional[T], 'b': int, 'c': str} ) + def test_pep695_generic_typeddict(self): + class A[T](TypedDict): + a: T + + T, = A.__type_params__ + self.assertIsInstance(T, TypeVar) + self.assertEqual(T.__name__, 'T') + self.assertEqual(A.__bases__, (Generic, dict)) + self.assertEqual(A.__orig_bases__, (TypedDict, Generic[T])) + self.assertEqual(A.__mro__, (A, Generic, dict, object)) + self.assertEqual(A.__parameters__, (T,)) + self.assertEqual(A[str].__parameters__, ()) + self.assertEqual(A[str].__args__, (str,)) + def test_generic_inheritance(self): class A(TypedDict, Generic[T]): a: T diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 624dc63ce82cc0..2fbcafe91aadc6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1460,18 +1460,6 @@ type_get_annotations(PyTypeObject *type, void *context) return annotations; } -static PyObject * -type_get_type_params(PyTypeObject *type, void *context) -{ - PyObject *params = PyDict_GetItem(lookup_tp_dict(type), &_Py_ID(__type_params__)); - - if (params) { - return Py_NewRef(params); - } - - return PyTuple_New(0); -} - static int type_set_annotations(PyTypeObject *type, PyObject *value, void *context) { @@ -1502,6 +1490,34 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context) return result; } +static PyObject * +type_get_type_params(PyTypeObject *type, void *context) +{ + PyObject *params = PyDict_GetItem(lookup_tp_dict(type), &_Py_ID(__type_params__)); + + if (params) { + return Py_NewRef(params); + } + + return PyTuple_New(0); +} + +static int +type_set_type_params(PyTypeObject *type, PyObject *value, void *context) +{ + if (!check_set_special_type_attr(type, value, "__type_params__")) { + return -1; + } + + PyObject *dict = lookup_tp_dict(type); + int result = PyDict_SetItem(dict, &_Py_ID(__type_params__), value); + + if (result == 0) { + PyType_Modified(type); + } + return result; +} + /*[clinic input] type.__instancecheck__ -> bool @@ -1548,7 +1564,7 @@ static PyGetSetDef type_getsets[] = { {"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL}, {"__text_signature__", (getter)type_get_text_signature, NULL, NULL}, {"__annotations__", (getter)type_get_annotations, (setter)type_set_annotations, NULL}, - {"__type_params__", (getter)type_get_type_params, NULL, NULL}, + {"__type_params__", (getter)type_get_type_params, (setter)type_set_type_params, NULL}, {0} };