[ExDoc] Why I get always term as a type of first argument in spec for any macro?

Hi, I’m just writing documentation for my library and I’m surprised seeing results of mix docs command.
Any macro that I declare and add specs like:

defmodule Example do
  @spec sample :: nil
  defmacro sample, do: nil
end

have always term as a first argument type (even if I don’t have any arguments!).

On html page it looks like:

sample()                                                                                                 (macro)

sample(term) :: nil

Here is my version of Elixir and Erlang (as always compiled using asdf):

Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.5.0-rc.0 (d3a66d7)

and here are my deps:

  • earmark 1.2.2 (Hex package) (mix)
    locked at 1.2.2 (earmark) f718159d
    ok
  • ex_doc 0.16.2 (Hex package) (mix)
    locked at 0.16.2 (ex_doc) 3b3e210e
    ok

There are no updates for ex_docs for now, I already tried removing _build and deps directories and I can reproduce it also in new project with exactly same results.

For me it looks like a bug, but maybe there is something that I don’t know? What do you think about it?

ping @josevalim

1 Like

I didn’t even know, that it would make sense to @spec defmacros.

All they take is AST, all they return is AST.

1 Like

@NobbZ: So you want to say that documenting macro by @spec is not expected and therefore not supported?

btw. AST (as Macro.t) is not only quoted expression (as Macro.expr), but also some literals like atoms or integers.

I wanted to document only few of all my macros and at least one of them accepts literals, so I would like to document it also it, but if it’s not supported then I do not need to force it.

I haven’t seen any macro in the :elixir package @speced, therefore I always assumed that it is not supported.

Also remember, that defmacro basically creates a function internally, which is prefixed MACRO- (or was it suffixed?), also it gets some arguments extra, that’s actually why you get __CALLER__/0 inside of an macro…

2 Likes

Correct.

If you do:

defmodule Blah do
  defmacro bloop(x), do: IO.inspect({__CALLER__, x}) |> elem(1)
end

It is ‘basically’ doing:

defmodule Blah do
  def MACRO-bloop({line, callers_env}, x) do
    callers_env = %{callers_env | line: line}
    IO.inspect({callers_env, x})
    x
  end
end

And yes, you can even call it as such:

iex> apply(Blah, :"MACRO-bloop", [{42, %{line: -1}}, 2])
{%{line: 42}, 2}
2
1 Like

Surprise! Macros are supported! Please see: github issue and there is much more interesting in PR:

@ScrimpyCat asked:

The macro specs include a first argument of type term(), I’m not sure if it’s best for that to be stripped out of the displayed spec or not. So I just left it be.

and @josevalim answered:

Thank you! We should definitely fix the spec name (by removing the “MACRO-” prefix) and remove the first argument. :slight_smile:

I do not see any MACRO- prefix, so it looks working, but I can see term() as first argument, so I will create an issue.

For all interested in this. i created PR that solves that issue.

2 Likes