Hi everyone!
I’m searching for a hint for a problem I cannot understand if it is in my code or a third party misbehaviour.
I’m currently working on a project linted by wonderful credo where I have enabled Credo.Check.Readability.Specs to enforce specs on public functions.
Now, I’m creating with a macro a bunch of functions with the same /arity but different matches based on a couple of parameters, e.g.
# example.exs
defmodule SpecMe do
defmacro __using__(attrs) do
for attr <- attrs do
quote do
def unquote(attr)(attr = unquote(attr)), do: Atom.to_string(attr)
def unquote(attr)(attr), do: attr |> Atom.to_string() |> String.reverse()
end
end
end
end
# usage
defmodule UseSpec do
use SpecMe, [:ab, :bc, :cd]
end
UseSpec.ab(:ab) |> IO.inspect()
UseSpec.ab(:bc) |> IO.inspect()
UseSpec.bc(:bc) |> IO.inspect()
UseSpec.bc(:cd) |> IO.inspect()
You can run the example to see it compiles and work with elixir example.exs
You can trigger the check on a credo-enabled project (configured with the check enabled) with mix credo example.exs
You should see a Functions should have a @spec type specification
( code also available via gist, but you’ll still need a credo-enabled project to run mix against it )
Things I tried:
- putting
@spec unquote(attr)(...)
wrapping a generic definition e.g.
@spec unquote(attr)(...) :: ...
def unquote(attr)(params)
- putting
@spec unquote(attr)(...)
before the first def - since my practical example is slightly more complex, putting it in a different quote/do block before the cycle, since I know the name before it, and returning the corresponding ast
Please note that also the name is dynamic, and more clauses are defined for the same name. If I refactor everything to have a static name and put a normal spec on it, it does not complain.
I’m therefore stuck in not understanding a couple of things:
- it seems to me I’m failing in setting @spec. I know
@
is a macro that wraps Module.get_attribute but since it is in fact settingspec
during compilation I imagine it has a special treatment, but I’m probably doing things wrong while trying to set it - I don’t fully understand why credo is pointing directly at my code inside
__using__
and not against the real module using the macro. It seems it is really pointing to thosedef
, but I may be missing a lot of things that happen during compilation
Anyone has any hint regarding this issue? Thanks in advance!
L