Emit Janet's in as a value so a user local can't shadow it (jolt-fjb1)
The back end emits `in` to deref var cells ((in cell :root)) and index shape-recs. It emitted the bare symbol, so a user local named `in` shadowed Janet's builtin in the surrounding scope and the generated cell-deref called the user's value as a function — "<table> called with 2 arguments, possibly expected 1". malli's explainer binds [value in acc], so m/explain hit this on every schema (m/validate was unaffected — its path doesn't bind `in`). Embed `in`'s function value at the emit sites (as jolt-call/core-get already are); a value in head position can't be shadowed. Fixes m/explain on malli (loaded with JOLT_FEATURES=clj so its .cljc reader-conditionals resolve).
This commit is contained in:
parent
93c86c20f7
commit
e3003ea86f
2 changed files with 21 additions and 7 deletions
|
|
@ -32,6 +32,14 @@
|
|||
# var-get/tuple-slice/...) and jolt runtime helpers resolve by name.
|
||||
(def jolt-runtime-env (curenv))
|
||||
|
||||
# Janet's `in` accessor as an embedded VALUE (jolt-fjb1). The back end uses `in`
|
||||
# to index cells/shape-recs/arg tuples in code it EMITS. Emitting the bare symbol
|
||||
# `in` is unhygienic: a user local named `in` (malli's explainer binds `[value in
|
||||
# acc]`) shadows it in the surrounding scope, so the generated `(in cell :root)`
|
||||
# would call the user's value as a function — "<table> called with 2 arguments".
|
||||
# Embedding the function value (as with jolt-call/core-get) can't be shadowed.
|
||||
(def- jin in)
|
||||
|
||||
(defn ctx-janet-env
|
||||
"Lazily create/cache a per-context Janet environment for compiled code: a child
|
||||
of the runtime env (so core fns resolve) that holds this context's user defs.
|
||||
|
|
@ -193,7 +201,7 @@
|
|||
(defn- emit-arity-invoke [ctx ar jargs]
|
||||
(def nfixed (length (vview (ar :params))))
|
||||
(def call @[(emit-arity-fn ctx ar)])
|
||||
(for i 0 nfixed (array/push call ['in jargs i]))
|
||||
(for i 0 nfixed (array/push call [jin jargs i]))
|
||||
# empty rest binds to NIL, not () — (f) with [& r] gives r = nil in Clojure
|
||||
(when (ar :rest)
|
||||
(array/push call ['if ['> ['length jargs] nfixed] ['tuple/slice jargs nfixed]]))
|
||||
|
|
@ -225,7 +233,7 @@
|
|||
# live-root call the :var emit uses, for the ns get/set helpers below.
|
||||
(defn- core-fn-call [ctx nm & args]
|
||||
(def cell (cell-for ctx "clojure.core" nm))
|
||||
(tuple (tuple 'in (tuple 'quote cell) :root) ;args))
|
||||
(tuple (tuple jin (tuple 'quote cell) :root) ;args))
|
||||
|
||||
(defn- emit-try [ctx node]
|
||||
(def core
|
||||
|
|
@ -420,11 +428,11 @@
|
|||
# NOT gated on compile-time shapes — core is baked without the flag but still
|
||||
# receives user shape-recs, so this must hold in baked core too. (jolt-t34)
|
||||
(defn get-or-shape [getexpr]
|
||||
(if sidx ['in m sidx]
|
||||
(if sidx [jin m sidx]
|
||||
(let [pos (jsym)]
|
||||
['if ['and ['tuple? m] ['struct? ['get m 0]]]
|
||||
['let [pos ['get [['get m 0] :idx] k]]
|
||||
['if ['nil? pos] (tuple core-get m k nil) ['in m ['+ 1 pos]]]]
|
||||
['if ['nil? pos] (tuple core-get m k nil) [jin m ['+ 1 pos]]]]
|
||||
getexpr])))
|
||||
(if (nil? d-expr)
|
||||
(let [fast (get-or-shape ['get m k])]
|
||||
|
|
@ -632,9 +640,9 @@
|
|||
# majority) pay two native table ops + a branch instead of a
|
||||
# function call.
|
||||
(let [qcell (tuple 'quote cell)]
|
||||
['if ['in qcell :dynamic]
|
||||
['if [jin qcell :dynamic]
|
||||
(tuple var-get qcell)
|
||||
['in qcell :root]]))))
|
||||
[jin qcell :root]]))))
|
||||
# (var x): the var object itself (not its value) — the embedded cell, by
|
||||
# reference. binding keys its thread-binding frame on this exact cell.
|
||||
:the-var (tuple 'quote (cell-for ctx (node :ns) (node :name)))
|
||||
|
|
|
|||
|
|
@ -11,7 +11,13 @@
|
|||
["variadic with fixed" "[1 [2 3]]" "(do (defn f [a & xs] [a xs]) (f 1 2 3))"]
|
||||
["closure captures" "8" "(do (defn adder [n] (fn [x] (+ x n))) ((adder 5) 3))"]
|
||||
["recursion" "120" "(do (defn fact [n] (if (< n 2) 1 (* n (fact (dec n))))) (fact 5))"]
|
||||
["named fn self-ref" "120" "((fn fact [n] (if (< n 2) 1 (* n (fact (dec n))))) 5)"])
|
||||
["named fn self-ref" "120" "((fn fact [n] (if (< n 2) 1 (* n (fact (dec n))))) 5)"]
|
||||
# A param whose name collides with a Janet builtin the back end emits in
|
||||
# generated code (here `in`, used to deref vars / index shape-recs) must not
|
||||
# shadow it — malli's explainer binds `[value in acc]` (jolt-fjb1).
|
||||
["param named `in`" "1" "((fn [in] (first in)) [1 2 3])"]
|
||||
["param `in` via core fn" "3" "((fn [in] (count in)) [1 2 3])"]
|
||||
["local `in` in let body" "2" "(let [in [2 3]] (first in))"])
|
||||
|
||||
(defspec "functions / application"
|
||||
["apply" "6" "(apply + [1 2 3])"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue