Problems with macros and dialyzer

I recently published the Audit library (described in another thread, hex, github).
It required capturing the current value of __ENV__ at the call-site,
so it had to be implemented as a macro. But it has the potential to create
space leaks, I wanted it turned off by default.
So the idea was to have two versions of the macro:

 @enabled? Application.compile_env(:audit, :enabled?, false)
 @key :__audit_trail__
...
  @spec payload(struct(), Macro.Env.t()) :: trail_t()
  def payload(r, e), do: {r, e.file, e.line} 
...
  @dialyzer {:nowarn_function, audit_fun: 2}
  @spec audit_fun(struct(), Macro.Env) :: struct()
  def audit_fun(r, e) do
    r |> struct([{@key, payload(r, e)}])
  end
...
  if @enabled? do
    defmacro audit(record) do
      quote generated: true do
        unquote(__MODULE__).audit_fun(unquote(record), __ENV__)
      end
    end
  else
    defmacro audit(record) do
      quote generated: true do
        unquote(record)
      end
    end
  end

And in the code that uses it, a compile-time decision is made as to whether
to include the __audit_trail__ field that it relies on for book-keeping.

I have two problems:

  1. when the option is turned on, I get lots of dialyzer errors at the places
    where the audit macro is used where:
%Foo{ x | bar: value }

is changed to:

%Foo{ audit(x) | bar: value }

to leverage the library.

It’s either:
Function ... will never be called.
or
Function ... has no local return.
or
The created anonymous function has no local return.

  1. Our CI pipeline mysteriously crashes while parallel compiling the Elixir code leaving no usable errors. (Is there a way to run the compiler single-threaded so we can at least isolate which file it’s crashing on?)

Am I doing something obviously wrong in my approach?
Somewhat baffled.

1 Like

For #1, try changing Macro.Env to Macro.Env.t in your specs. Essentially you are telling dialyzer than it should expect the atom Macro.Env as an argument, instead of the struct

Same errors, alas.