ir/analyzer: omit absent fields instead of writing nil

def-node carried :meta nil when a def had no metadata, fn-node carried :name
nil for anonymous fns, analyze-arity carried :rest nil for fixed arities, and
empty-env carried :recur nil. These all mean genuine absence, and every
consumer reads an absent key back as nil — so writing the nil is gratuitous.

Dropping them keeps the common nodes (defs, fns, fixed arities) nil-free, so
once user maps preserve nil they stay plain structs instead of becoming phm.
Faithful nil values (a nil const :val) are left as-is; the back end handles
those. No-op today since structs already drop nil: suite, conformance 218/218
x3, fixpoint, fib all unchanged.
This commit is contained in:
Yogthos 2026-06-06 22:05:52 -04:00
parent 22ff737f8b
commit b2ff65972b
2 changed files with 19 additions and 9 deletions

View file

@ -38,7 +38,7 @@
(swap! gensym-counter inc) (swap! gensym-counter inc)
(str "_r$" prefix n))) (str "_r$" prefix n)))
(defn- empty-env [] {:locals #{} :recur nil}) (defn- empty-env [] {:locals #{}})
(defn- local? [env nm] (contains? (:locals env) nm)) (defn- local? [env nm] (contains? (:locals env) nm))
(defn- add-locals [env names] (update env :locals #(reduce conj % names))) (defn- add-locals [env names] (update env :locals #(reduce conj % names)))
(defn- with-recur [env name] (assoc env :recur name)) (defn- with-recur [env name] (assoc env :recur name))
@ -82,9 +82,12 @@
;; is a self-call carrying the rest seq directly — Clojure semantics. ;; is a self-call carrying the rest seq directly — Clojure semantics.
rname (gen-name "arity") rname (gen-name "arity")
names (cond-> (vec fixed) rst (conj rst) fn-name (conj fn-name)) names (cond-> (vec fixed) rst (conj rst) fn-name (conj fn-name))
env* (-> (add-locals env names) (with-recur rname))] env* (-> (add-locals env names) (with-recur rname))
{:params fixed :rest rst :recur-name rname arity {:params fixed :recur-name rname
:body (analyze-seq ctx body env*)})) :body (analyze-seq ctx body env*)}]
;; :rest only when variadic — an absent :rest reads back nil, same as before,
;; but keeps a fixed arity a nil-free struct rather than a phm.
(if rst (assoc arity :rest rst) arity)))
(defn- analyze-fn [ctx items env] (defn- analyze-fn [ctx items env]
(let [named (form-sym? (nth items 1)) (let [named (form-sym? (nth items 1))

View file

@ -30,15 +30,22 @@
(defn invoke [f args] {:op :invoke :fn f :args args}) (defn invoke [f args] {:op :invoke :fn f :args args})
;; meta is the var metadata (e.g. {:dynamic true} / {:redef true}) the back end ;; meta is the var metadata (e.g. {:dynamic true} / {:redef true}) the back end
;; applies to the cell; nil when the def name carried none. ;; applies to the cell; absent when the def name carried none.
(defn def-node (defn def-node
([ns name init] {:op :def :ns ns :name name :init init :meta nil}) ([ns name init] {:op :def :ns ns :name name :init init})
([ns name init meta] {:op :def :ns ns :name name :init init :meta meta})) ([ns name init meta]
(if meta
{:op :def :ns ns :name name :init init :meta meta}
{:op :def :ns ns :name name :init init})))
(defn let-node [bindings body] {:op :let :bindings bindings :body body}) (defn let-node [bindings body] {:op :let :bindings bindings :body body})
;; A fn is one or more arities. Each arity: {:params [..] :rest name|nil :body ir}. ;; A fn is one or more arities. Each arity: {:params [..] :body ir}, plus :rest
(defn fn-node [name arities] {:op :fn :name name :arities arities}) ;; name when variadic. :name is absent for an anonymous fn.
(defn fn-node [name arities]
(if name
{:op :fn :name name :arities arities}
{:op :fn :arities arities}))
(defn vector-node [items] {:op :vector :items items}) (defn vector-node [items] {:op :vector :items items})
(defn map-node [pairs] {:op :map :pairs pairs}) (defn map-node [pairs] {:op :map :pairs pairs})