For the record, I used this code to fetch the spec as a string.
@spec fetch_spec(module(), atom(), arity()) :: {:ok, String.t()} | {:error, any()}
def fetch_spec(mod, fun, arity) do
with {:ok, core} <- mod |> :code.which() |> :dialyzer_utils.get_core_from_beam(),
{:ok, rec_dict} <- :dialyzer_utils.get_record_and_type_info(core),
{:ok, spec_info, %{}} <- :dialyzer_utils.get_spec_info(mod, core, rec_dict),
{:ok, {{_file, _line}, {_tmp_contract, [_fun], type}, []}} <-
fetch_spec_info(spec_info, {mod, fun, arity}),
do: {:ok, type_to_string(fun, type)}
end
defp fetch_spec_info(spec_info, {mod, fun, arity}) do
with :error <- Map.fetch(spec_info, {mod, fun, arity}), do: {:error, :no_mfa_info}
end
defp type_to_quoted(fun, type) do
for {{:type, _, _, _} = type, _} <- type do
Code.Typespec.spec_to_quoted(fun, type)
end
end
defp type_to_string(fun, type) do
fun
|> type_to_quoted(type)
|> Enum.map_join(" ", &Macro.to_string/1)
end
The first naïve attempt was to use :erl_types.t_form_to_string/1
from :dialyzer
and it worked to some extent, returning somewhat alongside
~c"fun((['Elixir.Task.Supervisor':option()]) -> 'Elixir.Task.Supervisor':on_start())"
I struggled to find an elixir helper to parse this into an elixir format and reached for Code.Typespec.spec_to_quoted/2
which kinda worked but not without glitches.
The type it returned for Process.send_after/4
did not recornize the when option: {:abs, boolean()}
guard and gave me when option: var
which resulted in compile error. I will probably file a bug to the core.
Another glitch I found is function spec syntax. While (binary() -> boolean())
is translated to the expected erlang ~c"fun((binary()) -> boolean())"
, the any-arity notation lacks parentheses around the fun as ~c"fun(...) -> integer()"
. That I am not sure whether it’s expected or not.
Anyway.