https://github.com/python/cpython/commit/d3b6bb1125576fe93e05ba2f4635804b2ed7ba6a
commit: d3b6bb1125576fe93e05ba2f4635804b2ed7ba6a
branch: 3.13
author: sobolevn <[email protected]>
committer: sobolevn <[email protected]>
date: 2025-09-08T10:03:03Z
summary:

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

(cherry picked from commit b0420b505e6c9bbc8418e0f6240835ea777137b5)

Co-authored-by: Emma Smith <[email protected]>

files:
A Misc/NEWS.d/next/Core and 
Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst
M Lib/test/test_genericalias.py
M Objects/genericaliasobject.c

diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py
index 04122fbdd0ae80..4a87e2bf7a1864 100644
--- a/Lib/test/test_genericalias.py
+++ b/Lib/test/test_genericalias.py
@@ -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:
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst b/Misc/NEWS.d/next/Core 
and Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst
new file mode 100644
index 00000000000000..968e92c94c1ffc
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst 
@@ -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`.
diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c
index efe18adb4a8d10..bf1838061e9eb1 100644
--- a/Objects/genericaliasobject.c
+++ b/Objects/genericaliasobject.c
@@ -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);
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to