Skip to content

Instantly share code, notes, and snippets.

@devdanzin
Last active July 27, 2026 08:46
Show Gist options
  • Select an option

  • Save devdanzin/c2d03bc167079b6fb72a600542d726c7 to your computer and use it in GitHub Desktop.

Select an option

Save devdanzin/c2d03bc167079b6fb72a600542d726c7 to your computer and use it in GitHub Desktop.
idlelib #154760: IDLE completions and calltips

IDLE completions and calltips

autocomplete.py, autocomplete_w.py, calltip.py, CPython main 993a0c65a7b.

Tracked in the umbrella issue python/cpython#154760 — one of 69 idlelib findings from a two-pass review with code-review-toolkit.

Each numbered heading below is one finding and has its own anchor, so the umbrella table links straight to it. The numbers are row numbers in that table, not identifiers — when referring to one of these, cite #154760 and the row, not the bare number.

Do not start work on a finding until a maintainer has cleared it in #154760. Every row there starts at Under analysis, which means not yet cleared.

AI disclaimer: these reports were drafted with Claude Code. Every cited line was re-read in the tree, every "Verified" block was produced by running the code, and the prior-art search was run against the full python/cpython tracker before publishing.

How to read a finding. Where is the line as of the commit above and will drift. Guarded twin is a place in idlelib that already handles the same situation correctly — it is both the evidence that this is a defect by the project's own standard and the shape of the fix. Prior art records what the tracker already says.


4 — Autocomplete's mode filter never applies to ATTRS because ATTRS == 0

FIX · reproduced · shape falsy-test-on-a-zero-valued-enum-member

Where: autocomplete.py:19, autocomplete.py:117, autocomplete.py:134

What happens. ATTRS, FILES = 0, 1 and the filter is (not mode or mode == FILES). not 0 is True, so an ATTRS-mode request never restricts anything while the FILES side is protected. Typing f("data. fires TRY_A in ATTRS mode; line 117 wins, fetch_completions('', FILES) runs os.listdir('.'), and a directory listing pops up inside a string literal. The docstring at :105 promises the opposite.

Guarded twin. The mirror TRY_F path in code correctly returns None.

Suggested fix. mode is None instead of not mode.

Prior art — no matching report found. Searched the cached python/cpython tracker corpus (1626 topic-IDLE issues + 281 unlabelled idlelib issues + 573 PRs + 86 idle.rst items; 2503 unique) for this defect's terms and for its file/function names. No matching report.

Note. Inspired the falsy-check-for-none-default shape, which does NOT catch this instance: mode is reassigned in the body, and the int-enum-with-zero variant needs constant tracking the scanner does not do.


6 — get_argspec guards two of its three touches of a user object

FIX · reproduced · shape except-exception-too-broad

Where: calltip.py:189

What happens. doc = inspect.getdoc(ob) is bare, and inspect.getdoc catches only AttributeError/TypeError. A class whose doc is a property that raises (metaclass-computed docs, lazy proxies) makes typing ( after it raise into the Tk callback. The sibling touches at :164-167 and :172-179 are both guarded.

Guarded twin. calltip.py:164-167 — except BaseException: # Buggy user object could raise anything.

Suggested fix. Wrap the getdoc call in the same guard the two sibling touches use.

Prior art — no matching report found. Searched the cached python/cpython tracker corpus (1626 topic-IDLE issues + 281 unlabelled idlelib issues + 573 PRs + 86 idle.rst items; 2503 unique) for this defect's terms and for its file/function names. No matching report.

Note. test_calltip.py:47-52 states the invariant outright: 'must return a string, even if blank ... a red buildbot is better than a user crash (as has happened)'.


10 — A failed is_active check permanently disables the completion window's Configure handler

FIX · confirmed by reading · shape flag-not-reset-on-early-exit

Where: autocomplete_w.py:238

What happens. self.is_configuring is set at 238 and reset at 284, but if not self.is_active(): return at 240 skips the reset. The recursion guard at 234 then blocks every subsequent event for that completion window, so the list stops repositioning itself.

Guarded twin. debugger.py:153-156 and three other try/finally flag twins in idlelib.

Suggested fix. try/finally around the body.

Prior art — novel; the change that introduced it is identified. git blame: self.is_configuring = True (autocomplete_w.py:238) is Tal Einat's 2019 recursion guard, 71662dc2f12a. The early if not self.is_active(): return one line below predates it by fourteen years (2005). The new flag is therefore set before a return that cannot clear it. PR #26404 later added TclError handling to the same function without touching this.

python/cpython#26404

Note. Found by the scanner, not by either agent — the shape sweep caught an instance depth-first review missed.


29 — and binds tighter than or, so the modifier guard covers one completion mode of two

FIX · reproduced · shape guard-conjoined-with-a-preference-flag

Where: autocomplete_w.py:363-368

What happens. The condition parses as ATTRS_arm or (FILES_arm and not_modified), so the ATTRS arm ignores the modifier guard entirely. Verified by evaluating the expression: ATTRS+space with CONTROL held returns True where FILES+slash with CONTROL held correctly returns False. IDLE's own default keysets bind force-open-completions= (config-keys.def:60,120,180,240), so with the completion window open in ATTRS mode, Control-Space commits the highlighted completion into the buffer instead of being ignored — which the function's own later branch at :418 ('A modifier key, so ignore') states was the intent.

Verified:

# Evaluating the elif condition directly:
ATTRS + space, no modifier  -> True
ATTRS + space, CONTROL held -> True    <- guard skipped
FILES + slash, CONTROL held -> False   (guard correctly applies)

Guarded twin. autocomplete_w.py:333-336, twenty lines above in the same function: the identical guard, correctly parenthesised so every arm honours it.

Suggested fix. Two parentheses around the two-arm disjunction.

Prior art — no matching report found. Searched the cached python/cpython tracker corpus (1626 topic-IDLE issues + 281 unlabelled idlelib issues + 573 PRs + 86 idle.rst items; 2503 unique) for this defect's terms and for its file/function names. No matching report.


68 — The completion popup teardown is unguarded where the calltip's identical teardown guards every step

FIX · confirmed by reading · shape fix-not-propagated-to-sibling-path

Where: autocomplete_w.py:454-491

What happens. hide_window runs four event_delete calls, six unbind calls and three destroy calls with no exception handling, and sets self.autocompletewindow = None only on the last line. If any step raises, is_active() stays True and the completion list is stranded on screen, unresponsive, until the window is closed.

Guarded twin. calltip_w.hidetip:123-144 wraps each teardown step in try/except and even names the cause — 'ValueError may be raised by MultiCall'.

Suggested fix. Guard each step and clear the handle first, as hidetip does.

Prior art — no matching report found. Searched the cached python/cpython tracker corpus (1626 topic-IDLE issues + 281 unlabelled idlelib issues + 573 PRs + 86 idle.rst items; 2503 unique) for this defect's terms and for its file/function names. No matching report.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment