Skip to content

Commit dcc29d8

Browse files
puredangerrichhickey
authored andcommitted
specs for let, if-let, when-let
Signed-off-by: Rich Hickey <richhickey@gmail.com>
1 parent df2749c commit dcc29d8

File tree

3 files changed

+68
-14
lines changed

3 files changed

+68
-14
lines changed

src/clj/clojure/core/specs.clj

+60-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,63 @@
11
(ns ^{:skip-wiki true} clojure.core.specs
22
(:require [clojure.spec :as s]))
33

4-
(alias 'cc 'clojure.core)
4+
;;;; destructure
5+
6+
(s/def ::local-name (s/and simple-symbol? #(not= '& %)))
7+
8+
(s/def ::binding-form
9+
(s/or :sym ::local-name
10+
:seq ::seq-binding-form
11+
:map ::map-binding-form))
12+
13+
;; sequential destructuring
14+
15+
(s/def ::seq-binding-form
16+
(s/cat :elems (s/* ::binding-form)
17+
:rest (s/? (s/cat :amp #{'&} :form ::binding-form))
18+
:as (s/? (s/cat :as #{:as} :sym ::local-name))))
19+
20+
;; map destructuring
21+
22+
(s/def ::keys (s/coll-of ident? :kind vector?))
23+
(s/def ::syms (s/coll-of symbol? :kind vector?))
24+
(s/def ::strs (s/coll-of simple-symbol? :kind vector?))
25+
(s/def ::or (s/map-of simple-symbol? any?))
26+
(s/def ::as ::local-name)
27+
28+
(s/def ::map-special-binding
29+
(s/keys :opt-un [::as ::or ::keys ::syms ::strs]))
30+
31+
(s/def ::map-binding (s/tuple ::binding-form any?))
32+
33+
(s/def ::ns-keys
34+
(s/tuple
35+
(s/and qualified-keyword? #(-> % name #{"keys" "syms"}))
36+
(s/coll-of simple-symbol? :kind vector?)))
37+
38+
(s/def ::map-bindings
39+
(s/every (s/or :mb ::map-binding
40+
:nsk ::ns-keys
41+
:msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {}))
42+
43+
(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding))
44+
45+
;; bindings
46+
47+
(s/def ::binding (s/cat :binding ::binding-form :init-expr any?))
48+
(s/def ::bindings (s/and vector? (s/* ::binding)))
49+
50+
;; let, if-let, when-let
51+
52+
(s/fdef clojure.core/let
53+
:args (s/cat :bindings ::bindings
54+
:body (s/* any?)))
55+
56+
(s/fdef clojure.core/if-let
57+
:args (s/cat :bindings (s/and vector? ::binding)
58+
:then any?
59+
:else (s/? any?)))
60+
61+
(s/fdef clojure.core/when-let
62+
:args (s/cat :bindings (s/and vector? ::binding)
63+
:body (s/* any?)))

test/clojure/test_clojure/errors.clj

+2-7
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,10 @@
4242
(refer 'clojure.core :rename '{with-open renamed-with-open})
4343

4444
; would have used `are` here, but :line meta on &form doesn't survive successive macroexpansions
45-
(doseq [[msg-regex-str form] [["if-let .* in %s:\\d+" '(if-let [a 5
46-
b 6]
47-
true nil)]
48-
["let .* in %s:\\d+" '(let [a])]
49-
["let .* in %s:\\d+" '(let (a))]
50-
["renamed-with-open .* in %s:\\d+" '(renamed-with-open [a])]]]
45+
(doseq [[msg-regex-str form] [["renamed-with-open" "(renamed-with-open [a])"]]]
5146
(is (thrown-with-msg? IllegalArgumentException
5247
(re-pattern (format msg-regex-str *ns*))
53-
(macroexpand form)))))
48+
(macroexpand (read-string form))))))
5449

5550
(deftest extract-ex-data
5651
(try

test/clojure/test_clojure/special.clj

+6-6
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,19 @@
6767
(is (= [1 2 3] [b c d]))))
6868

6969
(deftest keywords-not-allowed-in-let-bindings
70-
(is (thrown-with-msg? Exception #"Unsupported binding form: :a"
70+
(is (thrown-with-msg? Exception #"did not conform to spec"
7171
(eval '(let [:a 1] a))))
72-
(is (thrown-with-msg? Exception #"Unsupported binding form: :a/b"
72+
(is (thrown-with-msg? Exception #"did not conform to spec"
7373
(eval '(let [:a/b 1] b))))
74-
(is (thrown-with-msg? Exception #"Unsupported binding form: :a"
74+
(is (thrown-with-msg? Exception #"did not conform to spec"
7575
(eval '(let [[:a] [1]] a))))
76-
(is (thrown-with-msg? Exception #"Unsupported binding form: :a/b"
76+
(is (thrown-with-msg? Exception #"did not conform to spec"
7777
(eval '(let [[:a/b] [1]] b)))))
7878

7979
(deftest namespaced-syms-only-allowed-in-map-destructuring
80-
(is (thrown-with-msg? Exception #"Can't let qualified name: a/x"
80+
(is (thrown-with-msg? Exception #"did not conform to spec"
8181
(eval '(let [a/x 1, [y] [1]] x))))
82-
(is (thrown-with-msg? Exception #"Can't let qualified name: a/x"
82+
(is (thrown-with-msg? Exception #"did not conform to spec"
8383
(eval '(let [[a/x] [1]] x)))))
8484

8585
(deftest or-doesnt-create-bindings

0 commit comments

Comments
 (0)