Tidy up Typespec API, improve docs

This commit is contained in:
José Valim
2012-11-05 14:51:51 +01:00
parent bd8648d490
commit e9334fb7dc
5 changed files with 406 additions and 342 deletions
+252 -199
View File
@@ -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
+15 -1
View File
@@ -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
-8
View File
@@ -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
+66 -84
View File
@@ -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
View File
@@ -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