Alex Antonov
Alex Antonov

Reputation: 15146

How could I make function on a module defined only if all macroses has been run?

I'm writing a module A which defines macroses and these macroses are used by another module B

defmodule A do
  defmacro __using__(_) do
    quote do
      import A, only: [macross: 1]

      def call
        IO.inspect @macross_value
      end
    end
  end

  defmacro macross(value)
    quote do
      @macross_value value
    end 
  end
end

defmodule B do
  use A

  macross 6
end

Unfortunately, B.call => nil not a 6. It's because when def call is running macross in module B hasn't been runned yet. How could I fix it?

How could I make function on a module defined only if all macroses has been run via use? Is there any more interesting solution?

Upvotes: 1

Views: 67

Answers (1)

Dogbert
Dogbert

Reputation: 222128

You can use @before_compile to define the functions after the module has completed its initial evaluation and is about to be compiled. In the __before_compile__/1 callback, you'll need to return an AST that defines the function.

defmodule A do
  defmacro __using__(_) do
    quote do
      import A, only: [macross: 1]
      @before_compile A
    end
  end

  defmacro macross(value) do
    quote do
      @macross_value unquote(value)
    end 
  end

  defmacro __before_compile__(_) do
    quote do
      def call do
        IO.inspect @macross_value
      end
    end
  end
end

defmodule B do
  use A

  macross 6
end

B.call
6

This doesn't ensure that the macro has been called by the user at least once though. For that, you can wrap the def call with a check that the module attribute is defined:

defmacro __before_compile__(_) do
  quote do
    if Module.get_attribute(__MODULE__, :macross_value) do
      def call do
        IO.inspect @macross_value
      end
    end
  end
end

Now, if the attribute hasn't been defined, call/0 will not be defined on the module and you'll get:

** (UndefinedFunctionError) function B.call/0 is undefined or private
    B.call()
    (elixir) lib/code.ex:376: Code.require_file/2

Upvotes: 4

Related Questions