From 54cabc4751f81a803f6dde69dd8bbc6bbcc3ada7 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 3 Jul 2025 14:47:24 +0200 Subject: [PATCH 1/7] Add local_for_callback option to expand_import Allow hooking into local macro resolution --- lib/elixir/lib/macro/env.ex | 27 +++++++++++++++++++----- lib/elixir/src/elixir_dispatch.erl | 33 +++++++++++++++++++----------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 8e0016431bb..5276bf6a366 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -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 :: [ @@ -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} """ @@ -571,6 +578,10 @@ 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, fn meta, name, arity, kinds, e -> + :elixir_def.local_for(meta, name, arity, kinds, e) + end) + case :elixir_import.special_form(name, arity) do true -> {:error, :not_found} @@ -580,13 +591,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 Keyword.has_key?(opts, :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, allow_locals, trace, local_for_callback) do {:macro, receiver, expander} -> {:macro, receiver, wrap_expansion(receiver, expander, meta, name, arity, env, opts)} diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index e6769e2ba80..680869cbf6f 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -8,7 +8,7 @@ -module(elixir_dispatch). -export([dispatch_import/6, dispatch_require/7, require_function/5, import_function/4, - expand_import/7, expand_require/6, check_deprecated/6, + expand_import/8, expand_require/6, check_deprecated/6, default_functions/0, default_macros/0, default_requires/0, find_import/4, find_imports/3, format_error/1]). -include("elixir.hrl"). @@ -115,7 +115,8 @@ dispatch_import(Meta, Name, Args, S, E, Callback) -> _ -> false end, - case expand_import(Meta, Name, Arity, E, [], AllowLocals, true) of + DefaultLocalForCallback = fun(M, N, A, K, Env) -> elixir_def:local_for(M, N, A, K, Env) end, + case expand_import(Meta, Name, Arity, E, [], AllowLocals, true, DefaultLocalForCallback) of {macro, Receiver, Expander} -> check_deprecated(macro, Meta, Receiver, Name, Arity, E), Caller = {?line(Meta), S, E}, @@ -159,7 +160,7 @@ dispatch_require(_Meta, Receiver, Name, _Args, _S, _E, Callback) -> %% Macros expansion -expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> +expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace, LocalForCallback) -> Tuple = {Name, Arity}, Module = ?key(E, module), Dispatch = find_import_by_name_arity(Meta, Tuple, Extra, E), @@ -172,7 +173,7 @@ 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 = AllowLocals andalso LocalForCallback(Meta, Name, Arity, [defmacro, defmacrop], E), case Dispatch of %% There is a local and an import. This is a conflict unless @@ -249,14 +250,22 @@ expander_macro_named(Meta, Receiver, Name, Arity, E) -> fun(Args, Caller) -> expand_macro_fun(Meta, Fun, Receiver, Name, Args, Caller, E) end. expand_macro_fun(Meta, Fun, Receiver, Name, Args, Caller, E) -> - try - apply(Fun, [Caller | Args]) - catch - Kind:Reason:Stacktrace -> - Arity = length(Args), - MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1}, - Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(?line(Meta), E)], - erlang:raise(Kind, Reason, prune_stacktrace(Stacktrace, MFA, Info, {ok, Caller})) + %% Check if Fun is actually a function, as it might be a fake value for local macros + %% when using custom local_for_callback + case is_function(Fun) of + true -> + try + apply(Fun, [Caller | Args]) + catch + Kind:Reason:Stacktrace -> + Arity = length(Args), + MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1}, + Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(?line(Meta), E)], + erlang:raise(Kind, Reason, prune_stacktrace(Stacktrace, MFA, Info, {ok, Caller})) + end; + false -> + %% Return a fake value and omit expansion when Fun is not a function + ok end. expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E) -> From 997eb4c41c7cd7a34fa6a0f7d68cae2ab9bd7b66 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 4 Jul 2025 14:10:31 +0200 Subject: [PATCH 2/7] PR review comments --- lib/elixir/lib/macro/env.ex | 8 +++--- lib/elixir/src/elixir_dispatch.erl | 39 ++++++++++++++---------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 5276bf6a366..333c3cb03f8 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -578,9 +578,7 @@ 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, fn meta, name, arity, kinds, e -> - :elixir_def.local_for(meta, name, arity, kinds, e) - end) + local_for_callback = Keyword.get(opts, :local_for_callback) case :elixir_import.special_form(name, arity) do true -> @@ -594,7 +592,7 @@ defmodule Macro.Env do # 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 = - if Keyword.has_key?(opts, :local_for_callback) do + if local_for_callback do [] else case allow_locals and function_exported?(module, :__info__, 1) do @@ -603,7 +601,7 @@ defmodule Macro.Env do end end - case :elixir_dispatch.expand_import(meta, name, arity, env, extra, allow_locals, trace, local_for_callback) 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)} diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 680869cbf6f..6a8bcd53704 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -8,7 +8,7 @@ -module(elixir_dispatch). -export([dispatch_import/6, dispatch_require/7, require_function/5, import_function/4, - expand_import/8, expand_require/6, check_deprecated/6, + expand_import/7, expand_require/6, check_deprecated/6, default_functions/0, default_macros/0, default_requires/0, find_import/4, find_imports/3, format_error/1]). -include("elixir.hrl"). @@ -115,8 +115,7 @@ dispatch_import(Meta, Name, Args, S, E, Callback) -> _ -> false end, - DefaultLocalForCallback = fun(M, N, A, K, Env) -> elixir_def:local_for(M, N, A, K, Env) end, - case expand_import(Meta, Name, Arity, E, [], AllowLocals, true, DefaultLocalForCallback) of + case expand_import(Meta, Name, Arity, E, [], AllowLocals, true) of {macro, Receiver, Expander} -> check_deprecated(macro, Meta, Receiver, Name, Arity, E), Caller = {?line(Meta), S, E}, @@ -160,7 +159,7 @@ dispatch_require(_Meta, Receiver, Name, _Args, _S, _E, Callback) -> %% Macros expansion -expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace, LocalForCallback) -> +expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> Tuple = {Name, Arity}, Module = ?key(E, module), Dispatch = find_import_by_name_arity(Meta, Tuple, Extra, E), @@ -173,7 +172,13 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace, LocalForCallback) do_expand_import(Dispatch, Meta, Name, Arity, Module, E, Trace); _ -> - Local = AllowLocals andalso LocalForCallback(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 @@ -250,22 +255,14 @@ expander_macro_named(Meta, Receiver, Name, Arity, E) -> fun(Args, Caller) -> expand_macro_fun(Meta, Fun, Receiver, Name, Args, Caller, E) end. expand_macro_fun(Meta, Fun, Receiver, Name, Args, Caller, E) -> - %% Check if Fun is actually a function, as it might be a fake value for local macros - %% when using custom local_for_callback - case is_function(Fun) of - true -> - try - apply(Fun, [Caller | Args]) - catch - Kind:Reason:Stacktrace -> - Arity = length(Args), - MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1}, - Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(?line(Meta), E)], - erlang:raise(Kind, Reason, prune_stacktrace(Stacktrace, MFA, Info, {ok, Caller})) - end; - false -> - %% Return a fake value and omit expansion when Fun is not a function - ok + try + apply(Fun, [Caller | Args]) + catch + Kind:Reason:Stacktrace -> + Arity = length(Args), + MFA = {Receiver, elixir_utils:macro_name(Name), Arity+1}, + Info = [{Receiver, Name, Arity, [{file, "expanding macro"}]}, caller(?line(Meta), E)], + erlang:raise(Kind, Reason, prune_stacktrace(Stacktrace, MFA, Info, {ok, Caller})) end. expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E) -> From 884e98932c1f0a0ce85dc1571c8893eff8a2d9c6 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 4 Jul 2025 14:17:24 +0200 Subject: [PATCH 3/7] format --- lib/elixir/lib/macro/env.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 333c3cb03f8..9eeb5a8eaa3 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -601,7 +601,15 @@ defmodule Macro.Env do end end - case :elixir_dispatch.expand_import(meta, name, arity, env, extra, local_for_callback || 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)} From f9c418dcb4ec1974e21c9e42e23856076296c093 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 4 Jul 2025 17:01:46 +0200 Subject: [PATCH 4/7] remove extra use one param --- lib/elixir/lib/macro/env.ex | 93 +++++++++++++++++++++--------- lib/elixir/src/elixir_dispatch.erl | 16 ++--- 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 9eeb5a8eaa3..2a6c5d5bd9c 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -96,10 +96,9 @@ defmodule Macro.Env do ] @type expand_import_opts :: [ - allow_locals: boolean(), + allow_locals: boolean() | (Macro.metadata(), atom(), arity(), [atom()], t() -> any()), check_deprecations: boolean(), - trace: boolean(), - local_for_callback: (Macro.metadata(), atom(), arity(), [atom()], t() -> any()) + trace: boolean() ] @type expand_require_opts :: [ @@ -557,17 +556,15 @@ defmodule Macro.Env do ## Options * `:allow_locals` - when set to `false`, it does not attempt to capture - local macros defined in the current module in `env` + local macros defined in the current module in `env`. + When set to `true`, it uses a default resolver that looks for public macros. + When set to a function, it uses the function as a local resolver. + The function must have the following signature: + `fn meta, name, arity, kinds, env -> function() | false`. * `: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} """ @@ -578,36 +575,80 @@ 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} false -> - allow_locals = Keyword.get(opts, :allow_locals, true) - 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 = - if local_for_callback do - [] - else - case allow_locals and function_exported?(module, :__info__, 1) do - true -> [{module, module.__info__(:macros)}] - false -> [] - end + allow_locals = + case Keyword.get(opts, :allow_locals, true) do + false -> + false + + true -> + macros = + if function_exported?(module, :__info__, 1) do + module.__info__(:macros) + else + [] + end + + fn _meta, name, arity, kinds, _e -> + IO.puts( + "Resolving local macro #{name}/#{arity} in #{module} with kinds: #{inspect(kinds)}" + ) + + IO.puts("Macros defined in #{module}: #{inspect(macros)}") + + # by default use a resolver looking for public macros + cond do + :lists.any( + fn + :defmacro -> true + :defmacrop -> true + _ -> false + end, + kinds + ) and macro_exported?(module, name, arity) -> + # public macro found - return the expander + proper_name = :"MACRO-#{name}" + proper_arity = arity + 1 + Function.capture(module, proper_name, proper_arity) + + :lists.any( + fn + :def -> true + :defp -> true + _ -> false + end, + kinds + ) and function_exported?(module, name, arity) -> + Function.capture(module, name, arity) + + true -> + IO.puts( + "No local macro found for #{name}/#{arity} in #{module} with kinds: #{inspect(kinds)}" + ) + + false + end + end + + fun when is_function(fun, 5) -> + # If we have a custom local resolver, use it. + fun end + trace = Keyword.get(opts, :trace, true) + case :elixir_dispatch.expand_import( meta, name, arity, env, - extra, - local_for_callback || allow_locals, + allow_locals, trace ) do {:macro, receiver, expander} -> diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 6a8bcd53704..c5f095cea03 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -8,7 +8,7 @@ -module(elixir_dispatch). -export([dispatch_import/6, dispatch_require/7, require_function/5, import_function/4, - expand_import/7, expand_require/6, check_deprecated/6, + expand_import/6, expand_require/6, check_deprecated/6, default_functions/0, default_macros/0, default_requires/0, find_import/4, find_imports/3, format_error/1]). -include("elixir.hrl"). @@ -29,7 +29,7 @@ default_requires() -> find_import(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, - case find_import_by_name_arity(Meta, Tuple, [], E) of + case find_import_by_name_arity(Meta, Tuple, E) of {function, Receiver} -> elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E), Receiver; @@ -56,7 +56,7 @@ find_imports(Meta, Name, E) -> import_function(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, - case find_import_by_name_arity(Meta, Tuple, [], E) of + case find_import_by_name_arity(Meta, Tuple, E) of {function, Receiver} -> elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E), elixir_import:record(Tuple, Receiver, ?key(E, module), ?key(E, function)), @@ -115,7 +115,7 @@ dispatch_import(Meta, Name, Args, S, E, Callback) -> _ -> false end, - case expand_import(Meta, Name, Arity, E, [], AllowLocals, true) of + case expand_import(Meta, Name, Arity, E, AllowLocals, true) of {macro, Receiver, Expander} -> check_deprecated(macro, Meta, Receiver, Name, Arity, E), Caller = {?line(Meta), S, E}, @@ -159,10 +159,10 @@ dispatch_require(_Meta, Receiver, Name, _Args, _S, _E, Callback) -> %% Macros expansion -expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> +expand_import(Meta, Name, Arity, E, AllowLocals, Trace) -> Tuple = {Name, Arity}, Module = ?key(E, module), - Dispatch = find_import_by_name_arity(Meta, Tuple, Extra, E), + Dispatch = find_import_by_name_arity(Meta, Tuple, E), case Dispatch of {ambiguous, Ambiguous} -> @@ -303,13 +303,13 @@ find_imports_by_name(Name, [{ImportName, _} | Imports], Acc, Mod, Meta, E) when find_imports_by_name(_Name, _Imports, Acc, _Mod, _Meta, _E) -> Acc. -find_import_by_name_arity(Meta, {_Name, Arity} = Tuple, Extra, E) -> +find_import_by_name_arity(Meta, {_Name, Arity} = Tuple, E) -> case is_import(Meta, Arity) of {import, _} = Import -> Import; false -> Funs = ?key(E, functions), - Macs = Extra ++ ?key(E, macros), + Macs = ?key(E, macros), FunMatch = find_import_by_name_arity(Tuple, Funs), MacMatch = find_import_by_name_arity(Tuple, Macs), From 201952d3e24f5e8d69aceb00c578e4c3499d4647 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Fri, 4 Jul 2025 18:15:31 +0200 Subject: [PATCH 5/7] Revert "remove extra" --- lib/elixir/lib/macro/env.ex | 89 ++++++++---------------------- lib/elixir/src/elixir_dispatch.erl | 16 +++--- 2 files changed, 32 insertions(+), 73 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 2a6c5d5bd9c..ac3fbbbdadf 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -555,12 +555,17 @@ defmodule Macro.Env do ## Options - * `:allow_locals` - when set to `false`, it does not attempt to capture - local macros defined in the current module in `env`. - When set to `true`, it uses a default resolver that looks for public macros. - When set to a function, it uses the function as a local resolver. - The function must have the following signature: - `fn meta, name, arity, kinds, env -> function() | false`. + * `:allow_locals` - controls how local macros are resolved. + Defaults to `true`. + + - When `false`, does not attempt to capture local macros defined in the + current module in `env` + - When `true`, uses a default resolver that looks for public macros in + the current module + - When a function, uses the function as a custom local resolver. The function + must have the signature: `(meta, name, arity, kinds, env) -> function() | false` + where `kinds` is a list of atoms indicating the types of symbols being + searched (e.g., `[:defmacro, :defmacrop]`) * `:check_deprecations` - when set to `false`, does not check for deprecations when expanding macros @@ -580,74 +585,28 @@ defmodule Macro.Env do {:error, :not_found} false -> + allow_locals = Keyword.get(opts, :allow_locals, true) + trace = Keyword.get(opts, :trace, true) module = env.module - allow_locals = - case Keyword.get(opts, :allow_locals, true) do - false -> - false - - true -> - macros = - if function_exported?(module, :__info__, 1) do - module.__info__(:macros) - else - [] - end - - fn _meta, name, arity, kinds, _e -> - IO.puts( - "Resolving local macro #{name}/#{arity} in #{module} with kinds: #{inspect(kinds)}" - ) - - IO.puts("Macros defined in #{module}: #{inspect(macros)}") - - # by default use a resolver looking for public macros - cond do - :lists.any( - fn - :defmacro -> true - :defmacrop -> true - _ -> false - end, - kinds - ) and macro_exported?(module, name, arity) -> - # public macro found - return the expander - proper_name = :"MACRO-#{name}" - proper_arity = arity + 1 - Function.capture(module, proper_name, proper_arity) - - :lists.any( - fn - :def -> true - :defp -> true - _ -> false - end, - kinds - ) and function_exported?(module, name, arity) -> - Function.capture(module, name, arity) - - true -> - IO.puts( - "No local macro found for #{name}/#{arity} in #{module} with kinds: #{inspect(kinds)}" - ) - - false - end - end - - fun when is_function(fun, 5) -> - # If we have a custom local resolver, use it. - fun + # When allow_locals is a callback, we don't need to pass module macros as extra + # because the callback will handle local macro resolution + extra = + if is_function(allow_locals, 5) do + [] + else + case allow_locals and function_exported?(module, :__info__, 1) do + true -> [{module, module.__info__(:macros)}] + false -> [] + end end - trace = Keyword.get(opts, :trace, true) - case :elixir_dispatch.expand_import( meta, name, arity, env, + extra, allow_locals, trace ) do diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index c5f095cea03..6a8bcd53704 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -8,7 +8,7 @@ -module(elixir_dispatch). -export([dispatch_import/6, dispatch_require/7, require_function/5, import_function/4, - expand_import/6, expand_require/6, check_deprecated/6, + expand_import/7, expand_require/6, check_deprecated/6, default_functions/0, default_macros/0, default_requires/0, find_import/4, find_imports/3, format_error/1]). -include("elixir.hrl"). @@ -29,7 +29,7 @@ default_requires() -> find_import(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, - case find_import_by_name_arity(Meta, Tuple, E) of + case find_import_by_name_arity(Meta, Tuple, [], E) of {function, Receiver} -> elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E), Receiver; @@ -56,7 +56,7 @@ find_imports(Meta, Name, E) -> import_function(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, - case find_import_by_name_arity(Meta, Tuple, E) of + case find_import_by_name_arity(Meta, Tuple, [], E) of {function, Receiver} -> elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E), elixir_import:record(Tuple, Receiver, ?key(E, module), ?key(E, function)), @@ -115,7 +115,7 @@ dispatch_import(Meta, Name, Args, S, E, Callback) -> _ -> false end, - case expand_import(Meta, Name, Arity, E, AllowLocals, true) of + case expand_import(Meta, Name, Arity, E, [], AllowLocals, true) of {macro, Receiver, Expander} -> check_deprecated(macro, Meta, Receiver, Name, Arity, E), Caller = {?line(Meta), S, E}, @@ -159,10 +159,10 @@ dispatch_require(_Meta, Receiver, Name, _Args, _S, _E, Callback) -> %% Macros expansion -expand_import(Meta, Name, Arity, E, AllowLocals, Trace) -> +expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> Tuple = {Name, Arity}, Module = ?key(E, module), - Dispatch = find_import_by_name_arity(Meta, Tuple, E), + Dispatch = find_import_by_name_arity(Meta, Tuple, Extra, E), case Dispatch of {ambiguous, Ambiguous} -> @@ -303,13 +303,13 @@ find_imports_by_name(Name, [{ImportName, _} | Imports], Acc, Mod, Meta, E) when find_imports_by_name(_Name, _Imports, Acc, _Mod, _Meta, _E) -> Acc. -find_import_by_name_arity(Meta, {_Name, Arity} = Tuple, E) -> +find_import_by_name_arity(Meta, {_Name, Arity} = Tuple, Extra, E) -> case is_import(Meta, Arity) of {import, _} = Import -> Import; false -> Funs = ?key(E, functions), - Macs = ?key(E, macros), + Macs = Extra ++ ?key(E, macros), FunMatch = find_import_by_name_arity(Tuple, Funs), MacMatch = find_import_by_name_arity(Tuple, Macs), From 1be2d9d1cd0deb698c61da75f4ee439d332c194f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Jul 2025 09:47:34 +0200 Subject: [PATCH 6/7] Update lib/elixir/lib/macro/env.ex --- lib/elixir/lib/macro/env.ex | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index ac3fbbbdadf..1b47e45ccc1 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -601,15 +601,7 @@ defmodule Macro.Env do 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, allow_locals, trace) do {:macro, receiver, expander} -> {:macro, receiver, wrap_expansion(receiver, expander, meta, name, arity, env, opts)} From c80ffa28333accdfaf76de60e8c1050e31785ce1 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 6 Jul 2025 14:21:59 +0200 Subject: [PATCH 7/7] PR review fixes --- lib/elixir/lib/macro/env.ex | 9 ++++----- lib/elixir/src/elixir_dispatch.erl | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 1b47e45ccc1..edae8a4cc5d 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -96,7 +96,8 @@ defmodule Macro.Env do ] @type expand_import_opts :: [ - allow_locals: boolean() | (Macro.metadata(), atom(), arity(), [atom()], t() -> any()), + allow_locals: + boolean() | (Macro.metadata(), atom(), arity(), t() -> function() | false), check_deprecations: boolean(), trace: boolean() ] @@ -563,9 +564,7 @@ defmodule Macro.Env do - When `true`, uses a default resolver that looks for public macros in the current module - When a function, uses the function as a custom local resolver. The function - must have the signature: `(meta, name, arity, kinds, env) -> function() | false` - where `kinds` is a list of atoms indicating the types of symbols being - searched (e.g., `[:defmacro, :defmacrop]`) + must have the signature: `(meta, name, arity, env) -> function() | false` * `:check_deprecations` - when set to `false`, does not check for deprecations when expanding macros @@ -592,7 +591,7 @@ defmodule Macro.Env do # When allow_locals is a callback, we don't need to pass module macros as extra # because the callback will handle local macro resolution extra = - if is_function(allow_locals, 5) do + if is_function(allow_locals, 4) do [] else case allow_locals and function_exported?(module, :__info__, 1) do diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 6a8bcd53704..ea52a61df9b 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -175,9 +175,9 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> Local = case AllowLocals of false -> false; true -> elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E); - Fun when is_function(Fun, 5) -> + Fun when is_function(Fun, 4) -> %% If we have a custom local resolver, use it. - Fun(Meta, Name, Arity, [defmacro, defmacrop], E) + Fun(Meta, Name, Arity, E) end, case Dispatch of