]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-104600: Make type.__type_params__ writable (#104634)
authorJelle Zijlstra <jelle.zijlstra@gmail.com>
Fri, 19 May 2023 16:04:47 +0000 (09:04 -0700)
committerGitHub <noreply@github.com>
Fri, 19 May 2023 16:04:47 +0000 (09:04 -0700)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Lib/test/test_builtin.py
Lib/test/test_type_params.py
Lib/test/test_typing.py
Objects/typeobject.c

index 821710a7fa32869b706dec15fff33b6f462cc580..1257b529038afb706e3e8afe7643063530f9d964 100644 (file)
@@ -18,6 +18,7 @@ import re
 import sys
 import traceback
 import types
+import typing
 import unittest
 import warnings
 from contextlib import ExitStack
@@ -2485,6 +2486,17 @@ class TestType(unittest.TestCase):
             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})
index d4f5de573f51d27ed36ebfe4ee7597f6e2ea76b5..7b7b6122c028e5e595625c525669e6fc75c6084d 100644 (file)
@@ -816,10 +816,11 @@ class TypeParamsTypeParamsDunder(unittest.TestCase):
             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]():
index 450c85967dd75a872bfd65d76ba9731eb944dc52..6459fa3eb96a6075a66b5b186435821f50f91b93 100644 (file)
@@ -6810,6 +6810,19 @@ class NamedTupleTests(BaseTestCase):
                 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 TypedDictTests(BaseTestCase):
             {'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
index 624dc63ce82cc02087e33056dd5cf54092181aaa..2fbcafe91aadc6355d58b6d34c4bdc4803f781a6 100644 (file)
@@ -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}
 };