Does your macro provide & body arguments? Continue reading.
1. try splicing
Does your macro splice body into a try like so?
`(try ~@body (finally ...))
- Try passing
(catch Exception e)as your body arguments. Your macro can catch exceptions. - Try passing
(finally)as your body arguments. Your macro cannot invoke variables calledfinally.
For example:
(defmacro foo [& body]
`(try ~@body (finally 1)))
(foo (finally 1))
;; Syntax error compiling try at (REPL:1:1).
;; finally clause must be last in try expression
2. fn splicing
Does your macro splice body into a fn like so?
`(fn [] ~@body)
- Try passing
(recur)as your body arguments. Your macro now runs forever. - Try passing
{:pre [false]} (inc 1)as your body arguments. Your macro supports preconditions.
For example:
(defmacro bar [& body]
`((fn [] ~@body)))
(bar {:pre [false]} 1)
;; Execution error (AssertionError) at user/eval155$fn (REPL:1).
;; Assert failed: false
3. Composition of core macros
Does your macro expand to any of these macros?
- locking, binding, with-bindings, sync, with-local-vars, with-in-str, dosync, with-precision, with-loading-context, with-redefs, delay, lazy-seq, lazy-cat, future, pvalues
Does the call look like this?
`(<core-macro> ... ~@body)
You have inherited one or more of the above entertaining features.
For example:
(defmacro baz [& body]
`(locking 1
~@body))
(baz (finally 1))
;; Syntax error compiling try at (REPL:1:1).
;; finally clause must be last in try expression
How to fix it
Any time you have ~@body, do this instead:
`(let [res# (do ~@body)] res#)
It forces ~@body to always be in an expression position without a recur target.
If that’s too extreme, just wrap it in a (do ~@body) to force expression position.