From f22eb660d3546fcc3911ab1033cb026931bf1834 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 23:42:08 -0400 Subject: [PATCH] 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. --- jolt-core/clojure/core/20-coll.clj | 15 ++++++---- src/jolt/core.janet | 44 ++++++++++++++++-------------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index 760ac84..e235c52 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -122,13 +122,16 @@ ([f] (completing f identity)) ([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 -;; (so (nthrest nil n) is nil without a number check), matching Clojure. +;; Matches Clojure exactly: n<=0 returns coll unchanged; for n>0 the walk yields +;; (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] - (loop [n n xs coll] - (if (and (pos? n) (seq xs)) - (recur (dec n) (rest xs)) - xs))) + (if (pos? n) + (or (loop [n n xs coll] + (let [s (and (pos? n) (seq xs))] + (if s (recur (dec n) (rest s)) (seq xs)))) + (list)) + coll)) (defn abs [x] (if (neg? x) (- 0 x) x)) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 5d68f36..afecb50 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -2160,16 +2160,21 @@ {:jolt/type :symbol :ns nil :name "or__x"} @[{: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 "Macro: (if-let [binding val-expr] then else?)" [bindings then-form & else-forms] (def form-sym (in bindings 0)) (def val-form (in bindings 1)) - @[{:jolt/type :symbol :ns nil :name "let*"} - @[form-sym val-form] - @[{:jolt/type :symbol :ns nil :name "if"} - form-sym - then-form + (def temp (gensym "if_let__")) + @[(sym* "let*") @[temp val-form] + @[(sym* "if") temp + @[(sym* "let*") @[form-sym temp] then-form] ;else-forms]]) (defn core-when-let @@ -2177,22 +2182,21 @@ [bindings & body] (def form-sym (in bindings 0)) (def val-form (in bindings 1)) - @[{:jolt/type :symbol :ns nil :name "let*"} - @[form-sym val-form] - @[{:jolt/type :symbol :ns nil :name "when"} - form-sym - ;body]]) + (def temp (gensym "when_let__")) + @[(sym* "let*") @[temp val-form] + @[(sym* "if") temp + @[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]] + nil]]) (defn core-if-some "Macro: (if-some [binding val-expr] then else?)" [bindings then-form & else-forms] (def form-sym (in bindings 0)) (def val-form (in bindings 1)) - @[{:jolt/type :symbol :ns nil :name "let*"} - @[form-sym val-form] - @[{:jolt/type :symbol :ns nil :name "if"} - @[{:jolt/type :symbol :ns nil :name "some?"} form-sym] - then-form + (def temp (gensym "if_some__")) + @[(sym* "let*") @[temp val-form] + @[(sym* "if") @[(sym* "some?") temp] + @[(sym* "let*") @[form-sym temp] then-form] ;else-forms]]) (defn core-when-some @@ -2200,11 +2204,11 @@ [bindings & body] (def form-sym (in bindings 0)) (def val-form (in bindings 1)) - @[{:jolt/type :symbol :ns nil :name "let*"} - @[form-sym val-form] - @[{:jolt/type :symbol :ns nil :name "when"} - @[{:jolt/type :symbol :ns nil :name "some?"} form-sym] - ;body]]) + (def temp (gensym "when_some__")) + @[(sym* "let*") @[temp val-form] + @[(sym* "if") @[(sym* "some?") temp] + @[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]] + nil]]) (defn core-doto "Macro: (doto obj (method args)...) → let obj, call methods, return obj"