gh-104600: Make type.__type_params__ writable (#104634)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Jelle Zijlstra 2023-05-19 09:04:47 -07:00 committed by GitHub
parent dbe171e609
commit 8f1f3b9abd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 15 deletions

View File

@ -18,6 +18,7 @@ import re
import sys import sys
import traceback import traceback
import types import types
import typing
import unittest import unittest
import warnings import warnings
from contextlib import ExitStack from contextlib import ExitStack
@ -2485,6 +2486,17 @@ class TestType(unittest.TestCase):
A.__qualname__ = b'B' A.__qualname__ = b'B'
self.assertEqual(A.__qualname__, 'D.E') 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): def test_type_doc(self):
for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None: for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None:
A = type('A', (), {'__doc__': doc}) A = type('A', (), {'__doc__': doc})

View File

@ -816,10 +816,11 @@ class TypeParamsTypeParamsDunder(unittest.TestCase):
class ClassA[A](): class ClassA[A]():
pass pass
ClassA.__type_params__ = () ClassA.__type_params__ = ()
params = ClassA.__type_params__
""" """
with self.assertRaisesRegex(AttributeError, "attribute '__type_params__' of 'type' objects is not writable"): ns = run_code(code)
run_code(code) self.assertEqual(ns["params"], ())
def test_typeparams_dunder_function_01(self): def test_typeparams_dunder_function_01(self):
def outer[A, B](): def outer[A, B]():

View File

@ -6810,6 +6810,19 @@ class NamedTupleTests(BaseTestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
G[int, str] 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): def test_non_generic_subscript(self):
# For backward compatibility, subscription works # For backward compatibility, subscription works
# on arbitrary NamedTuple types. # on arbitrary NamedTuple types.
@ -7220,6 +7233,20 @@ class TypedDictTests(BaseTestCase):
{'a': typing.Optional[T], 'b': int, 'c': str} {'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): def test_generic_inheritance(self):
class A(TypedDict, Generic[T]): class A(TypedDict, Generic[T]):
a: T a: T

View File

@ -1460,18 +1460,6 @@ type_get_annotations(PyTypeObject *type, void *context)
return annotations; 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 static int
type_set_annotations(PyTypeObject *type, PyObject *value, void *context) type_set_annotations(PyTypeObject *type, PyObject *value, void *context)
{ {
@ -1502,6 +1490,34 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context)
return result; 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] /*[clinic input]
type.__instancecheck__ -> bool type.__instancecheck__ -> bool
@ -1548,7 +1564,7 @@ static PyGetSetDef type_getsets[] = {
{"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL}, {"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL},
{"__text_signature__", (getter)type_get_text_signature, NULL, NULL}, {"__text_signature__", (getter)type_get_text_signature, NULL, NULL},
{"__annotations__", (getter)type_get_annotations, (setter)type_set_annotations, 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} {0}
}; };