[3.13] gh-105487: Fix __dir__ entries of GenericAlias (GH-138578) (#138640)

(cherry picked from commit b0420b505e)

Co-authored-by: Emma Smith <emma@emmatyping.dev>
This commit is contained in:
sobolevn 2025-09-08 13:03:03 +03:00 committed by GitHub
parent 614c493406
commit d3b6bb1125
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 6 deletions

View file

@ -390,7 +390,10 @@ def __deepcopy__(self, memo):
aliases = [
GenericAlias(list, T),
GenericAlias(deque, T),
GenericAlias(X, T)
GenericAlias(X, T),
X[T],
list[T],
deque[T],
] + _UNPACKED_TUPLES
for alias in aliases:
with self.subTest(alias=alias):
@ -420,10 +423,26 @@ def test_union_generic(self):
self.assertEqual(a.__parameters__, (T,))
def test_dir(self):
dir_of_gen_alias = set(dir(list[int]))
ga = list[int]
dir_of_gen_alias = set(dir(ga))
self.assertTrue(dir_of_gen_alias.issuperset(dir(list)))
for generic_alias_property in ("__origin__", "__args__", "__parameters__"):
self.assertIn(generic_alias_property, dir_of_gen_alias)
for generic_alias_property in (
"__origin__", "__args__", "__parameters__",
"__unpacked__",
):
with self.subTest(generic_alias_property=generic_alias_property):
self.assertIn(generic_alias_property, dir_of_gen_alias)
for blocked in (
"__bases__",
"__copy__",
"__deepcopy__",
):
with self.subTest(blocked=blocked):
self.assertNotIn(blocked, dir_of_gen_alias)
for entry in dir_of_gen_alias:
with self.subTest(entry=entry):
getattr(ga, entry) # must not raise `AttributeError`
def test_weakref(self):
for t in self.generic_types:

View file

@ -0,0 +1 @@
Remove non-existent :meth:`~object.__copy__`, :meth:`~object.__deepcopy__`, and :attr:`~type.__bases__` from the :meth:`~object.__dir__` entries of :class:`types.GenericAlias`.

View file

@ -626,7 +626,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
static const char* const attr_exceptions[] = {
"__class__",
"__bases__",
"__origin__",
"__args__",
"__unpacked__",
@ -635,6 +634,11 @@ static const char* const attr_exceptions[] = {
"__mro_entries__",
"__reduce_ex__", // needed so we don't look up object.__reduce_ex__
"__reduce__",
NULL,
};
static const char* const attr_blocked[] = {
"__bases__",
"__copy__",
"__deepcopy__",
NULL,
@ -645,15 +649,29 @@ ga_getattro(PyObject *self, PyObject *name)
{
gaobject *alias = (gaobject *)self;
if (PyUnicode_Check(name)) {
// When we check blocked attrs, we don't allow to proxy them to `__origin__`.
// Otherwise, we can break existing code.
for (const char * const *p = attr_blocked; ; p++) {
if (*p == NULL) {
break;
}
if (_PyUnicode_EqualToASCIIString(name, *p)) {
goto generic_getattr;
}
}
// When we see own attrs, it has a priority over `__origin__`'s attr.
for (const char * const *p = attr_exceptions; ; p++) {
if (*p == NULL) {
return PyObject_GetAttr(alias->origin, name);
}
if (_PyUnicode_EqualToASCIIString(name, *p)) {
break;
goto generic_getattr;
}
}
}
generic_getattr:
return PyObject_GenericGetAttr(self, name);
}