]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-105487: Fix `__dir__` entries of `GenericAlias` (#138578)
authorsobolevn <mail@sobolevn.me>
Sun, 7 Sep 2025 21:33:24 +0000 (00:33 +0300)
committerGitHub <noreply@github.com>
Sun, 7 Sep 2025 21:33:24 +0000 (00:33 +0300)
Co-authored-by: Emma Smith <emma@emmatyping.dev>
Lib/test/test_genericalias.py
Misc/NEWS.d/next/Core_and_Builtins/2025-09-06-13-53-33.gh-issue-105487.a43YaY.rst [new file with mode: 0644]
Objects/genericaliasobject.c

index 7601cb00ff6685452d66d8530bfb28e53adf3b82..4e08adaca05cdd41a91e97293447bb4c5edd1749 100644 (file)
@@ -402,7 +402,10 @@ class BaseTest(unittest.TestCase):
         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):
@@ -432,10 +435,26 @@ class BaseTest(unittest.TestCase):
         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 (file)
index 0000000..968e92c
--- /dev/null
@@ -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`.
index 3bb961aa2b619d982c7b4679395fd3d8d4c2ea13..b3ff933c9b584e9aa0899b72e56c493f8bce907b 100644 (file)
@@ -636,7 +636,6 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
 
 static const char* const attr_exceptions[] = {
     "__class__",
-    "__bases__",
     "__origin__",
     "__args__",
     "__unpacked__",
@@ -645,6 +644,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,
@@ -655,15 +659,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);
 }