Tidy up Typespec API, improve docs
This commit is contained in:
+252
-199
@@ -1,24 +1,69 @@
|
||||
defmodule Kernel.Typespec do
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
Holds macros and functions for working with typespecs.
|
||||
|
||||
The attributes `@type`, `@opaque`, `@typep`, `@spec` and
|
||||
`@callback` available in modules are handled by the equivalent
|
||||
macros defined by this module.
|
||||
|
||||
See http://www.erlang.org/doc/reference_manual/typespec.html
|
||||
for more information.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Defines a type.
|
||||
This macro is the one responsible to handle the attribute @type.
|
||||
|
||||
## Examples
|
||||
|
||||
@type my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro deftype(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:type, (quote line: :keep, do: unquote(type)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines an opaque type.
|
||||
This macro is the one responsible to handle the attribute @opaque.
|
||||
|
||||
## Examples
|
||||
|
||||
@opaque my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro defopaque(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:opaque, (quote line: :keep, do: unquote(type)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a private type.
|
||||
This macro is the one responsible to handle the attribute @typep.
|
||||
|
||||
## Examples
|
||||
|
||||
@typep my_type :: atom
|
||||
|
||||
"""
|
||||
defmacro deftypep(type) do
|
||||
quote do
|
||||
Kernel.Typespec.deftype(:typep, (quote line: :keep, do: unquote(type)), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a spec.
|
||||
This macro is the one responsible to handle the attribute @spec.
|
||||
|
||||
## Examples
|
||||
|
||||
@spec add(number, number), do: number
|
||||
|
||||
"""
|
||||
defmacro defspec(spec, block) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:spec, (quote line: :keep, do: unquote spec),
|
||||
@@ -26,6 +71,15 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback.
|
||||
This macro is the one responsible to handle the attribute @callback.
|
||||
|
||||
## Examples
|
||||
|
||||
@callback add(number, number), do: number
|
||||
|
||||
"""
|
||||
defmacro defcallback(spec, block) do
|
||||
quote do
|
||||
Kernel.Typespec.defspec(:callback, (quote line: :keep, do: unquote spec),
|
||||
@@ -33,6 +87,174 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_type(module, kind, { name, _, vars } = type) when kind in [:type, :typep, :opaque] do
|
||||
{ kind, export } =
|
||||
case kind do
|
||||
:type -> { :type, true }
|
||||
:typep -> { :type, false }
|
||||
:opaque -> { :opaque, true }
|
||||
end
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec module, :export_type, [{ name, length(vars) }]
|
||||
end
|
||||
|
||||
type
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, { tuple, definition }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, { tuple, definition }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
(private, opaque or not). This function is only available
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given spec.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a callback.
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
:lists.any(match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a spec clause back to Elixir AST.
|
||||
Returns a 2-items tuple with the spec arguments and return result.
|
||||
"""
|
||||
def spec_to_ast({ :type, _line, :fun, [{:type, _, :product, args},result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ args, typespec_to_ast(result) }
|
||||
end
|
||||
|
||||
def spec_to_ast({ :type, _, :fun, [] }) do
|
||||
{ [], quote do: term }
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type clause back to Elixir AST.
|
||||
"""
|
||||
def type_to_ast({ name, type, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all types available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is the type (`:typep`, `:type` and `:opaque`).
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_types(module) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
exported_types = lc { :attribute, _, :export_type, types } inlist abstract_code, do: types
|
||||
exported_types = List.flatten(exported_types)
|
||||
|
||||
lc { :attribute, _, kind, { name, _, args } = type } inlist abstract_code, kind in [:opaque, :type] do
|
||||
cond do
|
||||
kind == :opaque -> { :opaque, type }
|
||||
List.member?(exported_types, { name, length(args) }) -> { :type, type }
|
||||
true -> { :typep, type }
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all specs available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_specs(module) do
|
||||
from_abstract_code(module, :spec)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all callbacks available from the beam.
|
||||
|
||||
It is returned as a list of tuples where the first
|
||||
element is spec name and arity and the second is the spec.
|
||||
|
||||
The module has to have a corresponding beam file
|
||||
on the file system.
|
||||
"""
|
||||
def beam_callbacks(module) do
|
||||
from_abstract_code(module, :callback)
|
||||
end
|
||||
|
||||
defp from_abstract_code(module, kind) do
|
||||
case abstract_code(module) do
|
||||
{ :ok, abstract_code } ->
|
||||
lc { :attribute, _, abs_kind, value } inlist abstract_code, kind == abs_kind, do: value
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, { _, [{ :abstract_code, { raw_abstract_v1, abstract_code } }] } } ->
|
||||
{ :ok, abstract_code }
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code_beam(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
@@ -44,7 +266,7 @@ defmodule Kernel.Typespec do
|
||||
do_deftype(kind, type, { :term, caller.line, nil }, caller)
|
||||
end
|
||||
|
||||
defp do_deftype(raw_kind, { name, _, args }, definition, caller) do
|
||||
defp do_deftype(kind, { name, _, args }, definition, caller) do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -52,26 +274,13 @@ defmodule Kernel.Typespec do
|
||||
lc(arg inlist args, do: variable(arg))
|
||||
end
|
||||
|
||||
{ kind, export } =
|
||||
case raw_kind do
|
||||
:type -> { :type, true }
|
||||
:typep -> { :type, false }
|
||||
:opaque -> { :opaque, true }
|
||||
end
|
||||
|
||||
vars = lc { :var, _, var } inlist args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = lc { :var, _, _ } = var inlist args, do: var
|
||||
type = { name, spec, vars }
|
||||
|
||||
Module.compile_typespec caller.module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(caller.module, :export_type, [{ name, length(vars) }])
|
||||
end
|
||||
|
||||
{ raw_kind, type }
|
||||
define_type(caller.module, kind, type)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -83,224 +292,68 @@ defmodule Kernel.Typespec do
|
||||
code
|
||||
end
|
||||
|
||||
## Helpers
|
||||
## To AST conversion
|
||||
|
||||
@doc """
|
||||
Returns true if the current module defines a given type
|
||||
(opaque or not). This function is only available for modules
|
||||
being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = match?({ ^name, _, vars } when length(vars) == arity, &1)
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback from a spec if one is available.
|
||||
Returns true if successful, false otherwise.
|
||||
"""
|
||||
def callback_from_spec(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Module.compile_typespec(module, :callback, { tuple, v })
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns type spec retrieved from a BEAM file in a readable form
|
||||
"""
|
||||
def to_binary(typespec) do
|
||||
Macro.to_binary(typespec_to_ast(typespec))
|
||||
end
|
||||
|
||||
## to_ast
|
||||
|
||||
defp typespec_to_ast({{:spec, {name, _arity}}, args}) do
|
||||
contents = lc arg inlist args do
|
||||
{:__block__, _, [v]} = spec_typespec_to_ast(arg)
|
||||
[product|args] = Enum.reverse(v)
|
||||
args = Enum.reverse(args)
|
||||
quote do
|
||||
@spec unquote(name)(unquote_splicing(args)), unquote(product)
|
||||
end
|
||||
end
|
||||
quote do
|
||||
unquote_splicing(contents)
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, {name, type, args}}) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: @type unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:typep, {name, type, args}}) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: @typep unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:opaque, {name, type, args}}) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: @opaque unquote(name)(unquote_splicing(args)) :: unquote(typespec_to_ast(type))
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :tuple, :any}) do
|
||||
defp typespec_to_ast({ :type, line, :tuple, :any }) do
|
||||
typespec_to_ast({:type, line, :tuple, []})
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, line, :tuple, args}) do
|
||||
defp typespec_to_ast({ :type, line, :tuple, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
{ :{}, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :binary, [arg1, arg2]}) do
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
arg2 == 0 ->
|
||||
quote do: <<_ :: unquote(arg1)>>
|
||||
quote line: line, do: <<_ :: unquote(arg1)>>
|
||||
arg1 == 0 ->
|
||||
quote do: <<_ :: _ * unquote(arg2)>>
|
||||
quote line: line, do: <<_ :: _ * unquote(arg2)>>
|
||||
true ->
|
||||
quote do: <<_ :: unquote(arg1) * unquote(arg2)>>
|
||||
quote line: line, do: <<_ :: unquote(arg1) * unquote(arg2)>>
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :union, args}) do
|
||||
defp typespec_to_ast({ :type, line, :union, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
Enum.reduce tl(args), hd(args),
|
||||
fn(arg, expr) ->
|
||||
quote do: unquote(expr) | unquote(arg)
|
||||
end
|
||||
fn(arg, expr) -> { :|, line, [expr, arg] } end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _line, :fun, [{:type, _, :product, args},result]}) do
|
||||
defp typespec_to_ast({ :type, line, :fun, [{:type, _, :product, args},result] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do
|
||||
fun(unquote_splicing(args ++ [[do: typespec_to_ast(result)]]))
|
||||
end
|
||||
{ :fun, line, args ++ [[do: typespec_to_ast(result)]] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, :fun, []}) do
|
||||
quote do: fun()
|
||||
defp typespec_to_ast({ :type, line, :fun, [] }) do
|
||||
{ :fun, line, [] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:type, _, name, args}) do
|
||||
defp typespec_to_ast({ :type, line, name, args }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(name)(unquote_splicing(args))
|
||||
{ name, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:var, line, var}) do
|
||||
quote hygiene: false, do: unquote({var, line, nil})
|
||||
defp typespec_to_ast({ :var, line, var }) do
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:remote_type, _line, [mod, name, args]}) do
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do: unquote(typespec_to_ast(mod)).unquote(typespec_to_ast(name))(unquote_splicing(args))
|
||||
dot = { :., line, [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
{ dot, line, args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({:ann_type, _line, [var, type]}) do
|
||||
quote do: unquote(typespec_to_ast(var)) :: unquote(typespec_to_ast(type))
|
||||
defp typespec_to_ast({ :ann_type, line, [var, type] }) do
|
||||
{ :::, line, [typespec_to_ast(var), typespec_to_ast(type)] }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({t, _line, imm}) when is_atom(t) do
|
||||
imm
|
||||
defp typespec_to_ast({ t, _line, atom }) when is_atom(t) do
|
||||
atom
|
||||
end
|
||||
|
||||
defp spec_typespec_to_ast({:type, _line, :fun, [{:type, _, :product, args},result]}) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
quote do
|
||||
[unquote_splicing(args), [do: unquote(typespec_to_ast(result))]]
|
||||
end
|
||||
end
|
||||
|
||||
defp spec_typespec_to_ast({:type, _, :fun, []}) do
|
||||
quote do: []
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves all type specifications from a module.
|
||||
|
||||
The module has to have a corresponding beam file on the file system.
|
||||
"""
|
||||
def types(module) do
|
||||
types(module, nil, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves all type specifications from a module with a given name and arity.
|
||||
|
||||
The module has to have a corresponding beam file on the file system.
|
||||
"""
|
||||
def types(module, type, arity) do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
exported_types = List.flatten(lc {:attribute, _, :export_type, types} inlist abstract_code, do: types)
|
||||
lc {:attribute, _, type_a, {t, _, a} = ti} inlist abstract_code, type_a in [:opaque, :type], (nil?(type) and nil?(arity)) or (t == type and length(a) == arity) do
|
||||
cond do
|
||||
type_a == :opaque ->
|
||||
{:opaque, ti}
|
||||
List.member?(exported_types, {t, length(a)}) ->
|
||||
{:type, ti}
|
||||
true ->
|
||||
{:typep, ti}
|
||||
end
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Retrieves all functions specifications from a module.
|
||||
|
||||
The module has to have a corresponding beam file on the file system.
|
||||
"""
|
||||
def specs(module) do
|
||||
specs(module, nil, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves all functions specifications from a module for a given name and arity.
|
||||
|
||||
The module has to have a corresponding beam file on the file system.
|
||||
"""
|
||||
def specs(module, function, arity) do
|
||||
case abstract_code(module) do
|
||||
{:ok, abstract_code} ->
|
||||
lc {:attribute, _, :spec, {{f,a}=fa, s}} inlist abstract_code, (nil?(function) and nil?(arity)) or (f == function and a == arity), do: {{:spec, fa}, s}
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp abstract_code(module) do
|
||||
case :beam_lib.chunks(abstract_code_beam(module), [:abstract_code]) do
|
||||
{:ok, {_, [{:abstract_code, {raw_abstract_v1, abstract_code}}]}} ->
|
||||
{:ok, abstract_code}
|
||||
_ ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp abstract_code_beam(module) when is_atom(module) do
|
||||
case :code.which(module) do
|
||||
:non_existing -> module
|
||||
file -> file
|
||||
end
|
||||
end
|
||||
defp abstract_code_beam(binary) when is_binary(binary) do
|
||||
binary
|
||||
end
|
||||
|
||||
## Typespec conversion
|
||||
## From AST conversion
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
|
||||
@@ -400,7 +453,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, line, arguments}, vars, caller) do
|
||||
IO.write "#{caller.file}.#{caller.line}: warning: string() type use is discouraged. For character lists, use char_list() type, for strings, String.t()\n"
|
||||
IO.write "#{caller.file}:#{caller.line}: warning: string() type use is discouraged. For character lists, use char_list() type, for strings, String.t()\n"
|
||||
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
|
||||
{ :type, line, :string, arguments }
|
||||
end
|
||||
@@ -432,7 +485,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise(ArgumentError, message: "Unexpected list #{inspect l}")
|
||||
raise ArgumentError, message: "Unexpected list #{inspect l}"
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
|
||||
@@ -276,6 +276,7 @@ defmodule Protocol.DSL do
|
||||
# We need to use :lists because Enum is not available yet
|
||||
require :lists, as: L
|
||||
|
||||
@doc false
|
||||
def args_and_body(module, name, arity) do
|
||||
# Generate arguments according the arity. The arguments
|
||||
# are named xa, xb and so forth. We cannot use string
|
||||
@@ -304,6 +305,19 @@ defmodule Protocol.DSL do
|
||||
{ args, body }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def callback_from_spec(module, name, arity) do
|
||||
tuple = { name, arity }
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = lc { k, v } inlist specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
defp conversions_for(module) do
|
||||
only = Module.get_attribute(module, :only)
|
||||
except = Module.get_attribute(module, :except)
|
||||
@@ -378,7 +392,7 @@ defmodule Protocol.DSL do
|
||||
|
||||
# Convert the spec to callback if possible,
|
||||
# otherwise generate a dummy callback
|
||||
Kernel.Typespec.callback_from_spec(__MODULE__, name, arity) ||
|
||||
Protocol.DSL.callback_from_spec(__MODULE__, name, arity) ||
|
||||
@callback unquote(name)(unquote_splicing(type_args)), do: term
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,9 +33,6 @@ data_table(Module) ->
|
||||
docs_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([o, Module]).
|
||||
|
||||
spec_table(Module) ->
|
||||
?ELIXIR_ATOM_CONCAT([s, Module]).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
%% Transformation of args and scope into a compiled erlang call.
|
||||
@@ -87,7 +84,6 @@ compile(Line, Module, Block, Vars, #elixir_scope{} = S) when is_atom(Module) ->
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
ets:delete(docs_table(Module)),
|
||||
ets:delete(spec_table(Module)),
|
||||
elixir_def:delete_table(Module),
|
||||
elixir_import:delete_table(Module)
|
||||
end;
|
||||
@@ -130,10 +126,6 @@ build(Line, File, Module) ->
|
||||
DocsTable = docs_table(Module),
|
||||
ets:new(DocsTable, [ordered_set, named_table, public]),
|
||||
|
||||
%% Holds specs in a format it is easier to query.
|
||||
SpecTable = spec_table(Module),
|
||||
ets:new(SpecTable, [duplicate_bag, named_table, public]),
|
||||
|
||||
%% We keep a separated table for function definitions
|
||||
%% and another one for imports. We keep them in different
|
||||
%% tables for organization and speed purpose (since the
|
||||
|
||||
@@ -21,42 +21,42 @@ defmodule Typespec.Test.Type do
|
||||
spec = test_module do
|
||||
@type mytype
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:term,[]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a single type" do
|
||||
spec = test_module do
|
||||
@type mytype :: term
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:term,[]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:term,[]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an atom" do
|
||||
spec = test_module do
|
||||
@type mytype :: :atom
|
||||
end
|
||||
assert {:type,{:mytype,{:atom,_,:atom},[]}} = spec
|
||||
assert {:mytype,{:atom,_,:atom},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an atom alias" do
|
||||
spec = test_module do
|
||||
@type mytype :: Atom
|
||||
end
|
||||
assert {:type,{:mytype,{:atom,_,Atom},[]}} = spec
|
||||
assert {:mytype,{:atom,_,Atom},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an integer" do
|
||||
spec = test_module do
|
||||
@type mytype :: 10
|
||||
end
|
||||
assert {:type,{:mytype,{:integer,_,10},[]}} = spec
|
||||
assert {:mytype,{:integer,_,10},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a negative integer" do
|
||||
spec = test_module do
|
||||
@type mytype :: -10
|
||||
end
|
||||
assert {:type,{:mytype,{:op, _, :-, {:integer,_,10}},[]}} = spec
|
||||
assert {:mytype,{:op, _, :-, {:integer,_,10}},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a remote type" do
|
||||
@@ -65,50 +65,50 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype_arg :: Remote.type(integer)
|
||||
{t1, t2}
|
||||
end
|
||||
assert {:type,{:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]}} = spec1
|
||||
assert {:type,{:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]}} = spec2
|
||||
assert {:mytype,{:remote_type, _, [{:atom, _, Remote.Some},{:atom, _, :type}, []]},[]} = spec1
|
||||
assert {:mytype_arg,{:remote_type, _, [{:atom, _, Remote},{:atom, _, :type},[{:type,_, :integer,[]}]]},[]} = spec2
|
||||
end
|
||||
|
||||
test "@type with a binary" do
|
||||
spec = test_module do
|
||||
@type mytype :: binary
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, []},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, []},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with an empty binary" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 0}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a binary with a base size" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<_ :: 3>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 3},{:integer, _, 0}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a binary with a unit size" do
|
||||
spec = test_module do
|
||||
@type mytype :: <<_ :: _ * 8>>
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:binary, [{:integer, _, 0},{:integer, _, 8}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a range" do
|
||||
spec = test_module do
|
||||
@type mytype :: range(1,10)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a range op" do
|
||||
spec = test_module do
|
||||
@type mytype :: 1..10
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:range, [{:integer, _, 1},{:integer, _, 10}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a tuple" do
|
||||
@@ -117,40 +117,40 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype1 :: {}
|
||||
{t1, t2}
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:tuple, :any},[]}} = spec1
|
||||
assert {:type,{:mytype1,{:type,_,:tuple, :any},[]}} = spec2
|
||||
assert {:mytype,{:type,_,:tuple, :any},[]} = spec1
|
||||
assert {:mytype1,{:type,_,:tuple, :any},[]} = spec2
|
||||
end
|
||||
|
||||
test "@type with a fun" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, []},[]}} = spec
|
||||
assert {:mytype,{:type,_,:fun, []},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with arguments and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(integer, integer, do: integer)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]},[]} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with no arguments and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(do: integer)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with a fun with any arity and return type" do
|
||||
spec = test_module do
|
||||
@type mytype :: fun(..., do: integer)
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with a union" do
|
||||
@@ -158,17 +158,17 @@ defmodule Typespec.Test.Type do
|
||||
@type mytype :: integer | char_list
|
||||
| atom
|
||||
end
|
||||
assert {:type,{:mytype,{:type,_,:union, [{:type, _, :integer, []},
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]},
|
||||
{:type, _, :atom, []}]}, []}} = spec
|
||||
assert {:mytype,{:type,_,:union, [{:type, _, :integer, []},
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]},
|
||||
{:type, _, :atom, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with an access macro" do
|
||||
spec = test_module do
|
||||
@type mytype :: Range[first: integer]
|
||||
end
|
||||
assert {:type,{:mytype,{:type, _, :tuple,
|
||||
[{:atom, _, Range}, {:type, _, :integer, []}, {:type, _, :any, []}]}, []}} = spec
|
||||
assert {:mytype,{:type, _, :tuple,
|
||||
[{:atom, _, Range}, {:type, _, :integer, []}, {:type, _, :any, []}]}, []} = spec
|
||||
end
|
||||
|
||||
test "@type with parameters" do
|
||||
@@ -178,9 +178,9 @@ defmodule Typespec.Test.Type do
|
||||
t3 = @type mytype2(x,y) :: {x,y}
|
||||
{t1,t2,t3}
|
||||
end
|
||||
assert {:type,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec1
|
||||
assert {:type,{:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]}} = spec2
|
||||
assert {:type,{:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]}} = spec3
|
||||
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec1
|
||||
assert {:mytype1,{:type,_,:list,[{:var,_,:x}]},[{:var,_,:x}]} = spec2
|
||||
assert {:mytype2,{:type,_,:tuple,[{:var,_,:x},{:var,_,:y}]},[{:var,_,:x},{:var, _, :y}]} = spec3
|
||||
end
|
||||
|
||||
test "@type with annotations" do
|
||||
@@ -189,15 +189,15 @@ defmodule Typespec.Test.Type do
|
||||
t2 = @type mytype1 :: fun(a :: integer, do: integer)
|
||||
{t1,t2}
|
||||
end
|
||||
assert {:type, {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []}} = spec1
|
||||
assert {:type, {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]}} = spec2
|
||||
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
|
||||
assert {:mytype1,{:type,_,:fun,[{:type,_,:product,[{:ann_type,_,[{:var,_,:a},{:type,_,:integer,[]}]}]},{:type,_,:integer,[]}]},[]} = spec2
|
||||
end
|
||||
|
||||
test "@opaque(type)" do
|
||||
spec = test_module do
|
||||
@opaque mytype(x) :: x
|
||||
end
|
||||
assert {:opaque,{:mytype,{:var,_,:x},[{:var,_,:x}]}} = spec
|
||||
assert {:mytype,{:var,_,:x},[{:var,_,:x}]} = spec
|
||||
end
|
||||
|
||||
test "@type + opaque" do
|
||||
@@ -267,27 +267,10 @@ defmodule Typespec.Test.Type do
|
||||
] = List.sort(specs)
|
||||
end
|
||||
|
||||
test "defines a callback from a spec" do
|
||||
callbacks = test_module do
|
||||
def myfun(x), do: x
|
||||
@spec myfun(integer), do: integer
|
||||
@spec myfun(char_list), do: char_list
|
||||
Kernel.Typespec.callback_from_spec(__MODULE__, :myfun, 1)
|
||||
@callback
|
||||
end
|
||||
# Conversion to AST
|
||||
|
||||
assert [
|
||||
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[{:type,_,:integer,[]}]},{:type,_,:integer,[]}]} },
|
||||
{ {:myfun,1}, {:type,_,:fun,[{:type,_,:product,[
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]},
|
||||
{:remote_type, _, [{:atom, _, :elixir},{:atom, _, :char_list}, []]}]} }
|
||||
] = List.sort(callbacks)
|
||||
end
|
||||
|
||||
# to_binary
|
||||
|
||||
test "to_binary types" do
|
||||
types = [
|
||||
test "type_to_ast" do
|
||||
quoted = [
|
||||
(quote do: @type with_ann() :: (t :: atom())),
|
||||
(quote do: @type empty_tuple_type() :: {}),
|
||||
(quote do: @type imm_type_1() :: 1),
|
||||
@@ -302,19 +285,20 @@ defmodule Typespec.Test.Type do
|
||||
(quote do: @type ftype() :: fun() | fun(do: integer()) | fun(integer(), do: integer())),
|
||||
]
|
||||
|
||||
spec = test_module do
|
||||
Module.eval_quoted __MODULE__, quote do: unquote_splicing(types)
|
||||
types = test_module do
|
||||
Module.eval_quoted __MODULE__, quote do: unquote_splicing(quoted)
|
||||
@type ++ @opaque
|
||||
end
|
||||
|
||||
spec = Enum.reverse(spec)
|
||||
types = Enum.reverse(types)
|
||||
|
||||
lc { typespec, definition } inlist Enum.zip(spec, types) do
|
||||
assert Kernel.Typespec.to_binary({ :type, typespec }) == Macro.to_binary(definition)
|
||||
lc { type, definition } inlist Enum.zip(types, quoted) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
assert Macro.to_binary(quote do: @type unquote(ast)) == Macro.to_binary(definition)
|
||||
end
|
||||
end
|
||||
|
||||
test "to_binary spec" do
|
||||
test "spec_to_ast" do
|
||||
specs = [
|
||||
(quote do: @spec a(), do: integer()),
|
||||
(quote do: @spec a(atom()), do: integer()),
|
||||
@@ -327,28 +311,34 @@ defmodule Typespec.Test.Type do
|
||||
Enum.reverse @spec
|
||||
end
|
||||
|
||||
lc { { tuple, spec }, definition } inlist Enum.zip(compiled, specs) do
|
||||
assert Kernel.Typespec.to_binary({ { :spec, tuple }, [spec] }) == Macro.to_binary(definition)
|
||||
lc { { { name, _ }, spec }, definition } inlist Enum.zip(compiled, specs) do
|
||||
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
|
||||
quoted = quote do: @spec unquote(name)(unquote_splicing(args)), do: unquote(result)
|
||||
assert Macro.to_binary(quoted) == Macro.to_binary(definition)
|
||||
end
|
||||
end
|
||||
|
||||
# test/spec retrieval
|
||||
# types/specs retrieval
|
||||
|
||||
test "specs retrieval" do
|
||||
Code.compiler_options debug_info: true
|
||||
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@spec a, do: any
|
||||
def a, do: nil
|
||||
def a, do: nil
|
||||
end
|
||||
Code.compiler_options debug_info: false
|
||||
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
assert [{{:spec,{:a,_}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:any,[]}]}]}] = Kernel.Typespec.specs(binary)
|
||||
assert [{{:spec,{:a,_}},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:any,[]}]}]}] = Kernel.Typespec.specs(binary, :a, 0)
|
||||
|
||||
assert [{{:a,_},[{:type,_,:fun,[{:type,_,:product,[]},{:type,_,:any,[]}]}]}] = Kernel.Typespec.beam_specs(binary)
|
||||
after
|
||||
Code.compiler_options debug_info: false
|
||||
end
|
||||
|
||||
test "types retrieval" do
|
||||
Code.compiler_options debug_info: true
|
||||
|
||||
{ :module, T, binary, _ } = defmodule T do
|
||||
@type a :: any
|
||||
@typep b :: any
|
||||
@@ -357,23 +347,15 @@ defmodule Typespec.Test.Type do
|
||||
@opaque c :: any
|
||||
end
|
||||
|
||||
assert [
|
||||
{:opaque, {:c,{:type,_,:any,[]},[]}},
|
||||
{:type, {:a,{:type,_,:any,[]},[]}},
|
||||
{:typep, {:b,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.types(binary)
|
||||
assert [
|
||||
{:type, {:a,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.types(binary, :a, 0)
|
||||
assert [
|
||||
{:typep, {:b,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.types(binary, :b, 0)
|
||||
assert [
|
||||
{:opaque, {:c,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.types(binary, :c, 0)
|
||||
after
|
||||
Code.compiler_options debug_info: false
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
|
||||
assert [
|
||||
{:opaque, {:c,{:type,_,:any,[]},[]}},
|
||||
{:type, {:a,{:type,_,:any,[]},[]}},
|
||||
{:typep, {:b,{:type,_,:any,[]},[]}},
|
||||
] = Kernel.Typespec.beam_types(binary)
|
||||
after
|
||||
Code.compiler_options debug_info: false
|
||||
end
|
||||
end
|
||||
+73
-50
@@ -13,7 +13,8 @@ defmodule IEx.Helpers do
|
||||
|
||||
* `c/2` - compiles a file in the given path
|
||||
* `h/0`,`h/1`, `h/2` - prints help/documentation
|
||||
* `t/1`,`t/3` — prints type specification information
|
||||
* `t/1` — prints types information
|
||||
* `s/1`, `s/3` — prints specs information
|
||||
* `m/0` - prints loaded modules
|
||||
* `r/0` - recompiles and reloads the given module's source file
|
||||
* `v/0` - prints all commands and values
|
||||
@@ -214,69 +215,91 @@ defmodule IEx.Helpers do
|
||||
atom_to_binary(var)
|
||||
end
|
||||
|
||||
# FIXME: this can't be shown as h(t/1)
|
||||
@doc """
|
||||
Prints all types and function specifications from a given module
|
||||
Prints all types for the given module
|
||||
|
||||
## Examples
|
||||
|
||||
t(Enum)
|
||||
|
||||
"""
|
||||
def __t_module__(module) do
|
||||
types =
|
||||
lc type inlist Kernel.Typespec.types(module) do
|
||||
IO.puts Kernel.Typespec.to_binary(type)
|
||||
type
|
||||
end
|
||||
specs =
|
||||
lc spec inlist Kernel.Typespec.specs(module) do
|
||||
IO.puts Kernel.Typespec.to_binary(spec)
|
||||
spec
|
||||
end
|
||||
if specs == [] and types == [] do
|
||||
IO.puts "No type specifications for #{inspect module} has been found"
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the type specification for a given function or a type
|
||||
|
||||
## Examples
|
||||
|
||||
t(Enum.all?/2)
|
||||
t(Enum.t/0)
|
||||
t(Enum)
|
||||
|
||||
"""
|
||||
def t(module, function, arity) do
|
||||
types =
|
||||
lc type inlist Kernel.Typespec.types(module, function, arity) do
|
||||
IO.puts Kernel.Typespec.to_binary(type)
|
||||
type
|
||||
def t(module) do
|
||||
types = lc type inlist Kernel.Typespec.beam_types(module), do: print_type(type)
|
||||
|
||||
if types == [] do
|
||||
IO.puts "No types for #{inspect module} has been found"
|
||||
end
|
||||
specs =
|
||||
lc spec inlist Kernel.Typespec.specs(module, function, arity) do
|
||||
IO.puts Kernel.Typespec.to_binary(spec)
|
||||
spec
|
||||
end
|
||||
if specs == [] and types == [] do
|
||||
IO.puts "No type specification for #{inspect module}.#{function}/#{arity} has been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defmacro t({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
@doc """
|
||||
Prints all specs from a given module.
|
||||
|
||||
## Examples
|
||||
|
||||
s(Enum)
|
||||
|
||||
"""
|
||||
defmacro s({ :/, _, [{ { :., _, [mod, fun] }, _, [] }, arity] }) do
|
||||
quote do
|
||||
t(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
defmacro t(module) do
|
||||
quote do
|
||||
__t_module__(unquote(module))
|
||||
s(unquote(mod), unquote(fun), unquote(arity))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro s(module) do
|
||||
quote do
|
||||
s(unquote(module), :all)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def s(module, :all) do
|
||||
specs = lc spec inlist Kernel.Typespec.beam_specs(module), do: print_spec(spec)
|
||||
|
||||
if specs == [] do
|
||||
IO.puts "No specs for #{inspect module} has been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Prints the specs for a given function.
|
||||
|
||||
## Examples
|
||||
|
||||
s(Enum.all?/2)
|
||||
s(Enum.t/0)
|
||||
|
||||
"""
|
||||
def s(module, function, arity) do
|
||||
spec = List.keyfind(Kernel.Typespec.beam_specs(module), { function, arity }, 0)
|
||||
|
||||
if spec do
|
||||
print_spec(spec)
|
||||
else
|
||||
IO.puts "No spec for #{inspect module}.#{function}/#{arity} has been found"
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp print_type({ kind, type }) do
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
IO.puts "@#{kind} #{Macro.to_binary(ast)}"
|
||||
true
|
||||
end
|
||||
|
||||
defp print_spec({ { name, _arity }, specs }) do
|
||||
Enum.each specs, fn(spec) ->
|
||||
{ args, result } = Kernel.Typespec.spec_to_ast(spec)
|
||||
bin_args = Macro.to_binary { name, 0, args }
|
||||
bin_result = Macro.to_binary result
|
||||
IO.puts "@spec #{bin_args}, do: #{bin_result}"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves nth query's value from the history. Use negative
|
||||
|
||||
Reference in New Issue
Block a user