jolt-f79 asked to compile destructuring in fn* rest params. Checking
against Clojure inverts the premise: Clojure's fn* REJECTS destructuring
at compile time ('fn params must be Symbols'; let*/loop* 'Bad binding
form, expected symbol'). So the self-hosted analyzer was already correct
— fn*/let*/loop* are plain-symbol primitives; the fn/let/loop/defn
MACROS desugar destructuring. The real defect was the interpreter
leniently destructuring raw fn*, and defn emitting raw fn* to rely on it.
Changes:
- evaluator: fn*/let*/loop* now reject non-symbol binding forms with
Clojure's exact messages (require-symbol-params/plain-sym?), so the
interpreter agrees with the analyzer + Clojure.
- 00-syntax: defn emits the fn MACRO (not raw fn*) so destructuring
params desugar; unnamed, so self-recursion still resolves via the var.
- 00-syntax: completing that exposed a real gap — the overlay destructure
fn didn't handle kwargs (a map pattern bound against a fn's sequential
rest); it had only worked via the interpreter's destructure-bind. Added
the seq->map coercion to the map? branch (sequential: 1 map elt => that
map, else apply hash-map), matching destructure-bind so interpret ==
compile.
Net: fn*/let*/loop* are plain-symbol primitives across interpreter,
analyzer, and Clojure; all real destructuring (fn/defn/let/loop/macro
params, incl kwargs & {:keys}) compiles through the macros with no
interpreter fallback. Regression spec added.
Gate green: conformance 267x3, fallback-zero 31/5, bootstrap-fixpoint
stage1==2==3, self-host, staged-bootstrap, lazy-infinite 44/44,
clojure-test-suite >=4034/67, sci-bootstrap, features 78/78, all
unit+spec (destructuring 50/50).
79 lines
4.6 KiB
Text
79 lines
4.6 KiB
Text
# Specification: destructuring (in let, fn, loop, doseq, for).
|
|
(use ../support/harness)
|
|
|
|
(defspec "destructure / sequential"
|
|
["basic vector" "3" "(let [[a b] [1 2]] (+ a b))"]
|
|
["skip with _" "3" "(let [[_ b] [1 2]] (+ b 1))"]
|
|
["rest with &" "[3 4]" "(let [[a & more] [1 3 4]] more)"]
|
|
[":as whole" "[1 2]" "(let [[a :as v] [1 2]] v)"]
|
|
["nested" "3" "(let [[[a b]] [[1 2]]] (+ a b))"]
|
|
["fewer values nil" "nil" "(let [[a b c] [1 2]] c)"]
|
|
["over a list" "1" "(let [[a] (list 1 2)] a)"]
|
|
["over a seq" "2" "(let [[a b] (rest [9 1 2])] b)"]
|
|
["string chars" "\\a" "(let [[a] (seq \"ab\")] a)"])
|
|
|
|
(defspec "destructure / associative"
|
|
["keys" "3" "(let [{:keys [a b]} {:a 1 :b 2}] (+ a b))"]
|
|
[":as map" "{:a 1}" "(let [{:as m} {:a 1}] m)"]
|
|
[":or default" "9" "(let [{:keys [a] :or {a 9}} {}] a)"]
|
|
[":or present" "1" "(let [{:keys [a] :or {a 9}} {:a 1}] a)"]
|
|
["explicit binding" "1" "(let [{x :a} {:a 1}] x)"]
|
|
["nested map" "2" "(let [{{b :b} :a} {:a {:b 2}}] b)"]
|
|
["keys + as" "[1 {:a 1}]" "(let [{:keys [a] :as m} {:a 1}] [a m])"]
|
|
["map in vector" "1" "(let [[{:keys [a]}] [{:a 1}]] a)"])
|
|
|
|
(defspec "destructure / in forms"
|
|
["fn params" "3" "((fn [[a b]] (+ a b)) [1 2])"]
|
|
["fn map param" "1" "((fn [{:keys [a]}] a) {:a 1})"]
|
|
["defn destructure" "3" "(do (defn f [[a b]] (+ a b)) (f [1 2]))"]
|
|
["loop destructure" "3" "(loop [[a b] [1 2]] (+ a b))"]
|
|
["doseq destructure" "12" "(let [s (atom 0)] (doseq [[k v] {:a 4 :b 8}] (swap! s (fn [x] (+ x v)))) @s)"]
|
|
["for destructure" "[3 7]" "(for [[a b] [[1 2] [3 4]]] (+ a b))"]
|
|
["& rest in fn" "[2 3]" "((fn [a & more] more) 1 2 3)"])
|
|
|
|
(defspec "destructure / associative extras"
|
|
[":strs" "7" "(let [{:strs [a]} {\"a\" 7}] a)"]
|
|
[":syms" "8" "(let [{:syms [a]} {(quote a) 8}] a)"]
|
|
["namespaced :keys" "3" "(let [{:keys [x/y]} {:x/y 3}] y)"]
|
|
["namespaced :syms" "4" "(let [{:syms [p/q]} {(quote p/q) 4}] q)"]
|
|
# :keys also accepts keyword elements ({:keys [:a :b]}), binding bare locals.
|
|
["keyword :keys" "3" "(let [{:keys [:a :b]} {:a 1 :b 2}] (+ a b))"]
|
|
["keyword :keys ns" "3" "(let [{:keys [:x/y]} {:x/y 3}] y)"])
|
|
|
|
(defspec "destructure / keyword args (& {:keys})"
|
|
["fn kwargs" "[1 2]" "(do (defn f [& {:keys [a b]}] [a b]) (f :a 1 :b 2))"]
|
|
["fn kwargs + fixed" "[0 5]" "(do (defn g [x & {:keys [a]}] [x a]) (g 0 :a 5))"]
|
|
["fn kwargs :or" "9" "(do (defn h [& {:keys [a] :or {a 9}}] a) (h))"]
|
|
["fn kwargs trailing map" "7" "(do (defn k [& {:keys [a]}] a) (k {:a 7}))"])
|
|
|
|
(defspec "destructure / fn params & loop"
|
|
["fn vector param" "7" "((fn [[a b]] (+ a b)) [3 4])"]
|
|
["fn map param" "30" "((fn [{:keys [x y]}] (* x y)) {:x 5 :y 6})"]
|
|
["fn :or param" "7" "((fn [{:keys [x] :or {x 7}}] x) {})"]
|
|
["fn multi-arity destr" "15" "((fn ([[a]] a) ([[a] b] (+ a b))) [10] 5)"]
|
|
["loop vector binding" "[4 2]" "(loop [[a b] [1 2] n 0] (if (< n 3) (recur [(inc a) b] (inc n)) [a b]))"]
|
|
["loop map binding" "4" "(loop [{:keys [v]} {:v 1} n 0] (if (< n 2) (recur {:v (* v 2)} (inc n)) v))"]
|
|
["loop init sees destr" "[1 2 3]" "(loop [[a b] [1 2] c (+ a b)] [a b c])"])
|
|
|
|
# fn*/let*/loop* are PRIMITIVES: their binding forms must be plain symbols, exactly
|
|
# as in Clojure ("fn params must be Symbols" / "Bad binding form, expected symbol").
|
|
# Destructuring lives in the fn/let/loop/defn MACROS, which desugar to plain
|
|
# primitives — so the interpreter and the self-hosted analyzer agree (neither
|
|
# destructures fn*), and nothing silently falls back to a lenient interpreter path.
|
|
(defspec "destructure / primitives reject patterns (jolt-f79)"
|
|
["fn* fixed pattern" :throws "((fn* [[a b]] a) [1 2])"]
|
|
["fn* rest pattern" :throws "((fn* [a & [b]] b) 1 2 3)"]
|
|
["let* pattern" :throws "(let* [[a b] [1 2]] a)"]
|
|
["loop* pattern" :throws "(loop* [[a b] [1 2]] a)"]
|
|
# the macros still desugar the same patterns to plain primitives:
|
|
["fn desugars" "[1 2]" "((fn [[a b]] [a b]) [1 2])"]
|
|
["let desugars" "[1 2]" "(let [[a b] [1 2]] [a b])"])
|
|
|
|
(defspec "destructure / macro params"
|
|
["macro & [a & more :as all]"
|
|
"[1 [2 3] [1 2 3]]"
|
|
"(do (defmacro m [& [a & more :as all]] (list (quote quote) [a (vec more) (vec all)])) (m 1 2 3))"]
|
|
["macro fixed destructure" "[2 1]"
|
|
"(do (defmacro mm [[a b]] (list (quote quote) [b a])) (mm [1 2]))"]
|
|
["macro & {:keys}" "5"
|
|
"(do (defmacro mk [& {:keys [x]}] (list (quote quote) x)) (mk :x 5))"])
|