Skip to content

Commit c0006fa

Browse files
authored
gh-155752: Do not crash when GenericAlias parameters change during substitution (#155761)
An alias argument can gain __typing_subst__ after __parameters__ has been cached, including during a preparation or substitution callback. Check that the argument is present before indexing the substitution arguments.
1 parent 94c6066 commit c0006fa

3 files changed

Lines changed: 30 additions & 2 deletions

File tree

Lib/test/test_typing.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6054,6 +6054,22 @@ class A:
60546054
with self.assertRaises(TypeError):
60556055
a[int]
60566056

6057+
def test_parameter_added_after_parameters_cached(self):
6058+
# gh-155752: GenericAlias parameters are cached before substitution, so
6059+
# an argument can gain __typing_subst__ after the tuple is calculated.
6060+
class Parameter:
6061+
pass
6062+
6063+
first = Parameter()
6064+
first.__typing_subst__ = lambda value: value
6065+
late = Parameter()
6066+
alias = types.GenericAlias(dict, (first, late))
6067+
self.assertEqual(alias.__parameters__, (first,))
6068+
late.__typing_subst__ = lambda value: value
6069+
6070+
with self.assertRaisesRegex(TypeError, "not found in __parameters__"):
6071+
alias[0]
6072+
60576073
def test_return_non_tuple_while_unpacking(self):
60586074
# GH-138497: GenericAlias objects didn't ensure that __typing_subst__ actually
60596075
# returned a tuple
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash when a :class:`types.GenericAlias` argument gains a
2+
``__typing_subst__`` hook after the alias parameters have been cached.

Objects/genericaliasobject.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,18 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje
525525
}
526526
if (subst) {
527527
Py_ssize_t iparam = tuple_index(parameters, nparams, arg);
528-
assert(iparam >= 0);
529-
arg = PyObject_CallOneArg(subst, argitems[iparam]);
528+
if (iparam < 0) {
529+
// __parameters__ may be stale if an argument gained
530+
// __typing_subst__ after the tuple was computed.
531+
PyErr_Format(PyExc_TypeError,
532+
"argument %R with __typing_subst__ was not found "
533+
"in __parameters__",
534+
arg);
535+
arg = NULL;
536+
}
537+
else {
538+
arg = PyObject_CallOneArg(subst, argitems[iparam]);
539+
}
530540
Py_DECREF(subst);
531541
}
532542
else {

0 commit comments

Comments
 (0)