Skip to content

fix: cond flat-pairs + multi-arity fn/defmethod &rest + self-hosting runtime regen#15

Merged
BuddhiLW merged 5 commits into
mainfrom
fix/cljel-emitter-cond-fn-defmethod
Jul 10, 2026
Merged

fix: cond flat-pairs + multi-arity fn/defmethod &rest + self-hosting runtime regen#15
BuddhiLW merged 5 commits into
mainfrom
fix/cljel-emitter-cond-fn-defmethod

Conversation

@BuddhiLW

@BuddhiLW BuddhiLW commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Three correctness fixes found during the args-collision audit, each a separate commit with nREPL/Emacs-verified regression tests. All work via carto CRUD + nREPL(7922)/Emacs verification.

Fix 1 — cond is always flat pairs (kanban 20260710133453-372031e6)

analyze-cond guessed "Elisp pre-grouped" via (every? seq? clauses); a flat Clojure cond whose tests and results were all lists (e.g. clel-merge) mis-compiled into spurious (progn) clauses. Now cond is always (partition 2 …); the Elisp pre-grouped shape is desugared to flat by the elisp-cond macro (preserved).

Fix 2 — multi-arity fn dispatch + defmethod variadic &rest (kanban 20260710134255-3ce11ce0)

Multi-arity fn literals dropped all but the first arity; now they emit (lambda (&rest clel--args) (cl-case (length clel--args) …)), reusing the multi-arity-defn codegen. defmethod variadic emitted a bare &; now &rest. DRY: extracted analyze-arity-clauses, emit-arity-cl-case, mangle-param.

Fix 3 — self-hosting runtime is cleanly regenerable (kanban 20260710133513-7938b464)

