Files
n8n-openai-adapter/lib/elixir/lib/module.ex
T

1160 lines
33 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
defmodule Module do
@moduledoc ~S'''
Provides functions to deal with modules during compilation time.
It allows a developer to dynamically add, delete and register
attributes, attach documentation and so forth.
After a module is compiled, using many of the functions in
this module will raise errors, since it is out of their scope
to inspect runtime data. Most of the runtime data can be inspected
via the `__info__(attr)` function attached to each compiled module.
## Module attributes
Each module can be decorated with one or more attributes. The following ones
are currently defined by Elixir:
* `@after_compile`
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{<module>, <function atom>}`.
See the "Compile callbacks" section below.
* `@before_compile`
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{<module>, <function/macro atom>}`.
See the "Compile callbacks" section below.
* `@behaviour` (notice the British spelling)
Behaviours can be referenced by modules to ensure they implement
required specific function signatures defined by `@callback`.
For example, you can specify the URI.Parser behaviour as follows:
defmodule URI.Parser do
@doc "Parses the given URL"
@callback parse(uri_info :: URI.t) :: URI.t
@doc "Defines a default port"
@callback default_port() :: integer
end
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
If the behaviour changes or URI.HTTP does not implement one of the
callbacks, a warning will be raised.
Specifies an OTP or user-defined behaviour.
### Example
defmodule M do
@behaviour :gen_event
# ...
end
* `@callback`, `@macrocallback`, and `@optional_callbacks`
These attributes are used to define a behaviour (as shown in the
documentation for `@behaviour` above). `@callback` defines a function
callback, `@macrocallback` defines a macro callback, and
`@optional_callbacks` specifies which callbacks and macrocallbacks are
optional.
* `@compile`
Defines options for module compilation. This is used to configure
both Elixir and Erlang compilers, as any other compilation pass
added by external tools.
Multiple uses of `@compile` will accumulate instead of overriding
previous ones. See the "Compile options" section below.
### Example
defmodule M do
@compile {:inline, myfun: 1}
def myfun(arg) do
to_string(arg)
end
end
* `@doc`
Provides documentation for the function or macro that follows the
attribute.
Accepts a string (often a heredoc) or `false` where `@doc false` will
make the function/macro invisible to the documentation extraction tools
like ExDoc.
Can be invoked more than once.
### Example
defmodule M do
@doc "Hello world"
def hello do
"world"
end
@doc """
Sums `a` to `b`.
"""
def sum(a, b) do
a + b
end
end
* `@dialyzer`
Defines warnings to request or suppress when using a version of
`:dialyzer` that supports module attributes.
Accepts an atom, a tuple, or a list of atoms and tuples.
For the list of supported warnings, see
[`:dialyzer` module](http://www.erlang.org/doc/man/dialyzer.html).
Multiple uses of `@dialyzer` will accumulate instead of overriding
previous ones.
### Example
defmodule M do
@dialyzer {:nowarn_function, myfun: 1}
def myfun(arg) do
M.not_a_function(arg)
end
end
* `@external_resource`
Specifies an external resource to the current module.
Many times a module embeds information from an external file. This
attribute allows the module to annotate which external resources
have been used.
Tools like Mix may use this information to ensure the module is
recompiled in case any of the external resources change.
* `@file`
Changes the filename used in stacktraces for the function or macro that
follows the attribute.
Accepts a string. Can be used more than once.
### Example
defmodule M do
@doc "Hello world"
@file "hello.ex"
def hello do
"world"
end
end
* `@moduledoc`
Provides documentation for the current module.
Accepts a string (which is often a heredoc) or `false` where
`@moduledoc false` will make the module invisible to the
documentation extraction tools like ExDoc.
### Example
defmodule M do
@moduledoc """
A very useful module
"""
end
* `@on_definition`
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{<module>, <function atom>}`. See the
"Compile callbacks" section below.
* `@on_load`
A hook that will be invoked whenever the module is loaded.
Accepts a function atom of a function in the current module. The function
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
loading of the module will be aborted.
### Example
defmodule M do
@on_load :load_check
def load_check do
if some_condition() do
:ok
else
nil
end
end
def some_condition do
false
end
end
* `@vsn`
Specify the module version. Accepts any valid Elixir value.
### Example
defmodule M do
@vsn "1.0"
end
The following attributes are part of typespecs and are also reserved by
Elixir:
* `@type` - defines a type to be used in `@spec`
* `@typep` - defines a private type to be used in `@spec`
* `@opaque` - defines an opaque type to be used in `@spec`
* `@spec` - provides a specification for a function
* `@callback` - provides a specification for a behaviour callback
* `@macrocallback` - provides a specification for a macro behaviour callback
* `@optional_callbacks` - specifies which behaviour callbacks and macro
behaviour callbacks are optional
In addition to the built-in attributes outlined above, custom attributes may
also be added. A custom attribute is any valid identifier prefixed with an
`@` and followed by a valid Elixir value:
defmodule M do
@custom_attr [some: "stuff"]
end
For more advanced options available when defining custom attributes, see
`register_attribute/3`.
## Compile callbacks
There are three callbacks that are invoked when functions are defined,
as well as before and immediately after the module bytecode is generated.
### `@after_compile`
A hook that will be invoked right after the current module is compiled.
Accepts a module or a tuple `{<module>, <function atom>}`. The function
must take two arguments: the module environment and its bytecode.
When just a module is provided, the function is assumed to be
`__after_compile__/2`.
#### Example
defmodule M do
@after_compile __MODULE__
def __after_compile__(env, _bytecode) do
IO.inspect env
end
end
### `@before_compile`
A hook that will be invoked before the module is compiled.
Accepts a module or a tuple `{<module>, <function/macro atom>}`. The
function/macro must take one argument: the module environment. If it's a
macro, its returned value will be injected at the end of the module definition
before the compilation starts.
When just a module is provided, the function/macro is assumed to be
`__before_compile__/1`.
Note: unlike `@after_compile`, the callback function/macro must
be placed in a separate module (because when the callback is invoked,
the current module does not yet exist).
#### Example
defmodule A do
defmacro __before_compile__(_env) do
quote do
def hello, do: "world"
end
end
end
defmodule B do
@before_compile A
end
### `@on_definition`
A hook that will be invoked when each function or macro in the current
module is defined. Useful when annotating functions.
Accepts a module or a tuple `{<module>, <function atom>}`. The function
must take 6 arguments:
- the module environment
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
- function/macro name
- list of quoted arguments
- list of quoted guards
- quoted function body
Note the hook receives the quoted arguments and it is invoked before
the function is stored in the module. So `Module.defines?/2` will return
`false` for the first clause of every function.
If the function/macro being defined has multiple clauses, the hook will
be called for each clause.
Unlike other hooks, `@on_definition` will only invoke functions
and never macros. This is because the hook is invoked inside the context
of the function (and nested function definitions are not allowed in
Elixir).
When just a module is provided, the function is assumed to be
`__on_definition__/6`.
#### Example
defmodule H do
def on_def(_env, kind, name, args, guards, body) do
IO.puts "Defining #{kind} named #{name} with args:"
IO.inspect args
IO.puts "and guards"
IO.inspect guards
IO.puts "and body"
IO.puts Macro.to_string(body)
end
end
defmodule M do
@on_definition {H, :on_def}
def hello(arg) when is_binary(arg) or is_list(arg) do
"Hello" <> to_string(arg)
end
def hello(_) do
:ok
end
end
## Compile options
The `@compile` attribute accepts diverse options that is used by both
Elixir and Erlang compilers. Some of the common use cases are documented
below:
* `@compile :debug_info` - includes `:debug_info` regardless of the
setting in `Code.compiler_options`
* `@compile {:debug_info, false} - disables `:debug_info` regardless
of the setting in `Code.compiler_options`
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
name/arity pairs
* `@compile {:autoload, false}` - disables automatic loading of
modules after compilation. Instead, the module will be loaded after
it is dispatched to
You can see a handful more options used by the Erlang compiler in
the documentation for the `:compile` module.
'''
@doc """
Provides runtime information about functions and macros defined by the
module, enables docstring extraction, etc.
Each module gets an `__info__/1` function when it's compiled. The function
takes one of the following atoms:
* `:functions` - keyword list of public functions along with their arities
* `:macros` - keyword list of public macros along with their arities
* `:module` - module name (`Module == Module.__info__(:module)`)
In addition to the above, you may also pass to `__info__/1` any atom supported
by `:erlang.module_info/0` which also gets defined for each compiled module.
For a list of supported attributes and more information, see [Modules – Erlang Reference Manual](http://www.erlang.org/doc/reference_manual/modules.html#id77056).
"""
def __info__(kind)
@doc """
Checks if a module is open, i.e. it is currently being defined
and its attributes and functions can be modified.
"""
def open?(module) do
:elixir_module.is_open(module)
end
@doc """
Evaluates the quoted contents in the given module's context.
A list of environment options can also be given as argument.
See `Code.eval_string/3` for more information.
Raises an error if the module was already compiled.
## Examples
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents
end
Foo.sum(1, 2) #=> 3
For convenience, you can pass `__ENV__` as an argument and
all options will be automatically extracted from the environment:
defmodule Foo do
contents = quote do: (def sum(a, b), do: a + b)
Module.eval_quoted __MODULE__, contents, [], __ENV__
end
Foo.sum(1, 2) #=> 3
"""
def eval_quoted(module, quoted, binding \\ [], opts \\ [])
def eval_quoted(%Macro.Env{} = env, quoted, binding, opts) do
eval_quoted(env.module, quoted, binding, Keyword.merge(Map.to_list(env), opts))
end
def eval_quoted(module, quoted, binding, %Macro.Env{} = env) do
eval_quoted(module, quoted, binding, Map.to_list(env))
end
def eval_quoted(module, quoted, binding, opts) do
assert_not_compiled!(:eval_quoted, module)
:elixir_def.reset_last(module)
{value, binding, _env, _scope} =
:elixir.eval_quoted quoted, binding, Keyword.put(opts, :module, module)
{value, binding}
end
@doc """
Creates a module with the given name and defined by
the given quoted expressions.
The line where the module is defined and its file **must**
be passed as options.
## Examples
contents =
quote do
def world, do: true
end
Module.create(Hello, contents, Macro.Env.location(__ENV__))
Hello.world #=> true
## Differences from `defmodule`
`Module.create/3` works similarly to `defmodule` and
return the same results. While one could also use
`defmodule` to define modules dynamically, this
function is preferred when the module body is given
by a quoted expression.
Another important distinction is that `Module.create/3`
allows you to control the environment variables used
when defining the module, while `defmodule` automatically
shares the same environment.
"""
def create(module, quoted, opts)
def create(module, quoted, %Macro.Env{} = env) do
create(module, quoted, Map.to_list(env))
end
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
unless Keyword.has_key?(opts, :file) do
raise ArgumentError, "expected :file to be given as option"
end
next = :elixir_counter.next
line = Keyword.get(opts, :line, 0)
quoted = :elixir_quote.linify_with_context_counter(line, {module, next}, quoted)
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
end
@doc """
Concatenates a list of aliases and returns a new alias.
## Examples
iex> Module.concat([Foo, Bar])
Foo.Bar
iex> Module.concat([Foo, "Bar"])
Foo.Bar
"""
@spec concat([binary | atom]) :: atom
def concat(list) when is_list(list) do
:elixir_aliases.concat(list)
end
@doc """
Concatenates two aliases and returns a new alias.
## Examples
iex> Module.concat(Foo, Bar)
Foo.Bar
iex> Module.concat(Foo, "Bar")
Foo.Bar
"""
@spec concat(binary | atom, binary | atom) :: atom
def concat(left, right) do
:elixir_aliases.concat([left, right])
end
@doc """
Concatenates a list of aliases and returns a new alias only if the alias
was already referenced.
If the alias was not referenced yet, fails with `ArgumentError`.
It handles charlists, binaries and atoms.
## Examples
iex> Module.safe_concat([Module, Unknown])
** (ArgumentError) argument error
iex> Module.safe_concat([List, Chars])
List.Chars
"""
@spec safe_concat([binary | atom]) :: atom | no_return
def safe_concat(list) when is_list(list) do
:elixir_aliases.safe_concat(list)
end
@doc """
Concatenates two aliases and returns a new alias only if the alias was
already referenced.
If the alias was not referenced yet, fails with `ArgumentError`.
It handles charlists, binaries and atoms.
## Examples
iex> Module.safe_concat(Module, Unknown)
** (ArgumentError) argument error
iex> Module.safe_concat(List, Chars)
List.Chars
"""
@spec safe_concat(binary | atom, binary | atom) :: atom | no_return
def safe_concat(left, right) do
:elixir_aliases.safe_concat([left, right])
end
@doc """
Attaches documentation to a given function or type.
It expects the module the function/type belongs to, the line (a non
negative integer), the kind (`def` or `defmacro`), a tuple representing
the function and its arity, the function signature (the signature
should be omitted for types) and the documentation, which should
be either a binary or a boolean.
## Examples
defmodule MyModule do
Module.add_doc(__MODULE__, __ENV__.line + 1, :def, {:version, 0}, [], "Manually added docs")
def version, do: 1
end
"""
def add_doc(module, line, kind, tuple, signature \\ [], doc)
def add_doc(_module, _line, kind, _tuple, _signature, doc) when kind in [:defp, :defmacrop, :typep] do
if doc, do: {:error, :private_doc}, else: :ok
end
def add_doc(module, line, kind, tuple, signature, doc) when
kind in [:def, :defmacro, :type, :opaque] and (is_binary(doc) or is_boolean(doc) or doc == nil) do
assert_not_compiled!(:add_doc, module)
table = data_table_for(module)
signature = simplify_signature(signature)
case :ets.lookup(table, {:doc, tuple}) do
[] ->
:ets.insert(table, {{:doc, tuple}, line, kind, signature, doc})
:ok
[{doc_tuple, line, _old_kind, old_sign, old_doc}] ->
:ets.insert(table, {
doc_tuple,
line,
kind,
merge_signatures(old_sign, signature, 1),
if(is_nil(doc), do: old_doc, else: doc)
})
:ok
end
end
# Simplify signatures to be stored in docs
defp simplify_signature(signature) do
{signature, acc} = :lists.mapfoldl(&simplify_signature/2, [], signature)
{signature, _} = :lists.mapfoldl(&expand_signature/2, {acc, acc}, signature)
signature
end
defp simplify_signature({:\\, _, [left, right ]}, acc) do
{left, acc} = simplify_signature(left, acc)
{{:\\, [], [left, right]}, acc}
end
defp simplify_signature({:=, _, [_, right]}, acc) do
simplify_signature(right, acc)
end
defp simplify_signature({var, _, atom}, acc) when is_atom(atom) do
case Atom.to_string(var) do
"_" <> rest -> {{String.to_atom(rest), [], Elixir}, acc}
_ -> {{var, [], nil}, acc}
end
end
defp simplify_signature({:%, _, [left, _]}, acc) when is_atom(left) do
struct_name = String.to_atom(camelcase_to_underscore(List.last(split(left))))
autogenerated(acc, struct_name)
end
defp simplify_signature({:%{}, _, _}, acc) do
autogenerated(acc, :map)
end
defp simplify_signature(other, acc) when is_integer(other), do: autogenerated(acc, :int)
defp simplify_signature(other, acc) when is_boolean(other), do: autogenerated(acc, :bool)
defp simplify_signature(other, acc) when is_atom(other), do: autogenerated(acc, :atom)
defp simplify_signature(other, acc) when is_list(other), do: autogenerated(acc, :list)
defp simplify_signature(other, acc) when is_float(other), do: autogenerated(acc, :float)
defp simplify_signature(other, acc) when is_binary(other), do: autogenerated(acc, :binary)
defp simplify_signature(_, acc), do: autogenerated(acc, :arg)
defp autogenerated(acc, key) do
{key, [key | acc]}
end
defp expand_signature(key, {all_keys, acc}) when is_atom(key) do
case previous_values(key, all_keys, acc) do
{i, acc} -> {{:"#{key}#{i}", [], Elixir}, {all_keys, acc}}
:none -> {{key, [], Elixir}, {all_keys, acc}}
end
end
defp expand_signature(term, {_, _} = acc) do
{term, acc}
end
defp previous_values(key, all_keys, acc) do
total_occurrences = occurrences(key, all_keys)
if total_occurrences == 1 do
:none
else
index = total_occurrences - occurrences(key, acc) + 1
{index, :lists.delete(key, acc)}
end
end
defp occurrences(key, list) do
length(:lists.filter(fn(el) -> el == key end, list))
end
defp camelcase_to_underscore(<<c::utf8, rest::binary>>) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<c + 32::utf8>>)
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc) when c >= ?A and c <= ?Z,
do: do_camelcase_to_underscore(rest, <<acc::binary, ?_, c + 32::utf8>>)
defp do_camelcase_to_underscore(<<c::utf8, rest::binary>>, acc),
do: do_camelcase_to_underscore(rest, <<acc::binary, c>>)
defp do_camelcase_to_underscore(<<>>, acc),
do: acc
# Merge
defp merge_signatures([h1 | t1], [h2 | t2], i) do
[merge_signature(h1, h2, i) | merge_signatures(t1, t2, i + 1)]
end
defp merge_signatures([], [], _) do
[]
end
defp merge_signature({:\\, line, [left, right]}, newer, i) do
{:\\, line, [merge_signature(left, newer, i), right]}
end
defp merge_signature(older, {:\\, _, [left, _]}, i) do
merge_signature(older, left, i)
end
# The older signature, when given, always have higher precedence
defp merge_signature({_, _, nil} = older, _newer, _), do: older
defp merge_signature(_older, {_, _, nil} = newer, _), do: newer
# Both are a guess, so check if they are the same guess
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({_, line, _}, _newer, i), do: {:"arg#{i}", line, Elixir}
@doc """
Checks if the module defines the given function or macro.
Use `defines?/3` to assert for a specific type.
## Examples
defmodule Example do
Module.defines? __MODULE__, {:version, 0} #=> false
def version, do: 1
Module.defines? __MODULE__, {:version, 0} #=> true
end
"""
def defines?(module, tuple) when is_tuple(tuple) do
assert_not_compiled!(:defines?, module)
table = defs_table_for(module)
:ets.lookup(table, {:def, tuple}) != []
end
@doc """
Checks if the module defines a function or macro of the
given `kind`.
`kind` can be any of `:def`, `:defp`, `:defmacro` or `:defmacrop`.
## Examples
defmodule Example do
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
def version, do: 1
Module.defines? __MODULE__, {:version, 0}, :defp #=> false
end
"""
def defines?(module, tuple, kind) do
assert_not_compiled!(:defines?, module)
table = defs_table_for(module)
case :ets.lookup(table, {:def, tuple}) do
[{_, ^kind, _, _, _, _, _}] -> true
_ -> false
end
end
@doc """
Returns all functions defined in `module`.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__ #=> [{:version, 0}]
end
"""
def definitions_in(module) do
assert_not_compiled!(:definitions_in, module)
table = defs_table_for(module)
:lists.concat :ets.match(table, {{:def, :'$1'}, :_, :_, :_, :_, :_, :_})
end
@doc """
Returns all functions defined in `module`, according
to its kind.
## Examples
defmodule Example do
def version, do: 1
Module.definitions_in __MODULE__, :def #=> [{:version, 0}]
Module.definitions_in __MODULE__, :defp #=> []
end
"""
def definitions_in(module, kind) do
assert_not_compiled!(:definitions_in, module)
table = defs_table_for(module)
:lists.concat :ets.match(table, {{:def, :'$1'}, kind, :_, :_, :_, :_, :_})
end
@doc """
Makes the given functions in `module` overridable.
An overridable function is lazily defined, allowing a
developer to customize it. See `Kernel.defoverridable/1` for
more information and documentation.
"""
def make_overridable(module, tuples) do
assert_not_compiled!(:make_overridable, module)
:lists.foreach(fn tuple ->
case :elixir_def.take_definition(module, tuple) do
false ->
{name, arity} = tuple
raise "cannot make function #{name}/#{arity} overridable because it was not defined"
clause ->
neighbours =
if :elixir_compiler.get_opt(:internal) do
[]
else
Module.LocalsTracker.yank(module, tuple)
end
old = :elixir_def_overridable.overridable(module)
count = case :maps.find(tuple, old) do
{:ok, {count, _, _, _}} -> count + 1
:error -> 1
end
new = :maps.put(tuple, {count, clause, neighbours, false}, old)
:elixir_def_overridable.overridable(module, new)
end
end, tuples)
end
@doc """
Returns `true` if `tuple` in `module` is marked as overridable.
"""
def overridable?(module, tuple) do
:maps.is_key(tuple, :elixir_def_overridable.overridable(module))
end
@doc """
Puts an Erlang attribute to the given module with the given
key and value.
The semantics of putting the attribute depends
if the attribute was registered or not via `register_attribute/3`.
## Examples
defmodule MyModule do
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
end
"""
def put_attribute(module, key, value) do
put_attribute(module, key, value, nil)
end
@doc false
def put_attribute(module, key, value, stack) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = preprocess_attribute(key, value)
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
warn_if_redefining_doc_attribute(stack, table, key)
new =
if :lists.member(key, acc) do
case :ets.lookup(table, key) do
[{^key, old}] -> [value | old]
[] -> [value]
end
else
value
end
:ets.insert(table, {key, new})
end
@doc """
Gets the given attribute from a module.
If the attribute was marked with `accumulate` with
`Module.register_attribute/3`, a list is always returned. `nil` is returned
if the attribute has not been marked with `accumulate` and has not been set
to any value.
The `@` macro compiles to a call to this function. For example,
the following code:
@foo
Expands close to:
Module.get_attribute(__MODULE__, :foo)
## Examples
defmodule Foo do
Module.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> 1
Module.register_attribute __MODULE__, :value, accumulate: true
Module.put_attribute __MODULE__, :value, 1
Module.get_attribute __MODULE__, :value #=> [1]
end
"""
@spec get_attribute(atom, atom) :: term
def get_attribute(module, key) do
get_attribute(module, key, nil)
end
@doc """
Deletes all attributes that match the given key.
## Examples
defmodule MyModule do
Module.put_attribute __MODULE__, :custom_threshold_for_lib, 10
Module.delete_attribute __MODULE__, :custom_threshold_for_lib
end
"""
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
:ets.delete(table, key)
:ok
end
@doc """
Registers an attribute. By registering an attribute, a developer
is able to customize how Elixir will store and accumulate the
attribute values.
## Options
When registering an attribute, two options can be given:
* `:accumulate` - several calls to the same attribute will
accumulate instead of override the previous one. New attributes
are always added to the top of the accumulated list.
* `:persist` - the attribute will be persisted in the Erlang
Abstract Format. Useful when interfacing with Erlang libraries.
By default, both options are `false`.
## Examples
defmodule MyModule do
Module.register_attribute __MODULE__,
:custom_threshold_for_lib,
accumulate: true, persist: false
@custom_threshold_for_lib 10
@custom_threshold_for_lib 20
@custom_threshold_for_lib #=> [20, 10]
end
"""
def register_attribute(module, new, opts) when is_atom(new) do
assert_not_compiled!(:register_attribute, module)
table = data_table_for(module)
if Keyword.get(opts, :persist) do
old = :ets.lookup_element(table, {:elixir, :persisted_attributes}, 2)
:ets.insert(table, {{:elixir, :persisted_attributes}, [new | old]})
end
if Keyword.get(opts, :accumulate) do
old = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
:ets.insert(table, {{:elixir, :acc_attributes}, [new | old]})
end
end
@doc """
Splits the given module name into binary parts.
## Examples
iex> Module.split Very.Long.Module.Name.And.Even.Longer
["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
"""
def split(module) when is_atom(module) do
split(String.Chars.to_string(module))
end
def split("Elixir." <> name) do
String.split(name, ".")
end
@doc false
# Used internally to compile documentation. This function
# is private and must be used only internally.
def compile_doc(env, kind, name, args, _guards, _body) do
module = env.module
table = data_table_for(module)
arity = length(args)
pair = {name, arity}
{line, doc} = get_doc_info(table, env)
# Arguments are not expanded for the docs, but we make an exception for
# module attributes and for structs (aliases to be precise).
args = Macro.prewalk args, fn
{:@, _, _} = attr ->
Macro.expand_once(attr, env)
{:%, meta, [aliases, fields]} ->
{:%, meta, [Macro.expand_once(aliases, env), fields]}
x ->
x
end
case add_doc(module, line, kind, pair, args, doc) do
:ok ->
:ok
{:error, :private_doc} ->
:elixir_errors.warn line, env.file,
"function #{name}/#{arity} is private, " <>
"@doc's are always discarded for private functions"
end
:ok
end
@doc false
# Used internally to compile types. This function
# is private and must be used only internally.
def store_typespec(module, key, value) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
new =
case :ets.lookup(table, key) do
[{^key, old}] -> [value | old]
[] -> [value]
end
:ets.insert(table, {key, new})
end
@doc false
def get_attribute(module, key, stack) when is_atom(key) and (is_list(stack) or is_nil(stack)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] ->
val
[] ->
acc = :ets.lookup_element(table, {:elixir, :acc_attributes}, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(stack) ->
IO.warn "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it before access", stack
nil
true ->
nil
end
end
end
## Helpers
defp preprocess_attribute(key, value) when key in [:moduledoc, :typedoc, :doc] do
unless match?({line, _} when is_integer(line), value) do
raise ArgumentError, "expected #{key} attribute given in the {line, doc} format, got: #{inspect(value)}"
end
value
end
defp preprocess_attribute(:on_load, atom) when is_atom(atom) do
{atom, 0}
end
defp preprocess_attribute(:behaviour, atom) when is_atom(atom) do
# Attempt to compile behaviour but ignore failure (will warn later)
_ = Code.ensure_compiled(atom)
atom
end
defp preprocess_attribute(:file, file) when is_binary(file) do
file
end
defp preprocess_attribute(:before_compile, atom) when is_atom(atom),
do: {atom, :__before_compile__}
defp preprocess_attribute(:after_compile, atom) when is_atom(atom),
do: {atom, :__after_compile__}
defp preprocess_attribute(:on_definition, atom) when is_atom(atom),
do: {atom, :__on_definition__}
defp preprocess_attribute(key, _value) when key in [:type, :typep, :export_type, :opaque, :callback, :macrocallback, :optional_callbacks] do
raise ArgumentError, "attributes type, typep, export_type, opaque, callback, macrocallback, and optional_callbacks " <>
"must be set directly via the @ notation"
end
defp preprocess_attribute(_key, value) do
value
end
defp get_doc_info(table, env) do
case :ets.take(table, :doc) do
[doc: {_, _} = pair] -> pair
[] -> {env.line, nil}
end
end
defp data_table_for(module) do
:elixir_module.data_table(module)
end
defp defs_table_for(module) do
:elixir_module.defs_table(module)
end
defp assert_not_compiled!(fun, module) do
open?(module) ||
raise ArgumentError,
"could not call #{fun} on module #{inspect module} because it was already compiled"
end
defp warn_if_redefining_doc_attribute(stack, table, key)
when is_list(stack) and key in [:doc, :typedoc, :moduledoc] do
case :ets.lookup(table, key) do
[{_, {line, val}}] when val != false ->
IO.warn "redefining @#{key} attribute previously set at line #{line}", stack
_ ->
false
end
end
defp warn_if_redefining_doc_attribute(nil, _table, _key), do: false
end