Skip to content

Commit 09fdd21

Browse files
miss-islingtonsobolevnemmatyping
authored
[3.14] gh-105487: Fix __dir__ entries of GenericAlias (GH-138578) (GH-138629)
(cherry picked from commit b0420b5) Co-authored-by: sobolevn <mail@sobolevn.me> Co-authored-by: Emma Smith <emma@emmatyping.dev>
1 parent 63bd8cf commit 09fdd21

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

Lib/test/test_genericalias.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,10 @@ def __deepcopy__(self, memo):
402402
aliases = [
403403
GenericAlias(list, T),
404404
GenericAlias(deque, T),
405-
GenericAlias(X, T)
405+
GenericAlias(X, T),
406+
X[T],
407+
list[T],
408+
deque[T],
406409
] + _UNPACKED_TUPLES
407410
for alias in aliases:
408411
with self.subTest(alias=alias):
@@ -432,10 +435,26 @@ def test_union_generic(self):
432435
self.assertEqual(a.__parameters__, (T,))
433436

434437
def test_dir(self):
435-
dir_of_gen_alias = set(dir(list[int]))
438+
ga = list[int]
439+
dir_of_gen_alias = set(dir(ga))
436440
self.assertTrue(dir_of_gen_alias.issuperset(dir(list)))
437-
for generic_alias_property in ("__origin__", "__args__", "__parameters__"):
438-
self.assertIn(generic_alias_property, dir_of_gen_alias)
441+
for generic_alias_property in (
442+
"__origin__", "__args__", "__parameters__",
443+
"__unpacked__",
444+
):
445+
with self.subTest(generic_alias_property=generic_alias_property):
446+
self.assertIn(generic_alias_property, dir_of_gen_alias)
447+
for blocked in (
448+
"__bases__",
449+
"__copy__",
450+
"__deepcopy__",
451+
):
452+
with self.subTest(blocked=blocked):
453+
self.assertNotIn(blocked, dir_of_gen_alias)
454+
455+
for entry in dir_of_gen_alias:
456+
with self.subTest(entry=entry):
457+
getattr(ga, entry) # must not raise `AttributeError`
439458

440459
def test_weakref(self):
441460
for t in self.generic_types:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove non-existent :meth:`~object.__copy__`, :meth:`~object.__deepcopy__`, and :attr:`~type.__bases__` from the :meth:`~object.__dir__` entries of :class:`types.GenericAlias`.

Objects/genericaliasobject.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
648648

649649
static const char* const attr_exceptions[] = {
650650
"__class__",
651-
"__bases__",
652651
"__origin__",
653652
"__args__",
654653
"__unpacked__",
@@ -657,6 +656,11 @@ static const char* const attr_exceptions[] = {
657656
"__mro_entries__",
658657
"__reduce_ex__", // needed so we don't look up object.__reduce_ex__
659658
"__reduce__",
659+
NULL,
660+
};
661+
662+
static const char* const attr_blocked[] = {
663+
"__bases__",
660664
"__copy__",
661665
"__deepcopy__",
662666
NULL,
@@ -667,15 +671,29 @@ ga_getattro(PyObject *self, PyObject *name)
667671
{
668672
gaobject *alias = (gaobject *)self;
669673
if (PyUnicode_Check(name)) {
674+
// When we check blocked attrs, we don't allow to proxy them to `__origin__`.
675+
// Otherwise, we can break existing code.
676+
for (const char * const *p = attr_blocked; ; p++) {
677+
if (*p == NULL) {
678+
break;
679+
}
680+
if (_PyUnicode_EqualToASCIIString(name, *p)) {
681+
goto generic_getattr;
682+
}
683+
}
684+
685+
// When we see own attrs, it has a priority over `__origin__`'s attr.
670686
for (const char * const *p = attr_exceptions; ; p++) {
671687
if (*p == NULL) {
672688
return PyObject_GetAttr(alias->origin, name);
673689
}
674690
if (_PyUnicode_EqualToASCIIString(name, *p)) {
675-
break;
691+
goto generic_getattr;
676692
}
677693
}
678694
}
695+
696+
generic_getattr:
679697
return PyObject_GenericGetAttr(self, name);
680698
}
681699

0 commit comments

Comments
 (0)