Skip to content

Commit 80a9e0e

Browse files
authored
Update rlcompleter from 3.13.5 (#5990)
1 parent 559a7a5 commit 80a9e0e

File tree

2 files changed

+89
-25
lines changed

2 files changed

+89
-25
lines changed

Lib/rlcompleter.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@
3131

3232
import atexit
3333
import builtins
34+
import inspect
35+
import keyword
36+
import re
3437
import __main__
38+
import warnings
3539

3640
__all__ = ["Completer"]
3741

@@ -85,18 +89,25 @@ def complete(self, text, state):
8589
return None
8690

8791
if state == 0:
88-
if "." in text:
89-
self.matches = self.attr_matches(text)
90-
else:
91-
self.matches = self.global_matches(text)
92+
with warnings.catch_warnings(action="ignore"):
93+
if "." in text:
94+
self.matches = self.attr_matches(text)
95+
else:
96+
self.matches = self.global_matches(text)
9297
try:
9398
return self.matches[state]
9499
except IndexError:
95100
return None
96101

97102
def _callable_postfix(self, val, word):
98103
if callable(val):
99-
word = word + "("
104+
word += "("
105+
try:
106+
if not inspect.signature(val).parameters:
107+
word += ")"
108+
except ValueError:
109+
pass
110+
100111
return word
101112

102113
def global_matches(self, text):
@@ -106,18 +117,17 @@ def global_matches(self, text):
106117
defined in self.namespace that match.
107118
108119
"""
109-
import keyword
110120
matches = []
111121
seen = {"__builtins__"}
112122
n = len(text)
113-
for word in keyword.kwlist:
123+
for word in keyword.kwlist + keyword.softkwlist:
114124
if word[:n] == text:
115125
seen.add(word)
116126
if word in {'finally', 'try'}:
117127
word = word + ':'
118128
elif word not in {'False', 'None', 'True',
119129
'break', 'continue', 'pass',
120-
'else'}:
130+
'else', '_'}:
121131
word = word + ' '
122132
matches.append(word)
123133
for nspace in [self.namespace, builtins.__dict__]:
@@ -139,7 +149,6 @@ def attr_matches(self, text):
139149
with a __getattr__ hook is evaluated.
140150
141151
"""
142-
import re
143152
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
144153
if not m:
145154
return []
@@ -169,13 +178,20 @@ def attr_matches(self, text):
169178
if (word[:n] == attr and
170179
not (noprefix and word[:n+1] == noprefix)):
171180
match = "%s.%s" % (expr, word)
172-
try:
173-
val = getattr(thisobject, word)
174-
except Exception:
175-
pass # Include even if attribute not set
181+
if isinstance(getattr(type(thisobject), word, None),
182+
property):
183+
# bpo-44752: thisobject.word is a method decorated by
184+
# `@property`. What follows applies a postfix if
185+
# thisobject.word is callable, but know we know that
186+
# this is not callable (because it is a property).
187+
# Also, getattr(thisobject, word) will evaluate the
188+
# property method, which is not desirable.
189+
matches.append(match)
190+
continue
191+
if (value := getattr(thisobject, word, None)) is not None:
192+
matches.append(self._callable_postfix(value, match))
176193
else:
177-
match = self._callable_postfix(val, match)
178-
matches.append(match)
194+
matches.append(match)
179195
if matches or not noprefix:
180196
break
181197
if noprefix == '_':

Lib/test/test_rlcompleter.py

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittest.mock import patch
33
import builtins
44
import rlcompleter
5+
from test.support import MISSING_C_DOCSTRINGS
56

67
class CompleteMe:
78
""" Trivial class used in testing rlcompleter.Completer. """
@@ -40,21 +41,41 @@ def test_global_matches(self):
4041

4142
# test with a customized namespace
4243
self.assertEqual(self.completer.global_matches('CompleteM'),
43-
['CompleteMe('])
44+
['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()'])
4445
self.assertEqual(self.completer.global_matches('eg'),
4546
['egg('])
4647
# XXX: see issue5256
4748
self.assertEqual(self.completer.global_matches('CompleteM'),
48-
['CompleteMe('])
49+
['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()'])
4950

