diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 94f0212cc32..cf9dfd1a35a 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -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 diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 346097e9a76..436396857b5 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -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), diff --git a/lib/elixir/src/elixir_quote.erl b/lib/elixir/src/elixir_quote.erl index 3f872cd7944..4260985f28b 100644 --- a/lib/elixir/src/elixir_quote.erl +++ b/lib/elixir/src/elixir_quote.erl @@ -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}, + + Fun = if + Line =:= 0 -> fun(Meta) -> Meta end; + true -> fun(Meta) -> keynew(line, Meta, Line) end + end, - do_linify(Fun, Exprs, Var). + do_linify(Fun, Exprs, Var, Generated). -do_linify(Fun, {quote, Meta, [_ | _] = Args}, {Receiver, Counter} = Var) +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, Gen) when is_list(Meta) -> + do_tuple_linify(Fun, Meta, Left, Right, 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, Right}, Var, Gen) -> + {do_linify(Fun, Left, Var, Gen), do_linify(Fun, Right, Var, Gen)}; -do_linify(Fun, {Left, Right}, Var) -> - {do_linify(Fun, Left, Var), do_linify(Fun, Right, Var)}; +do_linify(Fun, List, Var, Gen) when is_list(List) -> + [do_linify(Fun, X, Var, Gen) || X <- List]; -do_linify(Fun, List, Var) when is_list(List) -> - [do_linify(Fun, X, Var) || X <- List]; +do_linify(_, Else, _, _Gen) -> Else. -do_linify(_, Else, _) -> Else. +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, --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_linify(Fun, Left, Var, NewGen), Fun(NewMeta), do_linify(Fun, Right, Var, NewGen)}. %% Escaping diff --git a/lib/elixir/test/elixir/macro/env_test.exs b/lib/elixir/test/elixir/macro/env_test.exs index 07e6a5b9546..1a62f3cab85 100644 --- a/lib/elixir/test/elixir/macro/env_test.exs +++ b/lib/elixir/test/elixir/macro/env_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index c3f752eea40..527a284ec2c 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -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__)} " <> diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex index 816aafa24f6..e1f64a99b28 100644 --- a/lib/ex_unit/lib/ex_unit/assertions.ex +++ b/lib/ex_unit/lib/ex_unit/assertions.ex @@ -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] ->