Skip to content

Commit 23769f3

Browse files
committed
gh-153506: Guard IDLE completions against a non-iterable __all__
The ATTRS empty-prefix branch of autocomplete.fetch_completions() evaluated __main__.__all__ without a guard, while the non-empty sibling branch already caught failures, so a non-iterable __all__ in the interactive namespace raised TypeError on Tab. Catch the failure and return empty completion lists instead, leaving the sibling branch untouched.
1 parent d83d267 commit 23769f3

3 files changed

Lines changed: 23 additions & 9 deletions

File tree

Lib/idlelib/autocomplete.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,18 @@ def fetch_completions(self, what, mode):
180180
else:
181181
if mode == ATTRS:
182182
if what == "": # Main module names.
183-
namespace = {**__main__.__builtins__.__dict__,
184-
**__main__.__dict__}
185-
bigl = eval("dir()", namespace)
186-
bigl.extend(completion_kwds)
187-
bigl.sort()
188-
if "__all__" in bigl:
189-
smalll = sorted(eval("__all__", namespace))
190-
else:
191-
smalll = [s for s in bigl if s[:1] != '_']
183+
try:
184+
namespace = {**__main__.__builtins__.__dict__,
185+
**__main__.__dict__}
186+
bigl = eval("dir()", namespace)
187+
bigl.extend(completion_kwds)
188+
bigl.sort()
189+
if "__all__" in bigl:
190+
smalll = sorted(eval("__all__", namespace))
191+
else:
192+
smalll = [s for s in bigl if s[:1] != '_']
193+
except Exception: # a broken __all__ must not abort completion
194+
return [], []
192195
else:
193196
try:
194197
entity = self.get_entity(what)

Lib/idlelib/idle_test/test_autocomplete.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,15 @@ def _listdir(path):
277277
self.assertEqual(s, ['monty', 'python'])
278278
self.assertEqual(b, ['.hidden', 'monty', 'python'])
279279

280+
def test_fetch_completions_bad_all(self):
281+
# gh-153506: a non-iterable __main__.__all__ must not abort
282+
# module-name completion at an empty prefix.
283+
acp = self.autocomplete
284+
with patch.dict('__main__.__dict__', {'__all__': None}):
285+
self.assertEqual(acp.fetch_completions('', ac.ATTRS), ([], []))
286+
# A valid namespace still yields real completions.
287+
self.assertIn('__name__', acp.fetch_completions('', ac.ATTRS)[1])
288+
280289
def test_get_entity(self):
281290
# Test that a name is in the namespace of sys.modules and
282291
# __main__.__dict__.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
IDLE no longer fails to show module-name completions when the interactive
2+
namespace has a non-iterable ``__all__``.

0 commit comments

Comments
 (0)