Skip to content

Commit d3b6bb1

Browse files
sobolevnemmatyping
andauthored
[3.13] pythongh-105487: Fix __dir__ entries of GenericAlias (pythonGH-138578) (python#138640)
(cherry picked from commit b0420b5) Co-authored-by: Emma Smith <[email protected]>
1 parent 614c493 commit d3b6bb1

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
@@ -390,7 +390,10 @@ def __deepcopy__(self, memo):
390390
aliases = [
391391
GenericAlias(list, T),
392392
GenericAlias(deque, T),
393-
GenericAlias(X, T)
393+
GenericAlias(X, T),
394+
X[T],
395+
list[T],
396+
deque[T],
394397
] + _UNPACKED_TUPLES
395398
for alias in aliases:
396399
with self.subTest(alias=alias):
@@ -420,10 +423,26 @@ def test_union_generic(self):
420423
self.assertEqual(a.__parameters__, (T,))
421424

422425
def test_dir(self):
423-
dir_of_gen_alias = set(dir(list[int]))
426+
ga = list[int]
427+
dir_of_gen_alias = set(dir(ga))
424428
self.assertTrue(dir_of_gen_alias.issuperset(dir(list)))
425-
for generic_alias_property in ("__origin__", "__args__", "__parameters__"):
426-
self.assertIn(generic_alias_property, dir_of_gen_alias)
429+
for generic_alias_property in (
430+
"__origin__", "__args__", "__parameters__",
431+
"__unpacked__",
432+
):
433+
with self.subTest(generic_alias_property=generic_alias_property):
434+
self.assertIn(generic_alias_property, dir_of_gen_alias)
435+
for blocked in (
436+
"__bases__",
437+
"__copy__",
438+
"__deepcopy__",
439+
):
440+
with self.subTest(blocked=blocked):
441+
self.assertNotIn(blocked, dir_of_gen_alias)
442+
443+
for entry in dir_of_gen_alias:
444+
with self.subTest(entry=entry):
445+
getattr(ga, entry) # must not raise `AttributeError`
427446

428447
def test_weakref(self):
429448
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
@@ -626,7 +626,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
626626

627627
static const char* const attr_exceptions[] = {
628628
"__class__",
629-
"__bases__",
630629
"__origin__",
631630
"__args__",
632631
"__unpacked__",
@@ -635,6 +634,11 @@ static const char* const attr_exceptions[] = {
635634
"__mro_entries__",
636635
"__reduce_ex__", // needed so we don't look up object.__reduce_ex__
637636
"__reduce__",
637+
NULL,
638+
};
639+
640+
static const char* const attr_blocked[] = {
641+
"__bases__",
638642
"__copy__",
639643
"__deepcopy__",
640644
NULL,
@@ -645,15 +649,29 @@ ga_getattro(PyObject *self, PyObject *name)
645649
{
646650
gaobject *alias = (gaobject *)self;
647651
if (PyUnicode_Check(name)) {
652+
// When we check blocked attrs, we don't allow to proxy them to `__origin__`.
653+
// Otherwise, we can break existing code.
654+
for (const char * const *p = attr_blocked; ; p++) {
655+
if (*p == NULL) {
656+
break;
657+
}
658+
if (_PyUnicode_EqualToASCIIString(name, *p)) {
659+
goto generic_getattr;
660+
}
661+
}
662+
663+
// When we see own attrs, it has a priority over `__origin__`'s attr.
648664
for (const char * const *p = attr_exceptions; ; p++) {
649665
if (*p == NULL) {
650666
return PyObject_GetAttr(alias->origin, name);
651667
}
652668
if (_PyUnicode_EqualToASCIIString(name, *p)) {
653-
break;
669+
goto generic_getattr;
654670
}
655671
}
656672
}
673+
674+
generic_getattr:
657675
return PyObject_GenericGetAttr(self, name);
658676
}
659677

0 commit comments

Comments
 (0)