Skip to content

Commit 862950d

Browse files
anmonteirodnolen
authored and
dnolen
committed
CLJS-1747: Port clojure.spec/assert over to ClojureScript
1 parent e7351a2 commit 862950d

File tree

3 files changed

+79
-9
lines changed

3 files changed

+79
-9
lines changed

src/main/cljs/cljs/spec.cljc

+30-7
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
; You must not remove this notice, or any other, from this software.
88

99
(ns cljs.spec
10-
(:refer-clojure :exclude [+ * and or cat def keys merge resolve])
10+
(:refer-clojure :exclude [+ * and or cat def keys merge resolve assert])
1111
(:require [cljs.core :as c]
1212
[cljs.analyzer :as ana]
13+
[cljs.env :as env]
1314
[cljs.analyzer.api :refer [resolve]]
1415
[clojure.walk :as walk]
1516
[cljs.spec.impl.gen :as gen]
@@ -44,7 +45,7 @@
4445
[env s]
4546
(if (namespace s)
4647
(let [v (resolve env s)]
47-
(assert v (str "Unable to resolve: " s))
48+
(clojure.core/assert v (str "Unable to resolve: " s))
4849
(->sym v))
4950
(symbol (str ana/*cljs-ns*) (str s))))
5051

@@ -138,7 +139,7 @@
138139
(let [unk #(-> % name keyword)
139140
req-keys (filterv keyword? (flatten req))
140141
req-un-specs (filterv keyword? (flatten req-un))
141-
_ (assert (every? #(clojure.core/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un))
142+
_ (clojure.core/assert (every? #(clojure.core/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un))
142143
"all keys must be namespace-qualified keywords")
143144
req-specs (into req-keys req-un-specs)
144145
req-keys (into req-keys (map unk req-un-specs))
@@ -180,7 +181,7 @@
180181
keys (mapv first pairs)
181182
pred-forms (mapv second pairs)
182183
pf (mapv #(res &env %) pred-forms)]
183-
(assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords")
184+
(clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords")
184185
`(cljs.spec/or-spec-impl ~keys '~pf ~pred-forms nil)))
185186

186187
(defmacro and
@@ -295,7 +296,7 @@
295296
keys (mapv first pairs)
296297
pred-forms (mapv second pairs)
297298
pf (mapv #(res &env %) pred-forms)]
298-
(assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords")
299+
(clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords")
299300
`(cljs.spec/alt-impl ~keys ~pred-forms '~pf)))
300301

301302
(defmacro cat
@@ -311,7 +312,7 @@
311312
pred-forms (mapv second pairs)
312313
pf (mapv #(res &env %) pred-forms)]
313314
;;(prn key-pred-forms)
314-
(assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords")
315+
(clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords")
315316
`(cljs.spec/cat-impl ~keys ~pred-forms '~pf)))
316317

317318
(defmacro &
@@ -355,7 +356,7 @@
355356
where each element conforms to the corresponding pred. Each element
356357
will be referred to in paths using its ordinal."
357358
[& preds]
358-
(assert (not (empty? preds)))
359+
(clojure.core/assert (not (empty? preds)))
359360
`(cljs.spec/tuple-impl '~(mapv #(res &env %) preds) ~(vec preds)))
360361

361362
(def ^:private _speced_vars (atom #{}))
@@ -467,3 +468,25 @@
467468
f# ~sym]
468469
(for [args# (gen/sample (gen (:args fspec#)) ~n)]
469470
[args# (apply f# args#)]))))
471+
472+
(defmacro ^:private init-compile-asserts []
473+
(let [compile-asserts (not (-> env/*compiler* deref :options :elide-asserts))]
474+
compile-asserts))
475+
476+
(defmacro assert
477+
"spec-checking assert expression. Returns x if x is valid? according
478+
to spec, else throws an error with explain-data plus ::failure of
479+
:assertion-failed.
480+
Can be disabled at either compile time or runtime:
481+
If *compile-asserts* is false at compile time, compiles to x. Defaults
482+
to the negation value of the ':elide-asserts' compiler option, or true if
483+
not set.
484+
If (check-asserts?) is false at runtime, always returns x. Defaults to
485+
value of 'cljs.spec/*runtime-asserts*', or false if not set. You can
486+
toggle check-asserts? with (check-asserts bool)."
487+
[spec x]
488+
`(if cljs.spec/*compile-asserts*
489+
(if cljs.spec/*runtime-asserts*
490+
(cljs.spec/assert* ~spec ~x)
491+
~x)
492+
~x))

src/main/cljs/cljs/spec.cljs

+40-1
Original file line numberDiff line numberDiff line change
@@ -1207,4 +1207,43 @@
12071207
(c/and (.lessThanOrEqual start val)
12081208
(.lessThan val end))
12091209

1210-
:else false))
1210+
:else false))
1211+
1212+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; assert ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1213+
(defonce
1214+
^{:dynamic true
1215+
:doc "If true, compiler will enable spec asserts, which are then
1216+
subject to runtime control via check-asserts? If false, compiler
1217+
will eliminate all spec assert overhead. See 'assert'.
1218+
Initially set to the negation of the ':elide-asserts' compiler option.
1219+
Defaults to true."}
1220+
*compile-asserts*
1221+
(s/init-compile-asserts))
1222+
1223+
(defonce ^{:private true
1224+
:dynamic true}
1225+
*runtime-asserts*
1226+
false)
1227+
1228+
(defn ^boolean check-asserts?
1229+
"Returns the value set by check-asserts."
1230+
[]
1231+
*runtime-asserts*)
1232+
1233+
(defn check-asserts
1234+
"Enable or disable spec asserts that have been compiled
1235+
with '*compile-asserts*' true. See 'assert'.
1236+
Initially set to boolean value of cljs.spec/*runtime-asserts*.
1237+
Defaults to false."
1238+
[^boolean flag]
1239+
(set! *runtime-asserts* flag))
1240+
1241+
(defn assert*
1242+
"Do not call this directly, use 'assert'."
1243+
[spec x]
1244+
(if (valid? spec x)
1245+
x
1246+
(let [ed (c/merge (assoc (explain-data* spec [] [] [] x)
1247+
::failure :assertion-failed))]
1248+
(throw (js/Error.
1249+
(str "Spec assertion failed\n" (with-out-str (explain-out ed))))))))

src/test/cljs/cljs/spec_test.cljs

+9-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@
5757
(let [xs [42 11 13 15 {:a 1 :b 2 :c 3} 1 2 3 42 43 44 11]]
5858
(is (= xs (s/unform s2 (s/conform s2 xs))))))
5959

60+
(deftest test-assert
61+
(s/def ::even-number (s/and number? even?))
62+
;; assertions off by default
63+
(is (= 42 (s/assert ::even-number 42)))
64+
(s/check-asserts true)
65+
(is (= 42 (s/assert ::even-number 42)))
66+
(is (thrown? js/Error (s/assert ::even-number 5))))
67+
6068
;; Copied from Clojure spec tests
6169

6270
(def even-count? #(even? (count %)))
@@ -198,4 +206,4 @@
198206

199207
(run-tests)
200208

201-
)
209+
)

0 commit comments

Comments
 (0)