Skip to content

Commit 7d439f8

Browse files
committed
gh-124787: Fix TypeAliasType and incorrect type_params
1 parent 6f4d64b commit 7d439f8

File tree

3 files changed

+133
-12
lines changed

3 files changed

+133
-12
lines changed

Lib/test/test_type_aliases.py

Lines changed: 43 additions & 1 deletion
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")
Lines changed: 4 additions & 0 deletions
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: 1. A type var without default follows
3+
a type var with default 2. Not a typelike parameter passed in
4+
``type_params`` tuple

Objects/typevarobject.c

Lines changed: 86 additions & 11 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy