Quote seems to treat module names differently to atoms

Hi
I am generating code for modules from some data read in from file. The module names are formed from strings, and obviously I need to create module atoms. At first glance, prefixing the module name (string) with “Elixir.” and then using String.to_atom() seems to be the way to do it, but the atom produced that way is not working in code like the following:

quote do [{ unquote(module_atom), 123}] end

I have got around this by hand-crafting the AST for a module name, the following explains, but I am hoping there is a more elegant solution?

iex(1)> :"Elixir.A.B" == A.B
true

But:

iex(2)> quote do unquote(String.to_atom("Elixir.A.B")) end
A.B
iex(3)> quote do :"Elixir.A.B" end
A.B
iex(4)> quote do A.B end
{:__aliases__, [alias: false], [:A, :B]}

Hand-crafted AST:

iex(5)> {:__aliases__, [alias: false], String.split("A.B", ".") |> Enum.map(&String.to_atom/1)} == quote do A.B end
true

And:

iex(6)> module_name = "A.B"
"A.B"
iex(7)> module_atom = {:__aliases__, [alias: false], module_name |> String.split(".") |> Enum.map(&String.to_atom/1)}
{:__aliases__, [alias: false], [:A, :B]}
iex(8)> ast = quote do defmodule unquote(module_atom) do end; end
{:defmodule, [context: Elixir, import: Kernel], [{:__aliases__, [alias: false], [:A, :B]}, [do: nil]]}
iex(9)> Macro.to_string(ast)
"defmodule(A.B) do\n  nil\nend"

iex(10)> ast2 = quote do [{ unquote(module_atom), 123}] end
[{{:__aliases__, [alias: false], [:A, :B]}, 123}]
iex(11)> Macro.to_string(ast2)
"[{A.B, 123}]"

As compared to:

iex(12)> module_atom_2 = String.to_atom("Elixir."<>module_name)
A.B
iex(13)> ast3 = quote do [{ unquote(module_atom_2), 123}] end
[{A.B, 123}]
iex(14)> Macro.to_string(ast3)
"[A.B:, 123]"

which is obviously wrong

Module names are often represented by aliases, which are structures that are converted to atoms during compilation. This means that, while at runtime they are atoms, you do need to know the distinction when working at the macro/AST level.

However, in your examples above, they are mostly supposed to be used interchangeably because they will compile down to the same thing. The only exception seems to be here:

Macro.to_string(ast3)
"[A.B:, 123]"

Which is definitely a bug in Macro.to_string. Could you please file a bug report?

Other than that, can you provide more information when they do not behave the same? Or is it only the Macro.to_string one?

2 Likes

Thanks for your quick and succinct helpful reply. It will be my first bug report. This is the only place I have found it so far, and only in the case where the module name is in a tuple in a list, where ast3 is from [{ModuleName, blah}]

Macro.to_string(ast3)
"[A.B:, 123]"

See https://github.com/elixir-lang/elixir/issues/5270

1 Like

Its already fixed - one day turnaround. Impressive!!!