The runtime .el was hand-maintained and un-regenerable (nth/last remapped to the clel-* wrappers the runtime defines → self-recursion/wrong arg order; clel-nth + Elisp-2 bridges lived only in the .el). Fixed in runtime.cljel source: routed the 12 primitive call sites through the existing elisp/-escape hatch, added clel-nth (raw elt) + the bridge defvars. The regenerated .el also inherits Fixes 1&2, correcting ~24 transducer/utility lambdas the hand-edited .el still shipped with the bare-& bug, plus ('_ …)(_ …) and cl-minmin.

Verification

  • Emacs functional battery: 27/27 pass on the regenerated runtime; byte-compiles with no real warnings.
  • Emitter fixes verified live: nested cond correct, multi-arity fn 1→10/2→15, variadic fn (1 (2 3)), defmethod (:k (1 2 3)).
  • 3 regression deftests added (incl. runtime-regen-test). 543 tests, 2783 assertions, 0 failures.

Also synced drifted resources/clojure-elisp/VERSION (0.5.4→0.5.5); release-pipeline dual-VERSION drift filed as 20260710145203-7196a846 (P-low).

🤖 Generated with Claude Code

BuddhiLW and others added 3 commits July 10, 2026 14:18
…e into elisp-cond

`analyze-cond` auto-detected "Elisp-style pre-grouped" clauses via
`(every? seq? clauses)`. A flat Clojure `cond` whose tests AND results were all
lists — e.g. `(cond (hash-table-p m) (maphash ...) (listp m) (dolist ...))`
(clel-merge, clel-rename-keys) — was mis-read as pre-grouped and mis-compiled
into spurious `(progn)` clauses with broken pairing.

Clojure `cond` is ALWAYS flat test/expr pairs; there is no pre-grouped form.
Fix: `analyze-cond` now always `(partition 2 clauses)` — no shape inspection.
The Elisp pre-grouped shape `(test body...)` is now desugared to flat cond by
the `elisp-cond` macro (`elisp-cond-expand`), which remains the ONLY surface
that accepts it (single body -> expr, multi-body -> (do ...), empty -> test).
elisp-cond stays a registered built-in macro (regression preserved).

Verified via nREPL: `(cond (pred x) (f) (pred2 y) (g))` and runtime-shaped
nested cond now pair correctly; flat/:else/atom regressions and all three
elisp-cond shapes unchanged. Added regression `cond-list-test-and-result`.
541 tests, 0 failures.

Kanban: 20260710133453-372031e6

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two anonymous-fn / defmethod param-emission bugs (separate path from defn):

(1) Multi-arity fn literal `(fn ([x] a) ([x y] b))` silently dropped all but
    the first arity: analyze-fn took `(first fdecl)` and ignored the rest.
    Now analyze-fn detects 2+ arity clauses and produces a multi-arity :fn AST;
    emit-node :fn emits `(lambda (&rest clel--args) (cl-case (length clel--args)
    ...))`, reusing the arity-dispatch codegen shared with multi-arity defn.

(2) defmethod variadic emitted a bare `&` (invalid Elisp) instead of `&rest`:
    params went through mangle-name, which doesn't translate the rest marker.

Refactor (DRY): extract `analyze-arity-clauses` (analyzer) and
`emit-arity-cl-case` + `mangle-param` (emitter), now shared by defn and fn.
Single-arity fn and one-wrapped-arity `(fn ([x] x))` stay plain lambdas
(count-1 guard). Multi-arity fn params are not destructured (matches defn).

Verified in Emacs: multi-arity fn dispatches (1->10, 2->15), variadic fn
-> (1 (2 3)), variadic defmethod -> (:k (1 2 3)). Added regression
`multi-arity-fn-and-defmethod-rest`. 542 tests, 0 failures.

Kanban: 20260710134255-3ce11ce0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ource

The committed clojure-elisp-runtime.el was hand-maintained and could NOT be
regenerated: a naive `compile-runtime` shipped a broken runtime.

Root causes, all fixed in runtime.cljel source (no compiler changes):
 - `nth`/`last` in core-fn-mapping rewrite to the very `clel-nth`/`clel-last`
   wrappers the runtime defines → self-recursion (clel-last) and wrong arg
   order (clel-deref etc. use Elisp index-first `(nth 1 atom)`). Fixed by
   routing the 11 `nth` + 1 `last` primitive call sites through the existing
   `elisp/`-escape hatch (`(elisp/nth 1 atom)` -> raw `(nth 1 atom)`), which
   bypasses core-fn-mapping.
 - `clel-nth` was defined ONLY in the hand-edited .el. Added its definition to
   runtime.cljel (uses raw `elt`, which is unmapped).
 - The Elisp-2 bridge defvars `clojure-core-vector`/`clojure-core-list` existed
   only in the .el. Added them to source.

The regenerated .el is now produced entirely from source and is a net
improvement: it also inherits the cond + variadic-`&rest` emitter fixes, so ~24
transducer/utility lambdas that the hand-edited .el still shipped with the bare
`&` bug (e.g. `(lambda (& args) (pcase (length args) ...))`) are now correct
`&rest`, plus `('_ ...)`->`(_ ...)` in clel-range and `cl-min`->`min`.

Verified: regenerated .el byte-compiles with no real warnings and passes a
27-case Emacs functional battery (nth/last/atoms/watchers/lazy/reduce/map/
filter/get/merge/str/range/ALL transducers/constantly/complement/juxt/comp).
Added `runtime-regen-test` locking the self-hosting-safe invariants. 543 tests,
0 failures.

Also synced resources/clojure-elisp/VERSION 0.5.4 -> 0.5.5 (the classpath
version resource that drives the .el header had drifted from the top-level
VERSION; the release process only bumps the latter — filed as follow-up).

Kanban: 20260710133513-7938b464

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BuddhiLW BuddhiLW changed the title fix(emitter): cond flat-pairs + multi-arity fn dispatch + defmethod &rest fix: cond flat-pairs + multi-arity fn/defmethod &rest + self-hosting runtime regen Jul 10, 2026
BuddhiLW and others added 2 commits July 10, 2026 16:19
resources/clojure-elisp/VERSION (the classpath resource that stamps the
runtime .el MELPA header via core/read-version) drifted from /VERSION (the
jar/tag source) because releases bumped only the latter. Add a build
sync-version step (also run inside `uber`) that regenerates the resource
from /VERSION, plus a test asserting the two files stay in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Untracked test-suite files present in the working tree before this session;
checkpointed so the local suite is reproducible. Golden snapshots bless
current emitter output as the baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BuddhiLW
BuddhiLW merged commit 4c53388 into main Jul 10, 2026
2 checks passed
@BuddhiLW
BuddhiLW deleted the fix/cljel-emitter-cond-fn-defmethod branch July 10, 2026 19:27
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