51+
# TODO: RUSTPYTHON
52+
@unittest.expectedFailure
5053
def test_attr_matches(self):
5154
# test with builtins namespace
5255
self.assertEqual(self.stdcompleter.attr_matches('str.s'),
5356
['str.{}('.format(x) for x in dir(str)
5457
if x.startswith('s')])
5558
self.assertEqual(self.stdcompleter.attr_matches('tuple.foospamegg'), [])
56-
expected = sorted({'None.%s%s' % (x, '(' if x != '__doc__' else '')
57-
for x in dir(None)})
59+
60+
def create_expected_for_none():
61+
if not MISSING_C_DOCSTRINGS:
62+
parentheses = ('__init_subclass__', '__class__')
63+
else:
64+
# When `--without-doc-strings` is used, `__class__`
65+
# won't have a known signature.
66+
parentheses = ('__init_subclass__',)
67+
68+
items = set()
69+
for x in dir(None):
70+
if x in parentheses:
71+
items.add(f'None.{x}()')
72+
elif x == '__doc__':
73+
items.add(f'None.{x}')
74+
else:
75+
items.add(f'None.{x}(')
76+
return sorted(items)
77+
78+
expected = create_expected_for_none()
5879
self.assertEqual(self.stdcompleter.attr_matches('None.'), expected)
5980
self.assertEqual(self.stdcompleter.attr_matches('None._'), expected)
6081
self.assertEqual(self.stdcompleter.attr_matches('None.__'), expected)
@@ -64,7 +85,7 @@ def test_attr_matches(self):
6485
['CompleteMe.spam'])
6586
self.assertEqual(self.completer.attr_matches('Completeme.egg'), [])
6687
self.assertEqual(self.completer.attr_matches('CompleteMe.'),
67-
['CompleteMe.mro(', 'CompleteMe.spam'])
88+
['CompleteMe.mro()', 'CompleteMe.spam'])
6889
self.assertEqual(self.completer.attr_matches('CompleteMe._'),
6990
['CompleteMe._ham'])
7091
matches = self.completer.attr_matches('CompleteMe.__')
@@ -81,17 +102,41 @@ def test_attr_matches(self):
81102
if x.startswith('s')])
82103

83104
def test_excessive_getattr(self):
84-
# Ensure getattr() is invoked no more than once per attribute
105+
"""Ensure getattr() is invoked no more than once per attribute"""
106+
107+
# note the special case for @property methods below; that is why
108+
# we use __dir__ and __getattr__ in class Foo to create a "magic"
109+
# class attribute 'bar'. This forces `getattr` to call __getattr__
110+
# (which is doesn't necessarily do).
85111
class Foo:
86112
calls = 0
113+
bar = ''
114+
def __getattribute__(self, name):
115+
if name == 'bar':
116+
self.calls += 1
117+
return None
118+
return super().__getattribute__(name)
119+
120+
f = Foo()
121+
completer = rlcompleter.Completer(dict(f=f))
122+
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
123+
self.assertEqual(f.calls, 1)
124+
125+
def test_property_method_not_called(self):
126+
class Foo:
127+
_bar = 0
128+
property_called = False
129+
87130
@property
88131
def bar(self):
89-
self.calls += 1
90-
return None
132+
self.property_called = True
133+
return self._bar
134+
91135
f = Foo()
92136
completer = rlcompleter.Completer(dict(f=f))
93137
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
94-
self.assertEqual(f.calls, 1)
138+
self.assertFalse(f.property_called)
139+
95140

96141
def test_uncreated_attr(self):
97142
# Attributes like properties and slots should be completed even when
@@ -114,6 +159,9 @@ def test_complete(self):
114159
self.assertEqual(completer.complete('el', 0), 'elif ')
115160
self.assertEqual(completer.complete('el', 1), 'else')
116161
self.assertEqual(completer.complete('tr', 0), 'try:')
162+
self.assertEqual(completer.complete('_', 0), '_')
163+
self.assertEqual(completer.complete('match', 0), 'match ')
164+
self.assertEqual(completer.complete('case', 0), 'case ')
117165

118166
def test_duplicate_globals(self):
119167
namespace = {
@@ -134,7 +182,7 @@ def test_duplicate_globals(self):
134182
# No opening bracket "(" because we overrode the built-in class
135183
self.assertEqual(completer.complete('memoryview', 0), 'memoryview')
136184
self.assertIsNone(completer.complete('memoryview', 1))
137-
self.assertEqual(completer.complete('Ellipsis', 0), 'Ellipsis(')
185+
self.assertEqual(completer.complete('Ellipsis', 0), 'Ellipsis()')
138186
self.assertIsNone(completer.complete('Ellipsis', 1))
139187

140188
if __name__ == '__main__':

0 commit comments

Comments
 (0)