Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions lib/elixir/lib/macro/env.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ defmodule Macro.Env do
@type expand_import_opts :: [
allow_locals: boolean(),
check_deprecations: boolean(),
trace: boolean()
trace: boolean(),
local_for_callback: (Macro.metadata(), atom(), arity(), [atom()], t() -> any())
]

@type expand_require_opts :: [
Expand Down Expand Up @@ -561,6 +562,12 @@ defmodule Macro.Env do
* `:check_deprecations` - when set to `false`, does not check for deprecations
when expanding macros

* `:local_for_callback` - a function that receives the metadata, name, arity,
kinds list, and environment, and returns the local macro expansion or `false`.
The expansion can be a function or any other value. Non-function values will
cause the macro expansion to be skipped and return `:ok`.
Defaults to calling `:elixir_def.local_for/5`

* #{trace_option}

"""
Expand All @@ -571,6 +578,8 @@ defmodule Macro.Env do
| {:error, :not_found | {:conflict, module()} | {:ambiguous, [module()]}}
def expand_import(env, meta, name, arity, opts \\ [])
when is_list(meta) and is_atom(name) and is_integer(arity) and is_list(opts) do
local_for_callback = Keyword.get(opts, :local_for_callback)

case :elixir_import.special_form(name, arity) do
true ->
{:error, :not_found}
Expand All @@ -580,13 +589,19 @@ defmodule Macro.Env do
trace = Keyword.get(opts, :trace, true)
module = env.module

# When local_for_callback is provided, we don't need to pass module macros as extra
# because the callback will handle local macro resolution
extra =
case allow_locals and function_exported?(module, :__info__, 1) do
true -> [{module, module.__info__(:macros)}]
false -> []
if local_for_callback do
[]
else
case allow_locals and function_exported?(module, :__info__, 1) do
true -> [{module, module.__info__(:macros)}]
false -> []
end
end

case :elixir_dispatch.expand_import(meta, name, arity, env, extra, allow_locals, trace) do
case :elixir_dispatch.expand_import(meta, name, arity, env, extra, local_for_callback || allow_locals, trace) do
{:macro, receiver, expander} ->
{:macro, receiver, wrap_expansion(receiver, expander, meta, name, arity, env, opts)}

Expand Down
8 changes: 7 additions & 1 deletion lib/elixir/src/elixir_dispatch.erl
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,13 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) ->
do_expand_import(Dispatch, Meta, Name, Arity, Module, E, Trace);

_ ->
Local = AllowLocals andalso elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E),
Local = case AllowLocals of
false -> false;
true -> elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E);
Fun when is_function(Fun, 5) ->
%% If we have a custom local resolver, use it.
Fun(Meta, Name, Arity, [defmacro, defmacrop], E)
end,

case Dispatch of
%% There is a local and an import. This is a conflict unless
Expand Down
Loading