core: move let and loop to the syntax tier

let -> let* with destructuring pre-expanded via destructure (now exposed as a
clojure.core fn, which it is in Clojure too) so the compiler sees plain bindings —
analyze-bindings rejects patterns as uncompilable. loop -> loop* with raw bindings,
matching the prior Janet macro: loop can't pre-destructure without breaking recur
arity, so the interpreter handles pattern loops and the compiler falls back.

conformance 228x3, fixpoint, clojure-test-suite 3930.
This commit is contained in:
Yogthos 2026-06-07 17:29:39 -04:00
parent 85cdb97320
commit 4eb2cf5c46
2 changed files with 18 additions and 22 deletions

View file

@ -64,6 +64,22 @@
;; multi-arity); fn is just the public spelling.
(defmacro fn [& args] `(fn* ~@args))
;; let desugars destructuring patterns to plain bindings (via destructure) so the
;; COMPILER sees only plain symbols — analyze-bindings rejects patterns as
;; uncompilable, relying on this macro to have expanded them. (The interpreter
;; could destructure let* directly, but the compiler can't.) let* is sequential, so
;; a later init can reference an earlier destructured name. destructure is a
;; clojure.core fn; calling it at expansion time is fine — it's interned at init.
(defmacro let [bindings & body]
`(let* ~(destructure bindings) ~@body))
;; loop -> loop* with raw bindings (matching the prior Janet macro). loop can't
;; pre-destructure like let: that would change the binding count and break recur's
;; arity. The interpreter destructures loop* directly; for destructuring loops the
;; compiler falls back (a pre-existing limitation).
(defmacro loop [bindings & body]
`(loop* ~bindings ~@body))
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet symbol
;; the destructurer rejects). This defn compiles fine: by the time a tier triggers
;; the analyzer build the kernel is in place (the build is gated until then).