Skip to content

Commit

Permalink
pythongh-104600: Make type.__type_params__ writable (python#104634)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
JelleZijlstra and AlexWaygood authored May 19, 2023
1 parent dbe171e commit 8f1f3b9
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 15 deletions.
12 changes: 12 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import sys
import traceback
import types
import typing
import unittest
import warnings
from contextlib import ExitStack
Expand Down Expand Up @@ -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})
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/test_type_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]():
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
42 changes: 29 additions & 13 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
};

Expand Down

0 comments on commit 8f1f3b9

Please sign in to comment.