Stop propagating generated on macro arguments (#14696)
* Stop propagating generated on macro arguments * Explicitly mark assert as generated
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)} " <>
|
||||
|
||||
@@ -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] ->
|
||||
|
||||
Reference in New Issue
Block a user