Vararg currently makes writing macros unsound. A macro that wraps user code can break in an unexpected way if user code uses vararg
..., and the macro wraps code in a function.
For example, if we were to implement
do as a macro, to prevent #97 from happening we would have to analyze the current scope at compile time to detect vararg:
(macro do+ [...] `((fn  ,...))) (fn foo [x ...] (and x (do+ (print ...) x))) ;; expanded to (fn foo [x ...] (and x (fn  (print ...) x))) ;; which results in 'unexpected vararg' error
Such macro can't analyze its body for
... vararg presence. However if we could analyze upper scope similar to the
in-scope? special we could adjust macro generation to produce the following code:
(macro do+ [...] (if (vararg-in-scope?) `((fn [...] ,...) ...) `((fn  ,...))))
Thus fixing the issue. If vararg is not used by the macro body, then there's no problem, and if it is then we are covered. Unfortunatelly
(in-scope? '...) doesn't work on
... directly, so currently it is impossible to cover such scenario.
We might have this already exposed:(fn abc  (eval-compiler (print (view _SCOPE.vararg)))) ; -> false (fn xyz [...] (eval-compiler (print (view _SCOPE.vararg)))) ; -> true
Not sure if that gives us everything we need tho.
So this works, but it needs to use
_SCOPE:(macro do+ [...] (if (. (get-scope) :vararg) `((fn [...] ,...) ...) `((fn  ,...)))) (fn abc [...] (do+ ...)) (fn def [x] (do+ x (print x)))
compiles to:local function abc(...) local function _1_(...) return ... end return _1_(...) end local function def(x) local function _2_() return print(x) end return _2_() end
I noticed that
get-scopewasn't covered in the documentation, so I've added it.
thanks! Really glad that we have it before 1.0.0. as I'm planning on making 1.0.0 mandatory for most of the libs when in will be out.
Yeah! To be clear, we have had this for a long time; it's just that the docs for it were missing previously.