https://github.com/python/cpython/commit/0aeb040979c2e0237a3e6f0f5365567785969bdb
commit: 0aeb040979c2e0237a3e6f0f5365567785969bdb
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: mdboom <[email protected]>
date: 2026-05-11T12:08:01-04:00
summary:

[3.14] gh-112821: Fix rlcompleter failures on objects with descriptors 
(GH-149577) (#149657)

* gh-112821: Fix rlcompleter failures on objects with descriptors (GH-149577)

* gh-112821: Fix rlcompleter failures on objects with descriptors

* Confirm no accesses
(cherry picked from commit f23a1837d7156c4c478528321a423eae2b31e4bf)

Co-authored-by: Michael Droettboom <[email protected]>

* Add missing import

---------

Co-authored-by: Michael Droettboom <[email protected]>
Co-authored-by: Michael Droettboom <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
M Lib/rlcompleter.py
M Lib/test/test_rlcompleter.py

diff --git a/Lib/rlcompleter.py b/Lib/rlcompleter.py
index 23eb0020f42e8a..e75dd0a9e3dc19 100644
--- a/Lib/rlcompleter.py
+++ b/Lib/rlcompleter.py
@@ -34,6 +34,7 @@
 import inspect
 import keyword
 import re
+import types
 import __main__
 import warnings
 
@@ -178,14 +179,14 @@ def attr_matches(self, text):
                 if (word[:n] == attr and
                     not (noprefix and word[:n+1] == noprefix)):
                     match = "%s.%s" % (expr, word)
-                    if isinstance(getattr(type(thisobject), word, None),
-                                  property):
-                        # bpo-44752: thisobject.word is a method decorated by
-                        # `@property`. What follows applies a postfix if
-                        # thisobject.word is callable, but know we know that
-                        # this is not callable (because it is a property).
-                        # Also, getattr(thisobject, word) will evaluate the
-                        # property method, which is not desirable.
+
+                    class_attr = getattr(type(thisobject), word, None)
+                    if isinstance(
+                        class_attr,
+                        (property, types.GetSetDescriptorType, 
types.MemberDescriptorType)
+                    ) or (hasattr(class_attr, '__get__') and not 
callable(class_attr)):
+                        # Avoid evaluating descriptors, which could run
+                        # arbitrary code or raise exceptions.
                         matches.append(match)
                         continue
                     if (value := getattr(thisobject, word, None)) is not None:
diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py
index a8914953ce9eb4..e6d727d417b298 100644
--- a/Lib/test/test_rlcompleter.py
+++ b/Lib/test/test_rlcompleter.py
@@ -1,6 +1,7 @@
 import unittest
 from unittest.mock import patch
 import builtins
+import types
 import rlcompleter
 from test.support import MISSING_C_DOCSTRINGS
 
@@ -135,6 +136,57 @@ def bar(self):
         self.assertEqual(completer.complete('f.b', 0), 'f.bar')
         self.assertFalse(f.property_called)
 
+    def test_released_memoryview_completion_works(self):
+        mv = memoryview(b"abc")
+        mv.release()
+
+        self.assertIsInstance(type(mv).shape, types.GetSetDescriptorType)
+        self.assertIsInstance(type(mv).strides, types.GetSetDescriptorType)
+
+        completer = rlcompleter.Completer(dict(mv=mv))
+        matches = completer.attr_matches('mv.')
+
+        # These are getset descriptors on memoryview and should be completed
+        # without evaluating the released-memoryview getters.
+        self.assertIn('mv.shape', matches)
+        self.assertIn('mv.strides', matches)
+
+    def test_member_descriptor_not_evaluated(self):
+        class Foo:
+            __slots__ = ("boom",)
+            boom_accesses = 0
+
+            def __getattribute__(self, name):
+                if name == "boom":
+                    type(self).boom_accesses += 1
+                    raise RuntimeError("boom access should be skipped")
+                return super().__getattribute__(name)
+
+        self.assertIsInstance(Foo.boom, types.MemberDescriptorType)
+
+        completer = rlcompleter.Completer(dict(f=Foo()))
+        matches = completer.attr_matches('f.')
+        self.assertIn('f.boom', matches)
+        self.assertEqual(Foo.boom_accesses, 0)
+
+    def test_raising_descriptor_completion_works(self):
+        class ExplodingDescriptor:
+            def __init__(self):
+                self.instance_get_calls = 0
+
+            def __get__(self, obj, owner):
+                if obj is None:
+                    return self
+                self.instance_get_calls += 1
+                raise RuntimeError("descriptor getter exploded")
+
+        class Foo:
+            boom = ExplodingDescriptor()
+
+        completer = rlcompleter.Completer(dict(f=Foo()))
+        matches = completer.attr_matches('f.')
+        self.assertIn('f.boom', matches)
+        self.assertEqual(Foo.boom.instance_get_calls, 0)
 
     def test_uncreated_attr(self):
         # Attributes like properties and slots should be completed even when
diff --git 
a/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst 
b/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
new file mode 100644
index 00000000000000..cfbcde81493e22
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-08-15-08-35.gh-issue-112821.t9T1YD.rst
@@ -0,0 +1,4 @@
+In the REPL, autocompletion might run arbitrary code in the getter of a
+descriptor. If that getter raised an exception, autocompletion would fail to
+present any options for the entire object.  Autocompletion now works as
+expected for these objects.

_______________________________________________
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