fix nthrest empty-list semantics + if-let/when-let/if-some/when-some scoping
Two correctness bugs found while migrating: - nthrest returned nil where Clojure returns () : for n>0 the walk yields (seq xs) wrapped in (or ... ()), so an exhausted/nil walk is () not nil (only n<=0 returns coll). Both the prior Janet impl and the first overlay port got this wrong. nthrest.cljc 13/1/0 -> 14/0/0. - if-let/when-let/if-some/when-some leaked their binding into the else branch: they wrapped the whole if in (let* [name val] ...), so (let [x 5] (if-let [x nil] x x)) returned nil instead of 5. Fixed to bind a fresh temp around the if and rebind the name only inside the taken branch, matching Clojure. conformance 218/218 x3, clojure-test-suite 3928 -> 3929.
This commit is contained in:
parent
185cf30c1d
commit
f22eb660d3
2 changed files with 33 additions and 26 deletions
|
|
@ -122,13 +122,16 @@
|
||||||
([f] (completing f identity))
|
([f] (completing f identity))
|
||||||
([f cf] (fn ([] (f)) ([x] (cf x)) ([x y] (f x y)))))
|
([f cf] (fn ([] (f)) ([x] (cf x)) ([x y] (f x y)))))
|
||||||
|
|
||||||
;; Canonical loop form: short-circuits on an empty/nil coll before examining n
|
;; Matches Clojure exactly: n<=0 returns coll unchanged; for n>0 the walk yields
|
||||||
;; (so (nthrest nil n) is nil without a number check), matching Clojure.
|
;; (seq xs), and an exhausted/nil walk falls back to () via (or ... ()) — so
|
||||||
|
;; (nthrest nil 100) is () (not nil), while (nthrest nil 0) is nil.
|
||||||
(defn nthrest [coll n]
|
(defn nthrest [coll n]
|
||||||
(loop [n n xs coll]
|
(if (pos? n)
|
||||||
(if (and (pos? n) (seq xs))
|
(or (loop [n n xs coll]
|
||||||
(recur (dec n) (rest xs))
|
(let [s (and (pos? n) (seq xs))]
|
||||||
xs)))
|
(if s (recur (dec n) (rest s)) (seq xs))))
|
||||||
|
(list))
|
||||||
|
coll))
|
||||||
|
|
||||||
(defn abs [x] (if (neg? x) (- 0 x) x))
|
(defn abs [x] (if (neg? x) (- 0 x) x))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2160,16 +2160,21 @@
|
||||||
{:jolt/type :symbol :ns nil :name "or__x"}
|
{:jolt/type :symbol :ns nil :name "or__x"}
|
||||||
@[{:jolt/type :symbol :ns nil :name "or"} ;(tuple/slice exprs 1)]]])))
|
@[{:jolt/type :symbol :ns nil :name "or"} ;(tuple/slice exprs 1)]]])))
|
||||||
|
|
||||||
|
# if-let / when-let / if-some / when-some bind the name ONLY in the then/body
|
||||||
|
# branch — the else branch must see the surrounding scope, not the binding (so
|
||||||
|
# (let [x 5] (if-let [x nil] x x)) returns 5, like Clojure). Achieved with a fresh
|
||||||
|
# temp around the if; the name is rebound to the temp inside the taken branch only.
|
||||||
|
(defn- sym* [name] {:jolt/type :symbol :ns nil :name name})
|
||||||
|
|
||||||
(defn core-if-let
|
(defn core-if-let
|
||||||
"Macro: (if-let [binding val-expr] then else?)"
|
"Macro: (if-let [binding val-expr] then else?)"
|
||||||
[bindings then-form & else-forms]
|
[bindings then-form & else-forms]
|
||||||
(def form-sym (in bindings 0))
|
(def form-sym (in bindings 0))
|
||||||
(def val-form (in bindings 1))
|
(def val-form (in bindings 1))
|
||||||
@[{:jolt/type :symbol :ns nil :name "let*"}
|
(def temp (gensym "if_let__"))
|
||||||
@[form-sym val-form]
|
@[(sym* "let*") @[temp val-form]
|
||||||
@[{:jolt/type :symbol :ns nil :name "if"}
|
@[(sym* "if") temp
|
||||||
form-sym
|
@[(sym* "let*") @[form-sym temp] then-form]
|
||||||
then-form
|
|
||||||
;else-forms]])
|
;else-forms]])
|
||||||
|
|
||||||
(defn core-when-let
|
(defn core-when-let
|
||||||
|
|
@ -2177,22 +2182,21 @@
|
||||||
[bindings & body]
|
[bindings & body]
|
||||||
(def form-sym (in bindings 0))
|
(def form-sym (in bindings 0))
|
||||||
(def val-form (in bindings 1))
|
(def val-form (in bindings 1))
|
||||||
@[{:jolt/type :symbol :ns nil :name "let*"}
|
(def temp (gensym "when_let__"))
|
||||||
@[form-sym val-form]
|
@[(sym* "let*") @[temp val-form]
|
||||||
@[{:jolt/type :symbol :ns nil :name "when"}
|
@[(sym* "if") temp
|
||||||
form-sym
|
@[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]]
|
||||||
;body]])
|
nil]])
|
||||||
|
|
||||||
(defn core-if-some
|
(defn core-if-some
|
||||||
"Macro: (if-some [binding val-expr] then else?)"
|
"Macro: (if-some [binding val-expr] then else?)"
|
||||||
[bindings then-form & else-forms]
|
[bindings then-form & else-forms]
|
||||||
(def form-sym (in bindings 0))
|
(def form-sym (in bindings 0))
|
||||||
(def val-form (in bindings 1))
|
(def val-form (in bindings 1))
|
||||||
@[{:jolt/type :symbol :ns nil :name "let*"}
|
(def temp (gensym "if_some__"))
|
||||||
@[form-sym val-form]
|
@[(sym* "let*") @[temp val-form]
|
||||||
@[{:jolt/type :symbol :ns nil :name "if"}
|
@[(sym* "if") @[(sym* "some?") temp]
|
||||||
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
|
@[(sym* "let*") @[form-sym temp] then-form]
|
||||||
then-form
|
|
||||||
;else-forms]])
|
;else-forms]])
|
||||||
|
|
||||||
(defn core-when-some
|
(defn core-when-some
|
||||||
|
|
@ -2200,11 +2204,11 @@
|
||||||
[bindings & body]
|
[bindings & body]
|
||||||
(def form-sym (in bindings 0))
|
(def form-sym (in bindings 0))
|
||||||
(def val-form (in bindings 1))
|
(def val-form (in bindings 1))
|
||||||
@[{:jolt/type :symbol :ns nil :name "let*"}
|
(def temp (gensym "when_some__"))
|
||||||
@[form-sym val-form]
|
@[(sym* "let*") @[temp val-form]
|
||||||
@[{:jolt/type :symbol :ns nil :name "when"}
|
@[(sym* "if") @[(sym* "some?") temp]
|
||||||
@[{:jolt/type :symbol :ns nil :name "some?"} form-sym]
|
@[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]]
|
||||||
;body]])
|
nil]])
|
||||||
|
|
||||||
(defn core-doto
|
(defn core-doto
|
||||||
"Macro: (doto obj (method args)...) → let obj, call methods, return obj"
|
"Macro: (doto obj (method args)...) → let obj, call methods, return obj"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue