From 8f1f3b9abdaa3e9d19aad22d6c310eb1f05ae5c2 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 19 May 2023 09:04:47 -0700 Subject: [PATCH] gh-104600: Make type.__type_params__ writable (#104634) Co-authored-by: Alex Waygood --- Lib/test/test_builtin.py | 12 +++++++++++ Lib/test/test_type_params.py | 5 +++-- Lib/test/test_typing.py | 27 +++++++++++++++++++++++ Objects/typeobject.c | 42 +++++++++++++++++++++++++----------- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 821710a7fa3..1257b529038 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -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}) diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index d4f5de573f5..7b7b6122c02 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -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](): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 450c85967dd..6459fa3eb96 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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 diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 624dc63ce82..2fbcafe91aa 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} };