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).

View file

@ -2433,25 +2433,6 @@
(while (< i n) (d-process (in bindings i) (in bindings (+ i 1)) out) (+= i 2))
(tuple/slice out))
(defn core-let
"Macro: (let [bindings] body) → (let* [plain-bindings] body), expanding
destructuring patterns so the compiler/interpreter see only plain symbols."
[bindings & body]
(def result @[])
(array/push result {:jolt/type :symbol :ns nil :name "let*"})
(array/push result (core-destructure bindings))
(each b body (array/push result b))
result)
(defn core-loop
"Macro: (loop [bindings] body) → (loop* [bindings] body)"
[bindings & body]
(def result @[])
(array/push result {:jolt/type :symbol :ns nil :name "loop*"})
(array/push result bindings)
(each b body (array/push result b))
result)
# Protocol implementation — methods dispatch via type registry
(defn core-defprotocol [protocol-name & sigs]
(def result @[])
@ -3424,8 +3405,7 @@
"remove-all-methods" core-remove-all-methods
"prefer-method" core-prefer-method
"Object" core-Object
"let" core-let
"loop" core-loop
"destructure" core-destructure
"defprotocol" core-defprotocol
"extend-type" core-extend-type
"extend-protocol" core-extend-protocol
@ -3487,7 +3467,7 @@
(defn core-macro-names
"Set of core binding names that are macros."
[]
@{"when-let" true "defn" true "defn-" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "lazy-seq" true "lazy-cat" true})
@{"when-let" true "defn" true "defn-" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "lazy-seq" true "lazy-cat" true})
(def init-core!
(fn [& args]