From c9f141d45f16ec1988f86972ae3865227faa61b6 Mon Sep 17 00:00:00 2001 From: vieirandre Date: Thu, 26 Mar 2026 02:04:21 -0300 Subject: [PATCH 1/2] Validate single binding pair in let-like conditional macros --- src/sci/impl/namespaces.cljc | 10 ++++++++++ test/sci/core_test.cljc | 20 ++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/sci/impl/namespaces.cljc b/src/sci/impl/namespaces.cljc index 635e2772..a589e44f 100644 --- a/src/sci/impl/namespaces.cljc +++ b/src/sci/impl/namespaces.cljc @@ -174,10 +174,17 @@ g (last steps))))) +(defn- assert-single-binding-vector [expr macro-name bindings] + (when-not (vector? bindings) + (utils/throw-error-with-location (str macro-name " requires a vector for its binding") expr)) + (when-not (= 2 (count bindings)) + (utils/throw-error-with-location (str macro-name " requires exactly 2 forms in binding vector") expr))) + (defn if-let* ([&form &env bindings then] (if-let* &form &env bindings then nil)) ([&form _&env bindings then else & _oldform] + (assert-single-binding-vector &form "if-let" bindings) (let [form (bindings 0) tst (bindings 1) tmp (gensym "temp")] `(let [~tmp ~tst] @@ -191,6 +198,7 @@ ([&form &env bindings then] (if-some* &form &env bindings then nil)) ([&form _&env bindings then else & _oldform] + (assert-single-binding-vector &form "if-some" bindings) (let [form (bindings 0) tst (bindings 1) tmp (gensym "temp")] `(let [~tmp ~tst] @@ -203,6 +211,7 @@ (defn when-let* [&form _&env bindings & body] + (assert-single-binding-vector &form "when-let" bindings) (let [form (bindings 0) tst (bindings 1) tmp (gensym "temp")] `(let [~tmp ~tst] @@ -219,6 +228,7 @@ ~@body)))) (defn when-some* [&form _ bindings & body] + (assert-single-binding-vector &form "when-some" bindings) (let [form (bindings 0) tst (bindings 1) tmp (gensym "temp")] `(let [~tmp ~tst] diff --git a/test/sci/core_test.cljc b/test/sci/core_test.cljc index 507decd2..f2d8dec5 100644 --- a/test/sci/core_test.cljc +++ b/test/sci/core_test.cljc @@ -1288,13 +1288,29 @@ (is (= 2 (eval* "(if-let [foo nil] 1 2)"))) (is (= 2 (eval* "(if-let [foo false] 1 2)"))) (is (= 2 (eval* "(if-some [foo nil] 1 2)"))) - (is (= 1 (eval* "(if-some [foo false] 1 2)")))) + (is (= 1 (eval* "(if-some [foo false] 1 2)"))) + (is (thrown-with-msg? + #?(:clj Exception :cljs js/Error) + #"if-let requires exactly 2 forms in binding vector" + (eval* "(if-let [x (range 5) y (range 5)] x :else)"))) + (is (thrown-with-msg? + #?(:clj Exception :cljs js/Error) + #"if-some requires exactly 2 forms in binding vector" + (eval* "(if-some [x (range 5) y (range 5)] x :else)")))) (deftest whens-test (is (= nil (eval* "(when-let [foo nil] 1)"))) (is (= nil (eval* "(when-let [foo false] 1)"))) (is (= nil (eval* "(when-some [foo nil] 1)"))) - (is (= 1 (eval* "(when-some [foo false] 1)")))) + (is (= 1 (eval* "(when-some [foo false] 1)"))) + (is (thrown-with-msg? + #?(:clj Exception :cljs js/Error) + #"when-let requires exactly 2 forms in binding vector" + (eval* "(when-let [x (range 5) y (range 5)] x)"))) + (is (thrown-with-msg? + #?(:clj Exception :cljs js/Error) + #"when-some requires exactly 2 forms in binding vector" + (eval* "(when-some [x (range 5) y (range 5)] x)")))) (deftest read-string-eval-test (is (= 3 (eval* "(load-string \"1 2 3\")"))) From 755f521eec069c472bfa5a44cfc1464e7ff51678 Mon Sep 17 00:00:00 2001 From: vieirandre Date: Thu, 26 Mar 2026 14:45:21 -0300 Subject: [PATCH 2/2] Add error-location tests for invalid let-like bindings --- test/sci/error_test.cljc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/sci/error_test.cljc b/test/sci/error_test.cljc index e5a7867c..39433f8b 100644 --- a/test/sci/error_test.cljc +++ b/test/sci/error_test.cljc @@ -200,6 +200,21 @@ (catch Exception e (is (= [line col] ((juxt :line :column) (ex-data e))) snippet))))) +(deftest let-like-binding-arity-test + (doseq [[snippet expected-message] + [["(str (if-let [x 0 y 1] x))" "if-let requires exactly 2 forms in binding vector"] + ["(str (when-let [x 0 y 1] x))" "when-let requires exactly 2 forms in binding vector"] + ["(str (if-some [x 0 y 1] x))" "if-some requires exactly 2 forms in binding vector"] + ["(str (when-some [x 0 y 1] x))" "when-some requires exactly 2 forms in binding vector"]]] + (try + (sci.core/eval-string snippet) + (is false snippet) + (catch Exception e + (is (= expected-message #?(:clj (.getMessage e) + :cljs (.-message e))) + snippet) + (is (= [1 6] ((juxt :line :column) (ex-data e))) snippet))))) + #?(:cljs (deftest js-interop-test (is (str/includes?