Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-104600: Make type.__type_params__ writable #104634

Merged
merged 5 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
carljm marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -813,10 +813,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
24 changes: 24 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6795,6 +6795,16 @@ 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))
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved

def test_non_generic_subscript(self):
# For backward compatibility, subscription works
# on arbitrary NamedTuple types.
Expand Down Expand Up @@ -7205,6 +7215,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
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
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