Macro check is there a specific function inside a module

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

https://elixirforum.com/t/how-to-access-module-attributes-in-a-macro-outside-quote/

So how can check and do this problem?
Thank you in advance

AaModule.__info__/1 isn’t even going to be defined until after AaModule is compiled.

What would test_macro generate if validator wasn’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:

1 Like

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

  def validator(_, value) do
    :ok
  end

But I just curious is there another way?

Thank you very much, I will try the link you sent

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?

Can I have your suggestion, Thank you in advance.

Update

I did this, but it can not be help full for me.

    File.read!(env.file)
    |> Code.string_to_quoted()
    |> Macro.postwalk(fn
      {:def, _, [{:validator, _, [:url | _fn_entry]} | _t]} ->
        IO.inspect("normal validator")

      quoted ->
        quoted
    end)

This is hard to do because you shouldn’t do it.

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, ...).

3 Likes

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 :rose:

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.

1 Like