1
votes

Elixir 1.3.0-rc1 compiler can't compile one of my macros. It was okay in Elixir 1.2.6.

defmodule M do
  defmacro ifa(a, exp) do
    if (Macro.expand_once(a, __ENV__)), do: exp
  end
end

defmodule Foo do
  @flag true

  require M
  def main do
    M.ifa (@flag), do: IO.puts 123
  end
end

Foo.main

The compiler complains about the attribute.

% /tmp/elixir-1.3.0-rc1/bin/elixir foobar.exs
** (ArgumentError) could not call get_attribute on module M because it was already compiled
    (elixir) lib/module.ex:1144: Module.assert_not_compiled!/2
    (elixir) lib/module.ex:1066: Module.get_attribute/3
    (elixir) lib/kernel.ex:2360: Kernel.do_at/4
    (elixir) expanding macro: Kernel.@/1
    foobar.exs:12: M.ifa/2
    expanding macro: M.ifa/2
    foobar.exs:12: Foo.main/0


% /tmp/elixir-1.2.6/bin/elixir foobar.exs
123

I wonder why Foo is compiled before expanding the macro. What is changed in 1.3?

1
I can reproduce the same behavior with 1.2.6 and the latest commit on 1.3 branch as of now. I would recommend asking on the IRC channel (#elixir-lang on freenode) or the official Google Group if you don't get an answer here. This might be a bug as it breaks code that worked fine in 1.2.Dogbert

1 Answers

4
votes

Elixir actually found a bug in your code! :D

In your macro, when you use __ENV__, you are expanding the user quoted expression in the context of the module that defines the macro and not in the context of the caller. The solution is to use __CALLER__ to ensure @flag gets properly expanded using Foo's context in both Elixir v1.2 and v1.3:

defmodule M do
  defmacro ifa(a, exp) do
    if (Macro.expand_once(a, __CALLER__)), do: exp
  end
end

Thank you for trying Elixir v1.3-rc!