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.
This commit is contained in:
Pietro Cerutti
2026-08-27 11:36:22 +00:00
parent cea6dd2261
commit 5c45e611a8
2 changed files with 29 additions and 9 deletions
+25 -9
View File
@@ -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
+4
View File
@@ -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)