I’m playing around with creating a rather unidiomatic testing library and I’d like to constrain a macro call of a macro that my library exposes to only be available within the do
block of one of the macros that my library defines. Is there a way to do that with some meta programming?
Here’s an example of what I mean:
import MyLib, only: [:lib_setup]
# Should succeed
lib_setup do
MyLib.other_macro(42)
end
# Should fail (at compiletime or runtime)
MyLib.other_macro(42)
And the reason that I want to constrain it like so is because of how unidiomatic other_macro
is.
In your example, having only imported :lib_setup
and not run require MyLib
, the second call would fail and the only way for the first call to succeed would be if the lib_setup
call had traversed the do block and transformed the inner call into something that could succeed.
If that particular behavior wasn’t intended in the example, one option to “communicate” between macros is for lib_setup
to set a module attribute at the beginning of the returned quote
and unset it at the end, and then the inner macro can look for the presence of that attribute to ensure it’s being called in a proper context.
1 Like
Yeah you’re probably right on the import call. In my actual code I’m doing a full import but I was trying to simplify it for the example.
Storing a module attribute should work! I’ll try it out and report back.
I think it should be possible. In this case other_macro
doesn’t even have to be itself a macro: lib_setup
being a macro, it could well transform the whole AST and give other_macro
- or rather not_a_macro
- its meaning.
A bit like Nx.Defn
redefines what +
or Nx.exp
would mean in its context.
You should be able to do something like this with Macro.prewalk/2
:
defmacro lib_test(do: block_ast) do
Macro.prewalk_ast(block_ast, fn
{:not_a_macro, _, args} -> handle_not_a_macro(args)
other -> other
end)
end
Hope this helps / makes sense