From 5c45e611a8738e5fc8ea7de8de39c34ef0840674 Mon Sep 17 00:00:00 2001 From: Pietro Cerutti Date: Wed, 26 Aug 2026 21:13:21 +0000 Subject: [PATCH] Extend the scope of truthy `if-let` bindings to the `fal` branch The `if-let` construct - together with the fact that only `nil` and `false` are falsey - is very handy to express monadic chains over maybe/optional values: ```janet (if-let [resp (make-http-request) # is nil on failure {:status stat :body body} resp # always truthy, binds `stat` and `body` check (= 200 stat) # continue only on 200 OK json (json/decode body true) # is nil on failure {:foo foo} json] foo) ``` However, none of the bindings are available in the false branch, which could be helpful to have for error reporting or fallback logic: ```janet (if-let [resp (make-http-request) {:status stat :body body} resp check (= 200 stat) json (json/decode body true) {:foo foo} json] foo (if (= 401 stat) # unauthorized, refresh the token (try-again) nil)) ``` This PR makes all symbols introduced by the bindings of an `if-let` available in all branches. --- src/boot/boot.janet | 34 +++++++++++++++++++++++++--------- test/suite-boot.janet | 4 ++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index f206b990..7b58270a 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -450,22 +450,38 @@ evaluate the `tru` form. If any are false or nil, evaluate the `fal` form. Bindings have the same syntax as the `let` macro.`` [bindings tru &opt fal] - (def len (length bindings)) + (def len (/ (length bindings) 2)) (if (= 0 len) (error "expected at least 1 binding")) - (if (odd? len) (error "expected an even number of bindings")) + (if-not (int? len) (error "expected an even number of bindings")) (def fal2 (if macexvar (macexvar fal) fal)) + (def syms (array/new len)) + (defn fill-syms [i] + (when (< i len) + (let [bl (in bindings (* i 2)) + sym (if (symbol? bl) bl (gensym))] + (put syms i sym)) + (fill-syms (+ 1 i)))) (defn aux [i] (if (>= i len) tru (do - (def bl (in bindings i)) - (def br (in bindings (+ 1 i))) + (def bl (in bindings (* 2 i))) + (def br (in bindings (+ 1 (* 2 i)))) (if (symbol? bl) - ~(if (def ,bl ,br) ,(aux (+ 2 i)) ,fal2) - ~(if (def ,(def sym (gensym)) ,br) - (do (def ,bl ,sym) ,(aux (+ 2 i))) - ,fal2))))) - (aux 0)) + ~(if (def ,bl ,br) ,(aux (+ 1 i)) ,fal2) + (do + (def sym (in syms i)) + ~(do (def ,sym ,br) (if (def ,bl ,sym) ,(aux (+ 1 i)) ,fal2))))))) + + (defn wrap [i] + (if (>= i len) + (aux 0) + ~(do + (def ,(in syms i) ,nil) + ,(wrap (+ 1 i))))) + + (fill-syms 0) + (wrap 0)) (defmacro when-with ``Similar to with, but if binding is false or nil, returns diff --git a/test/suite-boot.janet b/test/suite-boot.janet index fb2a9dd0..2b8c8842 100644 --- a/test/suite-boot.janet +++ b/test/suite-boot.janet @@ -141,6 +141,10 @@ (assert (= (if-let [[a b] my-array] a) 1) "if-let 5") (assert (= (if-let [{:a a :b b} {:a 1 :b 2}] b) 2) "if-let 6") (assert (= (if-let [[a b] nil] :t :f) :f) "if-let 7") +(assert (= (if-let [[a b] ["a" "b"]] a a) "a")) +(assert (= (if-let [[a b] ["a" "b"]] b b) "b")) +(assert (= (if-let [[a b] ["a" nil]] a a) "a")) +(assert (= (if-let [[a b] ["a" nil]] b b) nil)) # #1191 (var cnt 0)