Lets say we have a macro which takes one required argument followed by optional positional arguments like
(require '[clojure.spec :as spec]
'[clojure.spec.gen :as gen])
(defmacro dress [what & clothes]
`(clojure.string/join " " '(~what ~@clothes)))
(dress "me")
=> "me"
(dress "me" :hat "favourite")
=> "me :hat favourite"
and we write it a spec for it like
(spec/def ::hat string?)
(spec/fdef dress
:args (spec/cat :what string?
:clothes (spec/keys* :opt-un [::hat]))
:ret string?)
we'll find that spec/exercise-fn
fails to exercise the macro
(spec/exercise-fn `dress)
;1. Unhandled clojure.lang.ArityException
; Wrong number of args (1) passed to: project/dress
even though the data generated by the functions generator is accepted just fine by the macro:
(def args (gen/generate (spec/gen (spec/cat :what string?
:clothes (spec/keys* :opt-un [::hat])))))
; args => ("mO792pj0x")
(eval `(dress ~@args))
=> "mO792pj0x"
(dress "mO792pj0x")
=> "mO792pj0x"
Defining a function and exercising it the same way works fine on the other hand:
(defn dress [what & clothes]
(clojure.string/join " " (conj clothes what)))
(spec/def ::hat string?)
(spec/fdef dress
:args (spec/cat :what string?
:clothes (spec/keys* :opt-un [::hat]))
:ret string?)
(dress "me")
=> "me"
(dress "me" :hat "favourite")
=> "me :hat favourite"
(spec/exercise-fn `dress)
=> ([("") ""] [("l" :hat "z") "l :hat z"] [("") ""] [("h") "h"] [("" :hat "") " :hat "] [("m") "m"] [("8ja" :hat "N5M754") "8ja :hat N5M754"] [("2vsH8" :hat "Z") "2vsH8 :hat Z"] [("" :hat "TL") " :hat TL"] [("q4gSi1") "q4gSi1"])
And if we take a look at the built in macros with similar definition patterns we'll see the very same issue:
(spec/exercise-fn `let)
; 1. Unhandled clojure.lang.ArityException
; Wrong number of args (1) passed to: core/let
One interesting thing is that exercise-fn
works fine when there's always one required named argument present:
(defmacro dress [what & clothes]
`(clojure.string/join " " '(~what ~@clothes)))
(spec/def ::hat string?)
(spec/def ::tie string?)
(spec/fdef dress
:args (spec/cat :what string?
:clothes (spec/keys* :opt-un [::hat] :req-un [::tie]))
:ret string?)
(dress "me" :tie "blue" :hat "favourite")
=> "me :tie blue :hat favourite"
(spec/exercise-fn `dress)
In other words: There seems to be some hidden arguments always passed to macros during normal invocation which aren't passed by spec. Sadly I'm not experienced enough with Clojure to know about such details, but a little bird told me that there are things named &env and &form.
But my question boils down to: Is it possible to spec a macro with named arguments in such a way that spec/exercise-fn
can give it a good workout?
Addendum:
Wrapping keys*
with an and
seems to break exercise-fn
again, even if it has a required named arg.