Change import metadata to imports as a list

This is preparation to better ambiguity solving
of quoted expressions.
This commit is contained in:
José Valim
2022-05-08 17:00:09 +02:00
parent b3a8cb9aba
commit df64d5c6a1
7 changed files with 67 additions and 46 deletions
+1 -1
View File
@@ -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
+25 -7
View File
@@ -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
-1
View File
@@ -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. %% <, >, <=, >=
+32 -32
View File
@@ -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).
@@ -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
+1 -1
View File
@@ -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
@@ -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