Issue with __MODULE__ in a use statement with a sub module

Sorry about the title, this was the best I could come up with!

I’m not sure but this could be a slightly weird thing I am trying to do here, but I do not understand what is causing this behaviour.

I’m sure code will describe what I am attempting better than a description.

If you run the following code

defmodule Example.Base do
  defmacro __using__(opts) do
    quote do
      defmodule Sub do
        def sub_output do
          IO.puts unquote(opts[:module])
        end
      end

      def output do
        IO.puts unquote(opts[:module])
      end
    end
  end
end

defmodule Example do
  use Example.Base, module: __MODULE__
end

Example.output
Example.Sub.sub_output

You will get the following output

Elixir.Example
Elixir.Example.Sub

Does anyone know why the context of opts[:module] changes when being unquoted inside of a module definition?

p.s. sorry for lack of a good description, I am hoping a simple code example will speak for itself here :worried:

3 Likes

You mean why your module, which is obviously Example turned to be Example.Sub? :slight_smile:
If you don’t want to change the input you inserted from outside world to macro use bind_quoted [opts: opts] like this

quote bind_quoted: [opts: opts] do 
  # your code
end
3 Likes

The problem is that __MODULE__ is a call (a macro call, if I’m correct), so when you call a macro (like __using__) passing it as a parameter, you are not passing it’s value, but the code that makes the call (in Elixir’s AST: {:__MODULE__, _meta, nil}).

When you use unquote, you are evaluating the code inside of it in the macro definer environment (Example.Base). In your case, that translates to injecting the code that calls __MODULE__ (since opts[:module], in the macro definer environment, evaluates to that), and as one calls happens in the submodule and other in the main module, they output different values.

The bind_quoted solution provided by @PatNowak works perfectly, as it evaluates the code in the macro caller environment:

IO.inspect opts[:module] # => {:__MODULE__, _some_meta, nil}
quote bind_quoted: [opts: opts] do
  IO.inspect opts[:module] # => Example
  # Rest of your code
end

Just to make the unquote evaluation a bit clearer, if you replace your IO.puts calls to something like this in your code:

IO.puts unquote(__MODULE__)

You would get this output:

Elixir.Example.Base
Elixir.Example.Base

Understanding these evaluation semantics of macros helped me a lot in understanding them :smiley:

5 Likes

Ah that makes complete sense, I thought it would evaluate the __MODULE__ at the point it calls into the __using__ macro.

Thanks to both of you for your help :thumbsup:

3 Likes