by clemens (28.10.2024)

Warnings and errors in Elixir macros with the line number of the macro, not the macro calling site

tl;dr

When you’ve got an error or warning in an Elixir macro, the error message will give you the location of the code that is calling the macro, not the line in the macro itself.

To report the line inside the macro, add a location: :keep to your quote do.

I.e., consider this macro:

defmacro foo(a) do
    quote do
        case unquote(a) do
            x -> :single
            [] -> :empty
            nil -> :not_allowed
        end
    end
end

The compiler will now happily spit out a warning like this:

    warning: this clause cannot match because a previous clause at line 5 always matches
    
  5    use FooWeb.Plug
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    └─ lib/some_path/some_controller.ex:5

But this is the callers location, i.e. where you called the macro, and if your macros are long you might not spot that error. Now add the location: :keep to the quote do:

defmacro foo(a) do
    quote location: :keep do
        case unquote(a) do
            x -> :single
            [] -> :empty
            nil -> :not_allowed
        end
    end
end

And the error message will change to:

   warning: this clause cannot match because a previous clause at line 73 always matches
    
 74            [] -> :no_id
               ~~~~~~~~~~~~
    
    └─ lib/path_to_macro_module/macro_module.ex:74

Which makes finding the actual error way easier.