Hi everyone, I want to create a macro, and this macro should check is there a specific function inside the module is using this macro or not?
for Example it checks 2 functions like: validator and main_validator, if yes, the macro uses it to create a function named builder
the builder uses these functions. when user call the macro it creates for it the builder function.
Code example
defmodule AaMacro do
defmacro test_macro(opts \\ [], do: block) do
quote do
# It should check is there function I want if yes
def builder(props) do
__MODULE.validator(props)
end
end
end
end
defmodule AaModule do
use AaMacro
def validator(prop) do
:ok
end
end
Based on my knowledge and have read this post, the __info__ function is not working in compile time
AaModule.__info__/1 isn’t even going to be defined until after AaModule is compiled.
What would test_macro generate if validatorwasn’t defined?
One option would be to have builder check (at runtime) that function_exported?(__MODULE__, :validator, 1) is true, and do something else if it isn’t.
A way to do it at compile-time would be something like what GenServer does to supply a default init callback:
About function_exported , I thought about it, but I could not find a way to check is there a function with :type pattern as first entry if yes it is going to work is not so do not use it.
For example:
guardedstruct do
field(:id, String.t(), validator: :validator, validator_module: :test_module)
field(:type, String.t())
field(:name, String.t(), default: "Joe")
end
def validator(:type, value) do
IO.inspect(value)
end
if there is a validator function for type, it should use it, but it just covers def function_exported?(module, function, arity)
I can create a global validator for all of them like
Is there a way to check a function inside the module that uses our macro, the way like @before_compile and Module.defines? works, but I can not check is there x function with :url atom as the first entry?
for example:
defmodule AA do
... our macro
def x(:url, value) do
....
end
def x(:image, value) do
....
end
end
I think I need to check ast? but how can access to the module ast to check, because I do not have the module file path, there is just module name like AA inside my macro?
There’s no difference between these three functions in normal Elixir code:
def many_heads(:foo, x), do: ...
def many_heads(:bar, x), do: ...
def one_head(arg, x) do
case arg do
:foo -> ...
:bar -> ...
end
end
def one_head_with_a_function(arg, x) do
inner_function(arg, x)
end
defp inner_function(:foo, x), do: ...
defp inner_function(:bar, x), do: ...
It would be impractical / impossible to write a macro that checks what you’re trying to check that treats all of many_heads, one_head and one_head_with_a_function alike - but it would be very confusing to write a macro that treats them differently.
The usual practice in this situation would be to split the function so that independently-overridable pieces have distinct names - url_validator(...) instead of validator(:url, ...).
Yes you are right, after seeing and using different situation like using when and how can find it which is about my code, I decided to delete this way and change the approach
You can also require the modules that need to implement validator and main_validator to have a behavior.
This isn’t solid, but if these modules are to use your own custom module, you can force them to have this behavior in __using__ macro and then you’d get warnings (or errors, if you pass —warnings-as-errors to mix) when they don’t implement required functions.