Skip to content

Commit 2115d76

Browse files
sobolevnJelleZijlstra
andauthoredOct 11, 2024
gh-124787: Fix TypeAliasType and incorrect type_params (#124795)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent b3aa1b5 commit 2115d76

File tree

3 files changed

+133
-12
lines changed

3 files changed

+133
-12
lines changed
 

Diff for: ‎Lib/test/test_type_aliases.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from test.support import check_syntax_error, run_code
55
from test.typinganndata import mod_generics_cache
66

7-
from typing import Callable, TypeAliasType, TypeVar, get_args
7+
from typing import (
8+
Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, get_args,
9+
)
810

911

1012
class TypeParamsInvalidTest(unittest.TestCase):
@@ -225,6 +227,46 @@ def test_not_generic(self):
225227
):
226228
TA[int]
227229

230+
def test_type_params_order_with_defaults(self):
231+
HasNoDefaultT = TypeVar("HasNoDefaultT")
232+
WithDefaultT = TypeVar("WithDefaultT", default=int)
233+
234+
HasNoDefaultP = ParamSpec("HasNoDefaultP")
235+
WithDefaultP = ParamSpec("WithDefaultP", default=HasNoDefaultP)
236+
237+
HasNoDefaultTT = TypeVarTuple("HasNoDefaultTT")
238+
WithDefaultTT = TypeVarTuple("WithDefaultTT", default=HasNoDefaultTT)
239+
240+
for type_params in [
241+
(HasNoDefaultT, WithDefaultT),
242+
(HasNoDefaultP, WithDefaultP),
243+
(HasNoDefaultTT, WithDefaultTT),
244+
]:
245+
with self.subTest(type_params=type_params):
246+
TypeAliasType("A", int, type_params=type_params) # ok
247+
248+
msg = "follows default type parameter"
249+
for type_params in [
250+
(WithDefaultT, HasNoDefaultT),
251+
(WithDefaultP, HasNoDefaultP),
252+
(WithDefaultTT, HasNoDefaultTT),
253+
(WithDefaultT, HasNoDefaultP), # different types
254+
]:
255+
with self.subTest(type_params=type_params):
256+
with self.assertRaisesRegex(TypeError, msg):
257+
TypeAliasType("A", int, type_params=type_params)
258+
259+
def test_expects_type_like(self):
260+
T = TypeVar("T")
261+
262+
msg = "Expected a type param"
263+
with self.assertRaisesRegex(TypeError, msg):
264+
TypeAliasType("A", int, type_params=(1,))
265+
with self.assertRaisesRegex(TypeError, msg):
266+
TypeAliasType("A", int, type_params=(1, 2))
267+
with self.assertRaisesRegex(TypeError, msg):
268+
TypeAliasType("A", int, type_params=(T, 2))
269+
228270
def test_keywords(self):
229271
TA = TypeAliasType(name="TA", value=int)
230272
self.assertEqual(TA.__name__, "TA")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :class:`typing.TypeAliasType` with incorrect ``type_params`` argument.
2+
Now it raises a :exc:`TypeError` when a type parameter without a default
3+
follows one with a default, and when an entry in the ``type_params`` tuple
4+
is not a type parameter object.

Diff for: ‎Objects/typevarobject.c

+86-11
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,24 @@ _Py_make_typevartuple(PyThreadState *Py_UNUSED(ignored), PyObject *v)
17991799
return (PyObject *)typevartuple_alloc(v, NULL, NULL);
18001800
}
18011801

