Nested `use` and pass options from one to

I have a module which provides some functionality, then another module that provides some other functionality on top of the first one.

I wrote this using nested use, but the result is that one of the modules receive the options in an unexpected format and I need to “open” such format to provide some default values. The code is the following

defmodule Blah do
  def hi do
    IO.puts("hi")
  end
end

defmodule Foo do
  defmacro __using__(opts \\ []) do
    IO.puts("Foo")
    IO.inspect(opts)
    IO.puts("Foo UNQUOTED")
    # I'd like to convert the alias to "unquoted" here so that I can
    # Keyword.put_new on `opts`
    # IO.inspect(opts)

    quote location: :keep do
      IO.puts("Foo QUOTED")
      IO.inspect(unquote(opts))

      def hello do
        nested_hello()
        IO.puts("hello")
        IO.inspect(unquote(opts))
      end
    end
  end
end

defmodule Bar do
  defmacro __using__(opts \\ []) do
    IO.puts("Bar")
    IO.inspect(opts)

    quote location: :keep do
      IO.puts("Bar QUOTED")
      IO.inspect(unquote(opts))
      use Foo, unquote(opts)

      def nested_hello do
        IO.puts("nested hello")
      end
    end
  end
end

defmodule Test do
  use Bar, name: Blah
end

Test.hello()

The output I get so far is:

$ elixir ./double_use.exs 
Bar
[name: {:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}]
Foo
[name: {:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}]
Bar QUOTED
[name: Blah]
Foo QUOTED
[name: Blah]
nested hello
hello
[name: Blah]

So I’m quite lost in how to pass the options to the other module.

Your output seems odd, things like Foo UNQUOTED don’t exist.

However, about the alias are you talking the :__aliases__? That’s because on the line use Bar, name: Blah the Blah is an alias, if you want it resolved to a full proper atom then you just need to take the arguments of the alias and combine them via Module.concat like:

iex(7)> name_alias = {:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}
{:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}
iex(8)> {:__aliases__, _, name_split} = name_alias
{:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}
iex(9)> Module.concat(name_split)
Blah

Are you sure you didn’t actually want to call use Bar, name: :Blah (where :Blah is an atom instead of a module alias)?

Thanks for the answer.
Module.concat would do the trick I guess.
I need to accept “anything” since the value is supplied by the user of the library, if he wants to pass an alias, I need to be able to solve it to the right module.

1 Like

If its an alias then Macro.expand/2 should do the trick:

iex> Macro.expand {:__aliases__, [line: 43, counter: -576460752303423480], [:Blah]}, __ENV__
Blah
1 Like

That’s what I tried but it doesn’t expand. I guess is a matter of context

1 Like