676 lines
20 KiB
Elixir
676 lines
20 KiB
Elixir
defmodule Protocol do
|
|
@moduledoc """
|
|
Functions for working with protocols.
|
|
"""
|
|
|
|
@doc """
|
|
Defines a new protocol function.
|
|
|
|
Protocols do not allow functions to be defined directly, instead, the
|
|
regular `Kernel.def/*` macros are replaced by this macro which
|
|
defines the protocol functions with the appropriate callbacks.
|
|
"""
|
|
defmacro def(signature)
|
|
|
|
defmacro def({_, _, args}) when args == [] or is_atom(args) do
|
|
raise ArgumentError, "protocol functions expect at least one argument"
|
|
end
|
|
|
|
defmacro def({name, _, args}) when is_atom(name) and is_list(args) do
|
|
arity = length(args)
|
|
|
|
type_args = :lists.map(fn _ -> quote(do: term) end,
|
|
:lists.seq(2, arity))
|
|
type_args = [quote(do: t) | type_args]
|
|
|
|
call_args = :lists.map(fn pos -> Macro.var(String.to_atom("var" <> Integer.to_string(pos)), __MODULE__) end,
|
|
:lists.seq(2, arity))
|
|
call_args = [quote(do: term) | call_args]
|
|
|
|
quote do
|
|
name = unquote(name)
|
|
arity = unquote(arity)
|
|
|
|
@functions [{name, arity} | @functions]
|
|
|
|
# Generate a fake definition with the user
|
|
# signature that will be used by docs
|
|
Kernel.def unquote(name)(unquote_splicing(args))
|
|
|
|
# Generate the actual implementation
|
|
Kernel.def unquote(name)(unquote_splicing(call_args)) do
|
|
impl_for!(term).unquote(name)(unquote_splicing(call_args))
|
|
end
|
|
|
|
# Convert the spec to callback if possible,
|
|
# otherwise generate a dummy callback
|
|
Protocol.__spec__?(__MODULE__, name, arity) ||
|
|
@callback unquote(name)(unquote_splicing(type_args)) :: term
|
|
end
|
|
end
|
|
|
|
defmacro def(_) do
|
|
raise ArgumentError, "invalid arguments for def inside defprotocol"
|
|
end
|
|
|
|
@doc """
|
|
Checks if the given module is loaded and is protocol.
|
|
|
|
Returns `:ok` if so, otherwise raises `ArgumentError`.
|
|
"""
|
|
@spec assert_protocol!(module) :: :ok | no_return
|
|
def assert_protocol!(module) do
|
|
assert_protocol!(module, "")
|
|
end
|
|
|
|
defp assert_protocol!(module, extra) do
|
|
case Code.ensure_compiled(module) do
|
|
{:module, ^module} -> :ok
|
|
_ -> raise ArgumentError, "#{inspect module} is not available" <> extra
|
|
end
|
|
|
|
try do
|
|
module.__protocol__(:module)
|
|
rescue
|
|
UndefinedFunctionError ->
|
|
raise ArgumentError, "#{inspect module} is not a protocol" <> extra
|
|
end
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Checks if the given module is loaded and is an implementation
|
|
of the given protocol.
|
|
|
|
Returns `:ok` if so, otherwise raises `ArgumentError`.
|
|
"""
|
|
@spec assert_impl!(module, module) :: :ok | no_return
|
|
def assert_impl!(protocol, base) do
|
|
assert_impl!(protocol, base, "")
|
|
end
|
|
|
|
defp assert_impl!(protocol, base, extra) do
|
|
impl = Module.concat(protocol, base)
|
|
|
|
case Code.ensure_compiled(impl) do
|
|
{:module, ^impl} -> :ok
|
|
_ -> raise ArgumentError,
|
|
"#{inspect impl} is not available" <> extra
|
|
end
|
|
|
|
try do
|
|
impl.__impl__(:protocol)
|
|
rescue
|
|
UndefinedFunctionError ->
|
|
raise ArgumentError,
|
|
"#{inspect impl} is not an implementation of a protocol" <> extra
|
|
else
|
|
^protocol ->
|
|
:ok
|
|
other ->
|
|
raise ArgumentError,
|
|
"expected #{inspect impl} to be an implementation of #{inspect protocol}, got: #{inspect other}" <> extra
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Derives the `protocol` for `module` with the given options.
|
|
"""
|
|
defmacro derive(protocol, module, options \\ []) do
|
|
quote do
|
|
module = unquote(module)
|
|
Protocol.__derive__([{unquote(protocol), unquote(options)}], module, __ENV__)
|
|
end
|
|
end
|
|
|
|
## Consolidation
|
|
|
|
@doc """
|
|
Extracts all protocols from the given paths.
|
|
|
|
The paths can be either a charlist or a string. Internally
|
|
they are worked on as charlists, so passing them as lists
|
|
avoid extra conversion.
|
|
|
|
Does not load any of the protocols.
|
|
|
|
## Examples
|
|
|
|
# Get Elixir's ebin and retrieve all protocols
|
|
iex> path = :code.lib_dir(:elixir, :ebin)
|
|
iex> mods = Protocol.extract_protocols([path])
|
|
iex> Enumerable in mods
|
|
true
|
|
|
|
"""
|
|
@spec extract_protocols([charlist | String.t]) :: [atom]
|
|
def extract_protocols(paths) do
|
|
extract_matching_by_attribute paths, 'Elixir.',
|
|
fn module, attributes ->
|
|
case attributes[:protocol] do
|
|
[fallback_to_any: _] -> module
|
|
_ -> nil
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Extracts all types implemented for the given protocol from
|
|
the given paths.
|
|
|
|
The paths can be either a charlist or a string. Internally
|
|
they are worked on as charlists, so passing them as lists
|
|
avoid extra conversion.
|
|
|
|
Does not load any of the implementations.
|
|
|
|
## Examples
|
|
|
|
# Get Elixir's ebin and retrieve all protocols
|
|
iex> path = :code.lib_dir(:elixir, :ebin)
|
|
iex> mods = Protocol.extract_impls(Enumerable, [path])
|
|
iex> List in mods
|
|
true
|
|
|
|
"""
|
|
@spec extract_impls(module, [charlist | String.t]) :: [atom]
|
|
def extract_impls(protocol, paths) when is_atom(protocol) do
|
|
prefix = Atom.to_charlist(protocol) ++ '.'
|
|
extract_matching_by_attribute paths, prefix, fn
|
|
_mod, attributes ->
|
|
case attributes[:protocol_metadata] do
|
|
[protocol: ^protocol, for: for] -> for
|
|
_ -> nil
|
|
end
|
|
end
|
|
end
|
|
|
|
defp extract_matching_by_attribute(paths, prefix, callback) do
|
|
for path <- paths,
|
|
file <- list_dir(path),
|
|
mod = extract_from_file(path, file, prefix, callback),
|
|
do: mod
|
|
end
|
|
|
|
defp list_dir(path) when is_list(path) do
|
|
case :file.list_dir(path) do
|
|
{:ok, files} -> files
|
|
_ -> []
|
|
end
|
|
end
|
|
|
|
defp list_dir(path), do: list_dir(to_charlist(path))
|
|
|
|
defp extract_from_file(path, file, prefix, callback) do
|
|
if :lists.prefix(prefix, file) and :filename.extension(file) == '.beam' do
|
|
extract_from_beam(:filename.join(path, file), callback)
|
|
end
|
|
end
|
|
|
|
defp extract_from_beam(file, callback) do
|
|
case :beam_lib.chunks(file, [:attributes]) do
|
|
{:ok, {module, [attributes: attributes]}} ->
|
|
callback.(module, attributes)
|
|
_ ->
|
|
nil
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Returns `true` if the protocol was consolidated.
|
|
"""
|
|
@spec consolidated?(module) :: boolean
|
|
def consolidated?(protocol) do
|
|
protocol.__protocol__(:consolidated?)
|
|
end
|
|
|
|
@doc """
|
|
Receives a protocol and a list of implementations and
|
|
consolidates the given protocol.
|
|
|
|
Consolidation happens by changing the protocol `impl_for`
|
|
in the abstract format to have fast lookup rules. Usually
|
|
the list of implementations to use during consolidation
|
|
are retrieved with the help of `extract_impls/2`.
|
|
|
|
It returns the updated version of the protocol bytecode.
|
|
A given bytecode or protocol implementation can be checked
|
|
to be consolidated or not by analyzing the protocol
|
|
attribute:
|
|
|
|
Protocol.consolidated?(Enumerable)
|
|
|
|
If the first element of the tuple is `true`, it means
|
|
the protocol was consolidated.
|
|
|
|
This function does not load the protocol at any point
|
|
nor loads the new bytecode for the compiled module.
|
|
However each implementation must be available and
|
|
it will be loaded.
|
|
"""
|
|
@spec consolidate(module, [module]) ::
|
|
{:ok, binary} |
|
|
{:error, :not_a_protocol} |
|
|
{:error, :no_beam_info}
|
|
def consolidate(protocol, types) when is_atom(protocol) do
|
|
with {:ok, info} <- beam_protocol(protocol),
|
|
{:ok, code, docs} <- change_debug_info(info, types),
|
|
do: compile(code, docs)
|
|
end
|
|
|
|
@docs_chunk 'ExDc'
|
|
|
|
defp beam_protocol(protocol) do
|
|
chunk_ids = [:abstract_code, :attributes, @docs_chunk]
|
|
opts = [:allow_missing_chunks]
|
|
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
|
|
{:ok, {^protocol, [{:abstract_code, {_raw, abstract_code}},
|
|
{:attributes, attributes},
|
|
{@docs_chunk, docs}]}} ->
|
|
case attributes[:protocol] do
|
|
[fallback_to_any: any] ->
|
|
{:ok, {protocol, any, abstract_code, docs}}
|
|
_ ->
|
|
{:error, :not_a_protocol}
|
|
end
|
|
_ ->
|
|
{:error, :no_beam_info}
|
|
end
|
|
end
|
|
|
|
defp beam_file(module) when is_atom(module) do
|
|
case :code.which(module) do
|
|
atom when is_atom(atom) -> module
|
|
file -> file
|
|
end
|
|
end
|
|
|
|
# Change the debug information to the optimized
|
|
# impl_for/1 dispatch version.
|
|
defp change_debug_info({protocol, any, code, docs}, types) do
|
|
types = if any, do: types, else: List.delete(types, Any)
|
|
all = [Any] ++ for {_guard, mod} <- __builtin__(), do: mod
|
|
structs = types -- all
|
|
case change_impl_for(code, protocol, types, structs, false, []) do
|
|
{:ok, ret} -> {:ok, ret, docs}
|
|
other -> other
|
|
end
|
|
end
|
|
|
|
defp change_impl_for([{:function, line, :__protocol__, 1, clauses} | tail], protocol, types, structs, _, acc) do
|
|
clauses = :lists.map(fn
|
|
{:clause, l, [{:atom, _, :consolidated?}], [], [{:atom, _, _}]} ->
|
|
{:clause, l, [{:atom, 0, :consolidated?}], [], [{:atom, 0, true}]}
|
|
{:clause, _, _, _, _} = c ->
|
|
c
|
|
end, clauses)
|
|
|
|
change_impl_for(tail, protocol, types, structs, true,
|
|
[{:function, line, :__protocol__, 1, clauses} | acc])
|
|
end
|
|
|
|
defp change_impl_for([{:function, line, :impl_for, 1, _} | tail], protocol, types, structs, protocol?, acc) do
|
|
fallback = if Any in types, do: load_impl(protocol, Any)
|
|
|
|
clauses = for {guard, mod} <- __builtin__(),
|
|
mod in types,
|
|
do: builtin_clause_for(mod, guard, protocol, line)
|
|
|
|
clauses = [struct_clause_for(line) | clauses] ++
|
|
[fallback_clause_for(fallback, protocol, line)]
|
|
|
|
change_impl_for(tail, protocol, types, structs, protocol?,
|
|
[{:function, line, :impl_for, 1, clauses} | acc])
|
|
end
|
|
|
|
defp change_impl_for([{:function, line, :struct_impl_for, 1, _} | tail], protocol, types, structs, protocol?, acc) do
|
|
fallback = if Any in types, do: load_impl(protocol, Any)
|
|
clauses = for struct <- structs, do: each_struct_clause_for(struct, protocol, line)
|
|
clauses = clauses ++ [fallback_clause_for(fallback, protocol, line)]
|
|
|
|
change_impl_for(tail, protocol, types, structs, protocol?,
|
|
[{:function, line, :struct_impl_for, 1, clauses} | acc])
|
|
end
|
|
|
|
defp change_impl_for([{:attribute, line, :spec, {{:__protocol__, 1}, funspecs}} | tail], protocol, types, structs, protocol?, acc) do
|
|
new_specs = for spec <- funspecs do
|
|
case spec do
|
|
{:type, line, :fun, [{:type, _, :product, [{:atom, _, :consolidated?}]}, _]} ->
|
|
{:type, line, :fun,
|
|
[{:type, line, :product, [{:atom, 0, :consolidated?}]},
|
|
{:atom, 0, true}]}
|
|
other -> other
|
|
end
|
|
end
|
|
change_impl_for(tail, protocol, types, structs, protocol?, [{:attribute, line, :spec, {{:__protocol__, 1}, new_specs}} | acc])
|
|
end
|
|
|
|
defp change_impl_for([head | tail], protocol, info, types, protocol?, acc) do
|
|
change_impl_for(tail, protocol, info, types, protocol?, [head | acc])
|
|
end
|
|
|
|
defp change_impl_for([], protocol, _info, _types, protocol?, acc) do
|
|
if protocol? do
|
|
{:ok, {protocol, Enum.reverse(acc)}}
|
|
else
|
|
{:error, :not_a_protocol}
|
|
end
|
|
end
|
|
|
|
defp builtin_clause_for(mod, guard, protocol, line) do
|
|
{:clause, line,
|
|
[{:var, line, :x}],
|
|
[[{:call, line,
|
|
{:remote, line, {:atom, line, :erlang}, {:atom, line, guard}},
|
|
[{:var, line, :x}],
|
|
}]],
|
|
[{:atom, line, load_impl(protocol, mod)}]}
|
|
end
|
|
|
|
defp struct_clause_for(line) do
|
|
{:clause, line,
|
|
[{:map, line, [
|
|
{:map_field_exact, line, {:atom, line, :__struct__}, {:var, line, :x}}
|
|
]}],
|
|
[[{:call, line,
|
|
{:remote, line, {:atom, line, :erlang}, {:atom, line, :is_atom}},
|
|
[{:var, line, :x}],
|
|
}]],
|
|
[{:call, line,
|
|
{:atom, line, :struct_impl_for},
|
|
[{:var, line, :x}]}]}
|
|
end
|
|
|
|
defp each_struct_clause_for(struct, protocol, line) do
|
|
{:clause, line, [{:atom, line, struct}], [],
|
|
[{:atom, line, load_impl(protocol, struct)}]}
|
|
end
|
|
|
|
defp fallback_clause_for(value, _protocol, line) do
|
|
{:clause, line, [{:var, line, :_}], [],
|
|
[{:atom, line, value}]}
|
|
end
|
|
|
|
defp load_impl(protocol, for) do
|
|
Module.concat(protocol, for).__impl__(:target)
|
|
end
|
|
|
|
# Finally compile the module and emit its bytecode.
|
|
defp compile({protocol, code}, docs) do
|
|
opts = if Code.compiler_options[:debug_info], do: [:debug_info], else: []
|
|
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return | opts])
|
|
{:ok,
|
|
case docs do
|
|
:missing_chunk -> binary
|
|
_ -> :elixir_erl.add_beam_chunk(binary, @docs_chunk, docs)
|
|
end}
|
|
end
|
|
|
|
## Definition callbacks
|
|
|
|
@doc false
|
|
def __protocol__(name, [do: block]) do
|
|
quote do
|
|
defmodule unquote(name) do
|
|
# We don't allow function definition inside protocols
|
|
import Kernel, except: [
|
|
defmacrop: 1, defmacrop: 2, defmacro: 1, defmacro: 2,
|
|
defp: 1, defp: 2, def: 1, def: 2
|
|
]
|
|
|
|
# Import the new dsl that holds the new def
|
|
import Protocol, only: [def: 1]
|
|
|
|
# Compile with debug info for consolidation
|
|
@compile :debug_info
|
|
|
|
# Set up a clear slate to store defined functions
|
|
@functions []
|
|
@fallback_to_any false
|
|
|
|
# Invoke the user given block
|
|
_ = unquote(block)
|
|
|
|
# Finalize expansion
|
|
unquote(after_defprotocol())
|
|
end
|
|
end
|
|
end
|
|
|
|
defp after_defprotocol do
|
|
quote bind_quoted: [builtin: __builtin__()] do
|
|
@doc false
|
|
@spec impl_for(term) :: atom | nil
|
|
Kernel.def impl_for(data)
|
|
|
|
# Define the implementation for structs.
|
|
#
|
|
# It simply delegates to struct_impl_for which is then
|
|
# optimized during protocol consolidation.
|
|
Kernel.def impl_for(%{__struct__: struct}) when :erlang.is_atom(struct) do
|
|
struct_impl_for(struct)
|
|
end
|
|
|
|
# Define the implementation for built-ins
|
|
:lists.foreach(fn {guard, mod} ->
|
|
target = Module.concat(__MODULE__, mod)
|
|
|
|
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
|
case impl_for?(unquote(target)) do
|
|
true -> unquote(target).__impl__(:target)
|
|
false -> any_impl_for()
|
|
end
|
|
end
|
|
end, builtin)
|
|
|
|
# Define a catch-all impl_for/1 clause to pacify Dialyzer (since
|
|
# destructuring opaque types is illegal, Dialyzer will think none of the
|
|
# previous clauses matches opaque types, and without this clause, will
|
|
# conclude that impl_for can't handle an opaque argument). This is a hack
|
|
# since it relies on Dialyzer not being smart enough to conclude that all
|
|
# opaque types will get the any_impl_for/0 implementation.
|
|
Kernel.def impl_for(_) do
|
|
any_impl_for()
|
|
end
|
|
|
|
@doc false
|
|
@spec impl_for!(term) :: atom | no_return
|
|
Kernel.def impl_for!(data) do
|
|
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
|
end
|
|
|
|
# Internal handler for Any
|
|
if @fallback_to_any do
|
|
Kernel.defp any_impl_for(), do: __MODULE__.Any.__impl__(:target)
|
|
else
|
|
Kernel.defp any_impl_for(), do: nil
|
|
end
|
|
|
|
# Internal handler for Structs
|
|
Kernel.defp struct_impl_for(struct) do
|
|
target = Module.concat(__MODULE__, struct)
|
|
case impl_for?(target) do
|
|
true -> target.__impl__(:target)
|
|
false -> any_impl_for()
|
|
end
|
|
end
|
|
|
|
# Check if compilation is available internally
|
|
Kernel.defp impl_for?(target) do
|
|
Code.ensure_compiled?(target) and
|
|
function_exported?(target, :__impl__, 1)
|
|
end
|
|
|
|
# Inline any and struct implementations
|
|
@compile {:inline, any_impl_for: 0, struct_impl_for: 1, impl_for?: 1}
|
|
|
|
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
|
@type t :: term
|
|
end
|
|
|
|
# Store information as an attribute so it
|
|
# can be read without loading the module.
|
|
Module.register_attribute(__MODULE__, :protocol, persist: true)
|
|
@protocol [fallback_to_any: !!@fallback_to_any]
|
|
|
|
@doc false
|
|
@spec __protocol__(:module) :: __MODULE__
|
|
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
|
|
@spec __protocol__(:consolidated?) :: false
|
|
Kernel.def __protocol__(:module), do: __MODULE__
|
|
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
|
Kernel.def __protocol__(:consolidated?), do: false
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
def __functions_spec__([]),
|
|
do: []
|
|
def __functions_spec__([head | tail]),
|
|
do: [:lists.foldl(&{:|, [], [&1, &2]}, head, tail), quote(do: ...)]
|
|
|
|
@doc false
|
|
def __impl__(protocol, opts) do
|
|
do_defimpl(protocol, :lists.keysort(1, opts))
|
|
end
|
|
|
|
defp do_defimpl(protocol, [do: block, for: for]) when is_list(for) do
|
|
for f <- for, do: do_defimpl(protocol, [do: block, for: f])
|
|
end
|
|
|
|
defp do_defimpl(protocol, [do: block, for: for]) do
|
|
# Unquote the implementation just later
|
|
# when all variables will already be injected
|
|
# into the module body.
|
|
impl =
|
|
quote unquote: false do
|
|
@doc false
|
|
@spec __impl__(:for) :: unquote(for)
|
|
@spec __impl__(:target) :: __MODULE__
|
|
@spec __impl__(:protocol) :: unquote(protocol)
|
|
def __impl__(:for), do: unquote(for)
|
|
def __impl__(:target), do: __MODULE__
|
|
def __impl__(:protocol), do: unquote(protocol)
|
|
end
|
|
|
|
quote do
|
|
protocol = unquote(protocol)
|
|
for = unquote(for)
|
|
name = Module.concat(protocol, for)
|
|
|
|
Protocol.assert_protocol!(protocol)
|
|
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
|
|
|
|
defmodule name do
|
|
@behaviour protocol
|
|
@protocol protocol
|
|
@for for
|
|
|
|
unquote(block)
|
|
|
|
Module.register_attribute(__MODULE__, :protocol_metadata, persist: true)
|
|
@protocol_metadata [protocol: @protocol, for: @for]
|
|
|
|
unquote(impl)
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
def __derive__(derives, for, %Macro.Env{} = env) when is_atom(for) do
|
|
struct =
|
|
if for == env.module do
|
|
Module.get_attribute(for, :struct) ||
|
|
raise "struct is not defined for #{inspect for}"
|
|
else
|
|
for.__struct__
|
|
end
|
|
|
|
:lists.foreach(fn
|
|
proto when is_atom(proto) ->
|
|
derive(proto, for, struct, [], env)
|
|
{proto, opts} when is_atom(proto) ->
|
|
derive(proto, for, struct, opts, env)
|
|
end, :lists.flatten(derives))
|
|
|
|
:ok
|
|
end
|
|
|
|
defp derive(protocol, for, struct, opts, env) do
|
|
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
|
assert_protocol!(protocol, extra)
|
|
__ensure_defimpl__(protocol, for, env)
|
|
assert_impl!(protocol, Any, extra)
|
|
|
|
# Clean up variables from eval context
|
|
env = %{env | vars: [], export_vars: nil}
|
|
args = [for, struct, opts]
|
|
impl = Module.concat(protocol, Any)
|
|
|
|
:elixir_module.expand_callback(env.line, impl, :__deriving__, args, env, fn
|
|
mod, fun, args ->
|
|
if function_exported?(mod, fun, length(args)) do
|
|
apply(mod, fun, args)
|
|
else
|
|
Module.create(Module.concat(protocol, for), quote do
|
|
Module.register_attribute(__MODULE__, :protocol_metadata, persist: true)
|
|
@protocol_metadata [protocol: unquote(protocol), for: unquote(for)]
|
|
|
|
@doc false
|
|
@spec __impl__(:target) :: unquote(impl)
|
|
@spec __impl__(:protocol) :: unquote(protocol)
|
|
@spec __impl__(:for) :: unquote(for)
|
|
def __impl__(:target), do: unquote(impl)
|
|
def __impl__(:protocol), do: unquote(protocol)
|
|
def __impl__(:for), do: unquote(for)
|
|
end, Macro.Env.location(env))
|
|
end
|
|
end)
|
|
end
|
|
|
|
@doc false
|
|
def __ensure_defimpl__(protocol, for, env) do
|
|
if Protocol.consolidated?(protocol) do
|
|
message =
|
|
"the #{inspect protocol} protocol has already been consolidated" <>
|
|
", an implementation for #{inspect for} has no effect"
|
|
:elixir_errors.warn(env.line, env.file, message)
|
|
end
|
|
:ok
|
|
end
|
|
|
|
@doc false
|
|
def __spec__?(module, name, arity) do
|
|
signature = {name, arity}
|
|
|
|
specs = Module.get_attribute(module, :spec)
|
|
found =
|
|
:lists.map(fn {:spec, expr, caller} ->
|
|
if Kernel.Typespec.spec_to_signature(expr) == signature do
|
|
Kernel.Typespec.defspec(:callback, expr, caller)
|
|
true
|
|
end
|
|
end, specs)
|
|
|
|
:lists.any(& &1 == true, found)
|
|
end
|
|
|
|
## Helpers
|
|
|
|
@doc false
|
|
def __builtin__ do
|
|
[is_tuple: Tuple,
|
|
is_atom: Atom,
|
|
is_list: List,
|
|
is_map: Map,
|
|
is_bitstring: BitString,
|
|
is_integer: Integer,
|
|
is_float: Float,
|
|
is_function: Function,
|
|
is_pid: PID,
|
|
is_port: Port,
|
|
is_reference: Reference]
|
|
end
|
|
end
|