Stop propagating generated on macro arguments (#14696)

* Stop propagating generated on macro arguments

* Explicitly mark assert as generated
This commit is contained in:
Jean Klingler
2025-08-07 05:54:41 +09:00
committed by GitHub
parent 04f1b7ccae
commit 67042e8770
6 changed files with 105 additions and 42 deletions
+1 -1
View File
@@ -657,7 +657,7 @@ defmodule Macro.Env do
:elixir_dispatch.check_deprecated(:macro, meta, receiver, name, arity, env)
end
quoted = expander.(args, env)
quoted = expander.(:elixir_dispatch.stop_generated(args), env)
next = :elixir_module.next_counter(env.module)
:elixir_quote.linify_with_context_counter(expansion_meta, {receiver, next}, quoted)
end
+8 -2
View File
@@ -10,7 +10,7 @@
require_function/5, import_function/4,
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]).
find_import/4, find_imports/3, format_error/1, stop_generated/1]).
-include("elixir.hrl").
-import(ordsets, [is_element/2]).
-define(kernel, 'Elixir.Kernel').
@@ -119,7 +119,7 @@ dispatch_import(Meta, Name, Args, S, E, Callback) ->
{macro, Receiver, Expander} ->
check_deprecated(macro, Meta, Receiver, Name, Arity, E),
Caller = {?line(Meta), S, E},
expand_quoted(Meta, Receiver, Name, Arity, Expander(Args, Caller), S, E);
expand_quoted(Meta, Receiver, Name, Arity, Expander(stop_generated(Args), Caller), S, E);
{function, Receiver, NewName} ->
case elixir_rewrite:inline(Receiver, NewName, Arity) of
{AR, AN} ->
@@ -134,6 +134,12 @@ dispatch_import(Meta, Name, Args, S, E, Callback) ->
elixir_errors:file_error(Meta, E, ?MODULE, {import, Error, Name, Arity})
end.
stop_generated(Args) ->
lists:map(fun
({Call, Meta, Ctx}) when is_list(Meta) -> {Call, [{stop_generated, true} | Meta], Ctx};
(Other) -> Other
end, Args).
dispatch_require(Meta, Receiver, Name, Args, S, E, Callback) when is_atom(Receiver) ->
Arity = length(Args),
+31 -26
View File
@@ -9,7 +9,7 @@
-include("elixir.hrl").
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop; Kind == '@').
-define(lexical(Kind), Kind == import; Kind == alias; Kind == require).
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keynew/3, do_tuple_linify/5]}).
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keynew/3, do_tuple_linify/6]}).
-record(elixir_quote, {
line=false,
@@ -84,53 +84,58 @@ linify(Line, Key, Exprs) when is_integer(Line) ->
end
end,
do_linify(Fun, Exprs, nil).
do_linify(Fun, Exprs, nil, false).
%% Same as linify but also considers the context counter and generated.
linify_with_context_counter(ContextMeta, Var, Exprs) when is_list(ContextMeta) ->
Line = ?line(ContextMeta),
Fun =
case lists:keyfind(generated, 1, ContextMeta) of
{generated, true} when Line =:= 0 -> fun elixir_utils:generated/1;
{generated, true} -> fun(Meta) -> elixir_utils:generated(keynew(line, Meta, Line)) end;
_ when Line =:= 0 -> fun(Meta) -> Meta end;
_ -> fun(Meta) -> keynew(line, Meta, Line) end
end,
Generated = keyfind(generated, ContextMeta) == {generated, true},
do_linify(Fun, Exprs, Var).
Fun = if
Line =:= 0 -> fun(Meta) -> Meta end;
true -> fun(Meta) -> keynew(line, Meta, Line) end
end,
do_linify(Fun, {quote, Meta, [_ | _] = Args}, {Receiver, Counter} = Var)
do_linify(Fun, Exprs, Var, Generated).
do_linify(Fun, {quote, Meta, [_ | _] = Args}, {Receiver, Counter} = Var, Gen)
when is_list(Meta) ->
NewMeta =
case keyfind(context, Meta) == {context, Receiver} of
true -> keynew(counter, Meta, Counter);
false -> Meta
end,
do_tuple_linify(Fun, NewMeta, quote, Args, Var);
do_tuple_linify(Fun, NewMeta, quote, Args, Var, Gen);
do_linify(Fun, {Left, Meta, Receiver}, {Receiver, Counter} = Var)
do_linify(Fun, {Left, Meta, Receiver}, {Receiver, Counter} = Var, Gen)
when is_atom(Left), is_list(Meta), Left /= '_' ->
do_tuple_linify(Fun, keynew(counter, Meta, Counter), Left, Receiver, Var);
do_tuple_linify(Fun, keynew(counter, Meta, Counter), Left, Receiver, Var, Gen);
do_linify(Fun, {Lexical, Meta, [_ | _] = Args}, {_, Counter} = Var)
do_linify(Fun, {Lexical, Meta, [_ | _] = Args}, {_, Counter} = Var, Gen)
when ?lexical(Lexical); Lexical == '__aliases__' ->
do_tuple_linify(Fun, keynew(counter, Meta, Counter), Lexical, Args, Var);
do_tuple_linify(Fun, keynew(counter, Meta, Counter), Lexical, Args, Var, Gen);
do_linify(Fun, {Left, Meta, Right}, Var) when is_list(Meta) ->
do_tuple_linify(Fun, Meta, Left, Right, Var);
do_linify(Fun, {Left, Meta, Right}, Var, Gen) when is_list(Meta) ->
do_tuple_linify(Fun, Meta, Left, Right, Var, Gen);
do_linify(Fun, {Left, Right}, Var) ->
{do_linify(Fun, Left, Var), do_linify(Fun, Right, Var)};
do_linify(Fun, {Left, Right}, Var, Gen) ->
{do_linify(Fun, Left, Var, Gen), do_linify(Fun, Right, Var, Gen)};
do_linify(Fun, List, Var) when is_list(List) ->
[do_linify(Fun, X, Var) || X <- List];
do_linify(Fun, List, Var, Gen) when is_list(List) ->
[do_linify(Fun, X, Var, Gen) || X <- List];
do_linify(_, Else, _) -> Else.
do_linify(_, Else, _, _Gen) -> Else.
-compile({inline, do_tuple_linify/5}).
do_tuple_linify(Fun, Meta, Left, Right, Var) ->
{do_linify(Fun, Left, Var), Fun(Meta), do_linify(Fun, Right, Var)}.
do_tuple_linify(Fun, Meta, Left, Right, Var, Gen) ->
{NewMeta, NewGen} =
case keyfind(stop_generated, Meta) of
{stop_generated, true} -> {keydelete(stop_generated, Meta), false};
_ when Gen -> {elixir_utils:generated(Meta), Gen};
_ -> {Meta, Gen}
end,
{do_linify(Fun, Left, Var, NewGen), Fun(NewMeta), do_linify(Fun, Right, Var, NewGen)}.
%% Escaping
+21 -7
View File
@@ -4,7 +4,9 @@
Code.require_file("../test_helper.exs", __DIR__)
defmodule MacroEnvMacros do
defmacro my_macro(arg), do: arg
defmacro my_macro(arg) do
quote do: foo(unquote(arg))
end
@deprecated "this is deprecated"
defmacro my_deprecated_macro(arg), do: arg
@@ -154,9 +156,15 @@ defmodule Macro.EnvTest do
test "expands with argument" do
{:macro, MacroEnvMacros, fun} = expand_require(env(), meta(), MacroEnvMacros, :my_macro, 1)
assert fun.([], [quote(do: hello())]) == quote(do: hello())
assert fun.([line: 789], [quote(do: hello())]) == quote(line: 789, do: hello())
assert fun.([generated: true], [quote(do: hello())]) == quote(generated: true, do: hello())
assert fun.([], [quote(do: hello())]) == quote(do: foo(hello()))
assert fun.([line: 789], [quote(do: hello())]) == quote(line: 789, do: foo(hello()))
# do not propagate generated: true to arguments
assert {:foo, outer_meta, [{:hello, inner_meta, []}]} =
fun.([generated: true], [quote(do: hello())])
assert outer_meta[:generated]
refute inner_meta[:generated]
end
test "with tracing and deprecations" do
@@ -202,9 +210,15 @@ defmodule Macro.EnvTest do
test "expands with argument" do
{:macro, MacroEnvMacros, fun} = expand_import(env(), meta(), :my_macro, 1)
assert fun.([], [quote(do: hello())]) == quote(do: hello())
assert fun.([line: 789], [quote(do: hello())]) == quote(line: 789, do: hello())
assert fun.([generated: true], [quote(do: hello())]) == quote(generated: true, do: hello())
assert fun.([], [quote(do: hello())]) == quote(do: foo(hello()))
assert fun.([line: 789], [quote(do: hello())]) == quote(line: 789, do: foo(hello()))
# do not propagate generated: true to arguments
assert {:foo, outer_meta, [{:hello, inner_meta, []}]} =
fun.([generated: true], [quote(do: hello())])
assert outer_meta[:generated]
refute inner_meta[:generated]
end
defmacro allow_locals_example, do: :ok
+36 -1
View File
@@ -264,7 +264,7 @@ defmodule MacroTest do
assert Macro.expand_once(expr, __ENV__) == expr
end
test "propagates generated" do
test "propagates :generated" do
assert {:||, meta, [1, false]} = Macro.expand_once(quote(do: oror(1, false)), __ENV__)
refute meta[:generated]
@@ -274,6 +274,41 @@ defmodule MacroTest do
assert meta[:generated]
end
test "does not propagate :generated to unquoted" do
non_generated = quote do: foo()
assert {:||, outer_meta, [{:foo, inner_meta, []}, false]} =
Macro.expand_once(
quote generated: true do
oror(unquote(non_generated), false)
end,
__ENV__
)
assert outer_meta[:generated]
refute inner_meta[:generated]
end
defmacro foo_bar(x) do
y = quote do: bar(unquote(x))
quote do: foo(unquote(y))
end
test "propagates :generated to unquote within macro" do
non_generated = quote do: baz()
assert {:foo, foo_meta, [{:bar, bar_meta, [{:baz, baz_meta, []}]}]} =
Macro.expand_once(
quote(generated: true, do: foo_bar(unquote(non_generated))),
__ENV__
)
assert foo_meta[:generated]
assert bar_meta[:generated]
refute baz_meta[:generated]
end
test "does not expand module attributes" do
message =
"could not call Module.get_attribute/2 because the module #{inspect(__MODULE__)} " <>
+8 -5
View File
@@ -133,7 +133,7 @@ defmodule ExUnit.Assertions do
"""
defmacro assert({:=, meta, [left, right]} = assertion) do
code = escape_quoted(:assert, meta, assertion)
code = escape_quoted(:assert, meta, mark_as_generated(assertion))
check =
quote generated: true do
@@ -150,7 +150,7 @@ defmodule ExUnit.Assertions do
end
defmacro assert({:match?, meta, [left, right]} = assertion) do
code = escape_quoted(:assert, meta, assertion)
code = escape_quoted(:assert, meta, mark_as_generated(assertion))
match? = {:match?, meta, [left, Macro.var(:right, __MODULE__)]}
left = __expand_pattern__(left, __CALLER__)
@@ -727,10 +727,13 @@ defmodule ExUnit.Assertions do
defp has_var?(pattern, name, context), do: Enum.any?(pattern, &match?({^name, _, ^context}, &1))
defp mark_as_generated(vars) do
for {name, meta, context} <- vars, do: {name, [generated: true] ++ meta, context}
defp mark_as_generated(vars) when is_list(vars) do
Enum.map(vars, fn {name, meta, context} -> {name, [generated: true] ++ meta, context} end)
end
defp mark_as_generated({name, meta, context}), do: {name, [generated: true] ++ meta, context}
defp mark_as_generated(other), do: other
@doc false
def __expand_pattern__({:when, meta, [left, right]}, caller) do
left = expand_pattern(left, Macro.Env.to_match(caller))
@@ -952,7 +955,7 @@ defmodule ExUnit.Assertions do
defp do_catch(kind, expr) do
quote do
try do
_ = unquote(expr)
_ = unquote(mark_as_generated(expr))
flunk("Expected to catch #{unquote(kind)}, got nothing")
rescue
e in [ExUnit.AssertionError] ->