~technomancy/fennel#99: 
compiler special to detect vararg in current scope

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.

Status
RESOLVED IMPLEMENTED
Submitter
~andreyorst
Assigned to
No-one
Submitted
3 months ago
Updated
2 months ago
Labels
No labels applied.

~andreyorst referenced this from #97 3 months ago

~technomancy 2 months ago

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.

~technomancy REPORTED IMPLEMENTED 2 months ago

So this works, but it needs to use (get-scope) instead of _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-scope wasn't covered in the documentation, so I've added it.

~andreyorst 2 months ago

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.

~technomancy 2 months ago

Yeah! To be clear, we have had this for a long time; it's just that the docs for it were missing previously.

~andreyorst 2 months ago

That's even better

Register here or Log in to comment, or comment via email.