Skip to content

fix: describe-bindings freeze and crash#2190

Merged
theangelperalta merged 3 commits into
lem-project:mainfrom
theangelperalta:fix-describe-bindings-freeze-crash
May 19, 2026
Merged

fix: describe-bindings freeze and crash#2190
theangelperalta merged 3 commits into
lem-project:mainfrom
theangelperalta:fix-describe-bindings-freeze-crash

Conversation

@theangelperalta

Copy link
Copy Markdown
Collaborator

Fix: describe-bindings freezes and crashes

Summary

Running M-x describe-bindings would freeze LEM (infinite loop) and, once that was fixed, crash with a type error on buffers with transient keybindings.

Root Cause

Two bugs, both regressions from PR #2100 ("rewrite keymap system, introduce transient-like functionality"), merged 2026-04-24.

Bug 1: Infinite loop in describe-bindings-internal

File: src/commands/help.lisp

Commit 3793971c removed the line (setf keymap (keymap-parent keymap)) from the loop body in describe-bindings-internal because keymap-parent no longer existed after the keymap rewrite. However, the surrounding (loop :while keymap ...) was left in place, so keymap was never set to nil and the loop never terminated.

Fix: Added (setf keymap nil) at the end of the loop body to terminate after one iteration.

Bug 2: Type error on transient closures in traverse-keymap

File: src/keymap.lisp

The transient keymap system allows prefix-suffix to return closures (for toggle and choice infixes) rather than command symbols. traverse-keymap passed these to its callback unconditionally, and callers like describe-bindings-internal called (symbol-name command) on the result — which fails on function objects with: The value #<FUNCTION ...> is not of type SYMBOL.

Fix: Changed the catch-all t branch in traverse-prefix (within traverse-keymap) to (symbolp suffix), so only symbol commands are yielded to callbacks. This protects all callers at the contract boundary rather than requiring guards at each call site.

Files Changed

File Change
src/commands/help.lisp Added (setf keymap nil) to terminate the loop
src/keymap.lisp Changed traverse-keymap to skip non-symbol suffixes

Two regressions from PR lem-project#2100:

- help.lisp: describe-bindings-internal looped forever because the
  (setf keymap (keymap-parent keymap)) advance was removed but the
  (loop :while keymap ...) was left in place. Terminate after one
  iteration with (setf keymap nil).

- keymap.lisp: traverse-keymap yielded transient closures (toggle,
  choice infixes) to callbacks, causing (symbol-name command) to
  signal a type error in describe-bindings-internal. Guard the
  callback at the contract boundary by gating on (symbolp suffix).
@code-contractor-app

code-contractor-app Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

✅ Code Contractor Validation: PASSED

🎉 All violations have been dismissed.

ℹ️ 1 violation(s) dismissed

📚 About Code Contractor

Declarative Code Standards That Learn and Improve

Define domain-specific validation rules in YAML.
Your contracts document team knowledge and evolve into more accurate AI enforcement.

Want this for your repo?
Install Code Contractor

@code-contractor-app code-contractor-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Contractor validation passed ✅ — see the sticky comment for full results.

- traverse-keymap-skips-non-symbol-suffixes: builds a keymap whose
  prefix suffix is a closure (the shape produced by transient
  toggle/choice infixes) and asserts traverse-keymap only yields
  symbol commands.

- describe-bindings-internal-terminates: end-to-end check that the
  function returns (with a bordeaux-threads timeout guard) on a
  keymap containing a closure suffix, and that the rendered output
  includes the symbol binding.

@code-contractor-app code-contractor-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Contractor validation failed ❌ — see the sticky comment for full results.

Comment thread tests/keymap.lisp
Comment thread tests/keymap.lisp
Adds rationale docstrings to both deftest forms describing the
regressions they guard against, and an inline comment explaining
why the test reaches lem-core/commands/help::describe-bindings-internal
through the internal symbol path.
@code-contractor-app code-contractor-app Bot dismissed their stale review May 19, 2026 22:15

All violations have been dismissed via resolved conversations.

@code-contractor-app code-contractor-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ All violations have been dismissed. This PR is ready to merge.

@theangelperalta theangelperalta merged commit 8919e53 into lem-project:main May 19, 2026
10 checks passed
@theangelperalta theangelperalta deleted the fix-describe-bindings-freeze-crash branch May 19, 2026 22:35
Catsquotl pushed a commit to Catsquotl/lem that referenced this pull request May 31, 2026
* fix: describe-bindings freeze and crash after keymap rewrite

Two regressions from PR lem-project#2100:

- help.lisp: describe-bindings-internal looped forever because the
  (setf keymap (keymap-parent keymap)) advance was removed but the
  (loop :while keymap ...) was left in place. Terminate after one
  iteration with (setf keymap nil).

- keymap.lisp: traverse-keymap yielded transient closures (toggle,
  choice infixes) to callbacks, causing (symbol-name command) to
  signal a type error in describe-bindings-internal. Guard the
  callback at the contract boundary by gating on (symbolp suffix).

* test: cover describe-bindings/traverse-keymap regressions

- traverse-keymap-skips-non-symbol-suffixes: builds a keymap whose
  prefix suffix is a closure (the shape produced by transient
  toggle/choice infixes) and asserts traverse-keymap only yields
  symbol commands.

- describe-bindings-internal-terminates: end-to-end check that the
  function returns (with a bordeaux-threads timeout guard) on a
  keymap containing a closure suffix, and that the rendered output
  includes the symbol binding.

* test: docstring deftests; document internal access

Adds rationale docstrings to both deftest forms describing the
regressions they guard against, and an inline comment explaining
why the test reaches lem-core/commands/help::describe-bindings-internal
through the internal symbol path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant