Skip to content

Commit b0420b5

Browse files
sobolevnemmatyping
andauthored
gh-105487: Fix __dir__ entries of GenericAlias (#138578)
Co-authored-by: Emma Smith <[email protected]>
1 parent d7b9ea5 commit b0420b5

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
@@ -636,7 +636,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
636636

637637
static const char* const attr_exceptions[] = {
638638
"__class__",
639-
"__bases__",
640639
"__origin__",
641640
"__args__",
642641
"__unpacked__",
@@ -645,6 +644,11 @@ static const char* const attr_exceptions[] = {
645644
"__mro_entries__",
646645
"__reduce_ex__", // needed so we don't look up object.__reduce_ex__
647646
"__reduce__",
647+
NULL,
648+
};
649+
650+
static const char* const attr_blocked[] = {
651+
"__bases__",
648652
"__copy__",
649653
"__deepcopy__",
650654
NULL,
@@ -655,15 +659,29 @@ ga_getattro(PyObject *self, PyObject *name)
655659
{
656660
gaobject *alias = (gaobject *)self;
657661
if (PyUnicode_Check(name)) {
662+
// When we check blocked attrs, we don't allow to proxy them to `__origin__`.
663+
// Otherwise, we can break existing code.
664+
for (const char * const *p = attr_blocked; ; p++) {
665+
if (*p == NULL) {
666+
break;
667+
}
668+
if (_PyUnicode_EqualToASCIIString(name, *p)) {
669+
goto generic_getattr;
670+
}
671+
}
672+
673+
// When we see own attrs, it has a priority over `__origin__`'s attr.
658674
for (const char * const *p = attr_exceptions; ; p++) {
659675
if (*p == NULL) {
660676
return PyObject_GetAttr(alias->origin, name);
661677
}
662678
if (_PyUnicode_EqualToASCIIString(name, *p)) {
663-
break;
679+
goto generic_getattr;
664680
}
665681
}
666682
}
683+
684+
generic_getattr:
667685
return PyObject_GenericGetAttr(self, name);
668686
}
669687

0 commit comments

Comments
 (0)