diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 9a3b21c434..5c04e75e96 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -187,7 +187,7 @@ defmodule Macro do * `:alias` - Used for alias hygiene. * `:ambiguous_op` - Used for improved error messages in the compiler. - * `:import` - Used for import hygiene. + * `:imports` - Used for import hygiene. * `:var` - Used for improved error messages on undefined variables. Do not rely on them as they may change or be fully removed in future versions diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 248c5a8f7f..a51b1eb614 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -6,7 +6,7 @@ require_function/5, import_function/4, expand_import/7, expand_require/6, default_functions/0, default_macros/0, default_requires/0, - find_import/4, format_error/1]). + find_import/4, find_imports/4, format_error/1]). -include("elixir.hrl"). -import(ordsets, [is_element/2]). -define(kernel, 'Elixir.Kernel'). @@ -36,6 +36,20 @@ find_import(Meta, Name, Arity, E) -> false end. +find_imports(Meta, Name, Arity, E) -> + Tuple = {Name, Arity}, + + case find_dispatch(Meta, Tuple, [], E) of + {function, Receiver} -> + elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E), + [{Receiver, Arity}]; + {macro, Receiver} -> + elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E), + [{Receiver, Arity}]; + _ -> + [] + end. + %% Function retrieval import_function(Meta, Name, Arity, E) -> @@ -233,8 +247,8 @@ caller(Line, E) -> required(Meta) -> lists:keyfind(required, 1, Meta) == {required, true}. -find_dispatch(Meta, Tuple, Extra, E) -> - case is_import(Meta) of +find_dispatch(Meta, {_Name, Arity} = Tuple, Extra, E) -> + case is_import(Meta, Arity) of {import, _} = Import -> Import; false -> @@ -258,11 +272,15 @@ find_dispatch(Meta, Tuple, Extra, E) -> find_dispatch(Tuple, List) -> [Receiver || {Receiver, Set} <- List, is_element(Tuple, Set)]. -is_import(Meta) -> - case lists:keyfind(import, 1, Meta) of - {import, _} = Import -> +is_import(Meta, Arity) -> + case lists:keyfind(imports, 1, Meta) of + {imports, Imports} -> case lists:keyfind(context, 1, Meta) of - {context, _} -> Import; + {context, _} -> + case lists:keyfind(Arity, 2, Imports) of + {Receiver, Arity} -> {import, Receiver}; + false -> false + end; false -> false end; false -> false diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index 5539b0d59f..b18e9576df 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -66,7 +66,6 @@ Right 80 assoc_op_eol. %% => Nonassoc 90 capture_op_eol. %% & Right 100 match_op_eol. %% = Left 110 or_op_eol. %% ||, |||, or -% Left 120 xor_op_eol. %% ^^^ Left 130 and_op_eol. %% &&, &&&, and Left 140 comp_op_eol. %% ==, !=, =~, ===, !== Left 150 rel_op_eol. %% <, >, <=, >= diff --git a/lib/elixir/src/elixir_quote.erl b/lib/elixir/src/elixir_quote.erl index 9b32f67586..46af801ab4 100644 --- a/lib/elixir/src/elixir_quote.erl +++ b/lib/elixir/src/elixir_quote.erl @@ -290,7 +290,8 @@ do_quote({unquote, _Meta, [Expr]}, #elixir_quote{unquote=true}, _) -> %% Aliases -do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=true} = Q, E) when is_atom(H) and (H /= 'Elixir') -> +do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=true} = Q, E) + when is_atom(H), H /= 'Elixir' -> Annotation = case elixir_aliases:expand(Alias, E) of Atom when is_atom(Atom) -> Atom; @@ -301,12 +302,14 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t %% Vars -do_quote({Name, Meta, nil}, #elixir_quote{vars_hygiene=true, imports_hygiene=true} = Q, E) when is_atom(Name) -> - ImportMeta = import_meta(Meta, Name, 0, Q, E), - {'{}', [], [Name, meta(ImportMeta, Q), Q#elixir_quote.context]}; +do_quote({Name, Meta, nil}, #elixir_quote{vars_hygiene=true} = Q, E) + when is_atom(Name), is_list(Meta) -> + ImportMeta = if + Q#elixir_quote.imports_hygiene -> import_meta(Meta, Name, 0, Q, E); + true -> Meta + end, -do_quote({Name, Meta, nil}, #elixir_quote{vars_hygiene=true} = Q, _E) when is_atom(Name) -> - {'{}', [], [Name, meta(Meta, Q), Q#elixir_quote.context]}; + {'{}', [], [Name, meta(ImportMeta, Q), Q#elixir_quote.context]}; %% Unquote @@ -320,13 +323,26 @@ do_quote({{'.', Meta, [Left, unquote]}, _, [Expr]}, #elixir_quote{unquote=true} do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) -> - do_quote_fa('&', Meta, Args, F, A, Q, E); + NewMeta = + case elixir_dispatch:find_import(Meta, F, A, E) of + false -> + Meta; -do_quote({Name, Meta, Args}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name), is_list(Args) -> - do_quote_import(Meta, Name, length(Args), Args, Q, E); + Receiver -> + keystore(context, keystore(imports, Meta, [{Receiver, A}]), Q#elixir_quote.context) + end, + do_quote_tuple('&', NewMeta, Args, Q, E); -do_quote({Name, Meta, Context}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name), is_atom(Context) -> - do_quote_import(Meta, Name, 0, Context, Q, E); +do_quote({Name, Meta, ArgsOrContext}, #elixir_quote{imports_hygiene=true} = Q, E) + when is_atom(Name), is_list(Meta), is_list(ArgsOrContext) or is_atom(ArgsOrContext) -> + Arity = if + is_atom(ArgsOrContext) -> 0; + true -> length(ArgsOrContext) + end, + + ImportMeta = import_meta(Meta, Name, Arity, Q, E), + Annotated = annotate({Name, ImportMeta, ArgsOrContext}, Q#elixir_quote.context), + do_quote_tuple(Annotated, Q, E); %% Two-element tuples @@ -424,40 +440,24 @@ bad_escape(Arg) -> import_meta(Meta, Name, Arity, Q, E) -> case (keyfind(import, Meta) == false) andalso - elixir_dispatch:find_import(Meta, Name, Arity, E) of - false -> + elixir_dispatch:find_imports(Meta, Name, Arity, E) of + [] -> case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of {ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context); _ -> Meta end; - Receiver -> - keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver) + + Imports -> + keystore(imports, keystore(context, Meta, Q#elixir_quote.context), Imports) end. %% do_quote_* -do_quote_import(Meta, Name, Arity, ArgsOrAtom, Q, E) -> - ImportMeta = import_meta(Meta, Name, Arity, Q, E), - Annotated = annotate({Name, ImportMeta, ArgsOrAtom}, Q#elixir_quote.context), - do_quote_tuple(Annotated, Q, E). - do_quote_call(Left, Meta, Expr, Args, Q, E) -> All = [Left, {unquote, Meta, [Expr]}, Args, Q#elixir_quote.context], TAll = [do_quote(X, Q, E) || X <- All], {{'.', Meta, [elixir_quote, dot]}, Meta, [meta(Meta, Q) | TAll]}. -do_quote_fa(Target, Meta, Args, F, A, Q, E) -> - NewMeta = - case elixir_dispatch:find_import(Meta, F, A, E) of - false -> Meta; - Receiver -> - lists:keystore(context, 1, - lists:keystore(import, 1, Meta, {import, Receiver}), - {context, Q#elixir_quote.context} - ) - end, - do_quote_tuple(Target, NewMeta, Args, Q, E). - do_quote_tuple({Left, Meta, Right}, Q, E) -> do_quote_tuple(Left, Meta, Right, Q, E). diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index fb62e77f40..42bd147fd2 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -418,7 +418,7 @@ defmodule Kernel.ExpansionTest do test "in guards" do code = quote(do: fn pid when :erlang.==(pid, self) -> pid end) expanded_code = quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end) - assert clean_meta(expand(code), [:import, :context]) == expanded_code + assert clean_meta(expand(code), [:imports, :context]) == expanded_code message = ~r"cannot find or invoke local foo/1" @@ -1061,10 +1061,11 @@ defmodule Kernel.ExpansionTest do test "expands remotes" do assert expand(quote(do: &List.flatten/2)) == - quote(do: &:"Elixir.List".flatten/2) |> clean_meta([:import, :context, :no_parens]) + quote(do: &:"Elixir.List".flatten/2) + |> clean_meta([:imports, :context, :no_parens]) assert expand(quote(do: &Kernel.is_atom/1)) == - quote(do: &:erlang.is_atom/1) |> clean_meta([:import, :context, :no_parens]) + quote(do: &:erlang.is_atom/1) |> clean_meta([:imports, :context, :no_parens]) end test "expands macros" do diff --git a/lib/elixir/test/elixir/kernel/quote_test.exs b/lib/elixir/test/elixir/kernel/quote_test.exs index 44d8e881c4..c81aec22ed 100644 --- a/lib/elixir/test/elixir/kernel/quote_test.exs +++ b/lib/elixir/test/elixir/kernel/quote_test.exs @@ -631,6 +631,6 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do test "checks the context also for variables to zero-arity functions" do import BinaryUtils {:int32, meta, __MODULE__} = quote(do: int32) - assert meta[:import] == BinaryUtils + assert meta[:imports] == [{BinaryUtils, 0}] end end diff --git a/lib/elixir/test/elixir/protocol/consolidation_test.exs b/lib/elixir/test/elixir/protocol/consolidation_test.exs index 1a81b03fa4..6b3fad4d17 100644 --- a/lib/elixir/test/elixir/protocol/consolidation_test.exs +++ b/lib/elixir/test/elixir/protocol/consolidation_test.exs @@ -131,7 +131,10 @@ defmodule Protocol.ConsolidationTest do end test "consolidation errors on missing BEAM files" do - defprotocol(NoBeam, do: nil) + defprotocol NoBeam do + def example(arg) + end + assert Protocol.consolidate(String, []) == {:error, :not_a_protocol} assert Protocol.consolidate(NoBeam, []) == {:error, :no_beam_info} end