1802+
static PyObject *
1803+
get_type_param_default(PyThreadState *ts, PyObject *typeparam) {
1804+
// Does not modify refcount of existing objects.
1805+
if (Py_IS_TYPE(typeparam, ts->interp->cached_objects.typevar_type)) {
1806+
return typevar_default((typevarobject *)typeparam, NULL);
1807+
}
1808+
else if (Py_IS_TYPE(typeparam, ts->interp->cached_objects.paramspec_type)) {
1809+
return paramspec_default((paramspecobject *)typeparam, NULL);
1810+
}
1811+
else if (Py_IS_TYPE(typeparam, ts->interp->cached_objects.typevartuple_type)) {
1812+
return typevartuple_default((typevartupleobject *)typeparam, NULL);
1813+
}
1814+
else {
1815+
PyErr_Format(PyExc_TypeError, "Expected a type param, got %R", typeparam);
1816+
return NULL;
1817+
}
1818+
}
1819+
18021820
static void
18031821
typealias_dealloc(PyObject *self)
18041822
{
@@ -1906,25 +1924,75 @@ static PyGetSetDef typealias_getset[] = {
19061924
{0}
19071925
};
19081926

1909-
static typealiasobject *
1910-
typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value,
1911-
PyObject *value, PyObject *module)
1912-
{
1913-
typealiasobject *ta = PyObject_GC_New(typealiasobject, &_PyTypeAlias_Type);
1914-
if (ta == NULL) {
1927+
static PyObject *
1928+
typealias_check_type_params(PyObject *type_params, int *err) {
1929+
// Can return type_params or NULL without exception set.
1930+
// Does not change the reference count of type_params,
1931+
// sets `*err` to 1 when error happens and sets an exception,
1932+
// otherwise `*err` is set to 0.
1933+
*err = 0;
1934+
if (type_params == NULL) {
19151935
return NULL;
19161936
}
1917-
ta->name = Py_NewRef(name);
1937+
1938+
assert(PyTuple_Check(type_params));
1939+
Py_ssize_t length = PyTuple_GET_SIZE(type_params);
1940+
if (!length) { // 0-length tuples are the same as `NULL`.
1941+
return NULL;
1942+
}
1943+
1944+
PyThreadState *ts = _PyThreadState_GET();
1945+
int default_seen = 0;
1946+
for (Py_ssize_t index = 0; index < length; index++) {
1947+
PyObject *type_param = PyTuple_GET_ITEM(type_params, index);
1948+
PyObject *dflt = get_type_param_default(ts, type_param);
1949+
if (dflt == NULL) {
1950+
*err = 1;
1951+
return NULL;
1952+
}
1953+
if (dflt == &_Py_NoDefaultStruct) {
1954+
if (default_seen) {
1955+
*err = 1;
1956+
PyErr_Format(PyExc_TypeError,
1957+
"non-default type parameter '%R' "
1958+
"follows default type parameter",
1959+
type_param);
1960+
return NULL;
1961+
}
1962+
} else {
1963+
default_seen = 1;
1964+
Py_DECREF(dflt);
1965+
}
1966+
}
1967+
1968+
return type_params;
1969+
}
1970+
1971+
static PyObject *
1972+
typelias_convert_type_params(PyObject *type_params)
1973+
{
19181974
if (
19191975
type_params == NULL
19201976
|| Py_IsNone(type_params)
19211977
|| (PyTuple_Check(type_params) && PyTuple_GET_SIZE(type_params) == 0)
19221978
) {
1923-
ta->type_params = NULL;
1979+
return NULL;
19241980
}
19251981
else {
1926-
ta->type_params = Py_NewRef(type_params);
1982+
return type_params;
19271983
}
1984+
}
1985+
1986+
static typealiasobject *
1987+
typealias_alloc(PyObject *name, PyObject *type_params, PyObject *compute_value,
1988+
PyObject *value, PyObject *module)
1989+
{
1990+
typealiasobject *ta = PyObject_GC_New(typealiasobject, &_PyTypeAlias_Type);
1991+
if (ta == NULL) {
1992+
return NULL;
1993+
}
1994+
ta->name = Py_NewRef(name);
1995+
ta->type_params = Py_XNewRef(type_params);
19281996
ta->compute_value = Py_XNewRef(compute_value);
19291997
ta->value = Py_XNewRef(value);
19301998
ta->module = Py_XNewRef(module);
@@ -2002,11 +2070,18 @@ typealias_new_impl(PyTypeObject *type, PyObject *name, PyObject *value,
20022070
PyErr_SetString(PyExc_TypeError, "type_params must be a tuple");
20032071
return NULL;
20042072
}
2073+
2074+
int err = 0;
2075+
PyObject *checked_params = typealias_check_type_params(type_params, &err);
2076+
if (err) {
2077+
return NULL;
2078+
}
2079+
20052080
PyObject *module = caller();
20062081
if (module == NULL) {
20072082
return NULL;
20082083
}
2009-
PyObject *ta = (PyObject *)typealias_alloc(name, type_params, NULL, value,
2084+
PyObject *ta = (PyObject *)typealias_alloc(name, checked_params, NULL, value,
20102085
module);
20112086
Py_DECREF(module);
20122087
return ta;
@@ -2072,7 +2147,7 @@ _Py_make_typealias(PyThreadState* unused, PyObject *args)
20722147
assert(PyTuple_GET_SIZE(args) == 3);
20732148
PyObject *name = PyTuple_GET_ITEM(args, 0);
20742149
assert(PyUnicode_Check(name));
2075-
PyObject *type_params = PyTuple_GET_ITEM(args, 1);
2150+
PyObject *type_params = typelias_convert_type_params(PyTuple_GET_ITEM(args, 1));
20762151
PyObject *compute_value = PyTuple_GET_ITEM(args, 2);
20772152
assert(PyFunction_Check(compute_value));
20782153
return (PyObject *)typealias_alloc(name, type_params, compute_value, NULL, NULL);

0 commit comments

Comments
 (0)