Skip to content

Commit c47b731

Browse files
committed
Track all arities in imports inside quotes, closes #11651
1 parent fd1f1c6 commit c47b731

File tree

6 files changed

+103
-39
lines changed

6 files changed

+103
-39
lines changed

lib/elixir/lib/kernel/lexical_tracker.ex

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ defmodule Kernel.LexicalTracker do
5959
:gen_server.cast(pid, {:alias_dispatch, module})
6060
end
6161

62+
@doc false
63+
def import_quoted(pid, module, function, arities) when is_atom(module) do
64+
:gen_server.cast(pid, {:import_quoted, module, function, arities})
65+
end
66+
6267
@doc false
6368
def add_compile_env(pid, app, path, return) do
6469
:gen_server.cast(pid, {:compile_env, app, path, return})
@@ -170,6 +175,24 @@ defmodule Kernel.LexicalTracker do
170175
{:noreply, %{state | aliases: Map.delete(aliases, module)}}
171176
end
172177

178+
def handle_cast({:import_quoted, module, function, arities}, state) do
179+
%{imports: imports} = state
180+
181+
imports =
182+
case imports do
183+
%{^module => modules_and_fas} ->
184+
arities
185+
|> Enum.reduce(modules_and_fas, &Map.delete(&2, {function, &1}))
186+
|> Map.delete(module)
187+
|> then(&Map.put(imports, module, &1))
188+
189+
%{} ->
190+
imports
191+
end
192+
193+
{:noreply, %{state | imports: imports}}
194+
end
195+
173196
def handle_cast({:set_file, file}, state) do
174197
{:noreply, %{state | file: file}}
175198
end

lib/elixir/src/elixir_dispatch.erl

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
require_function/5, import_function/4,
77
expand_import/7, expand_require/6,
88
default_functions/0, default_macros/0, default_requires/0,
9-
find_import/4, find_imports/4, format_error/1]).
9+
find_import/4, find_imports/3, format_error/1]).
1010
-include("elixir.hrl").
1111
-import(ordsets, [is_element/2]).
1212
-define(kernel, 'Elixir.Kernel').
@@ -25,7 +25,7 @@ default_requires() ->
2525
find_import(Meta, Name, Arity, E) ->
2626
Tuple = {Name, Arity},
2727

28-
case find_dispatch(Meta, Tuple, [], E) of
28+
case find_import_by_name_arity(Meta, Tuple, [], E) of
2929
{function, Receiver} ->
3030
elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E),
3131
Receiver;
@@ -36,25 +36,37 @@ find_import(Meta, Name, Arity, E) ->
3636
false
3737
end.
3838

39-
find_imports(Meta, Name, Arity, E) ->
40-
Tuple = {Name, Arity},
39+
find_imports(Meta, Name, E) ->
40+
Funs = ?key(E, functions),
41+
Macs = ?key(E, macros),
4142

42-
case find_dispatch(Meta, Tuple, [], E) of
43-
{function, Receiver} ->
44-
elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E),
45-
[{Receiver, Arity}];
46-
{macro, Receiver} ->
47-
elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E),
48-
[{Receiver, Arity}];
49-
_ ->
50-
[]
51-
end.
43+
Acc0 = #{},
44+
Acc1 = find_imports_by_name(Funs, Acc0, Name, Meta, E),
45+
Acc2 = find_imports_by_name(Macs, Acc1, Name, Meta, E),
46+
47+
Imports = lists:sort(maps:to_list(Acc2)),
48+
trace_import_quoted(Imports, Meta, Name, E),
49+
Imports.
50+
51+
trace_import_quoted([{Arity, Mod} | Imports], Meta, Name, E) ->
52+
{Rest, Arities} = collect_trace_import_quoted(Imports, Mod, [], [Arity]),
53+
elixir_env:trace({imported_quoted, Meta, Mod, Name, Arities}, E),
54+
trace_import_quoted(Rest, Meta, Name, E);
55+
trace_import_quoted([], _Meta, _Name, _E) ->
56+
ok.
57+
58+
collect_trace_import_quoted([{Arity, Mod} | Imports], Mod, Acc, Arities) ->
59+
collect_trace_import_quoted(Imports, Mod, Acc, [Arity | Arities]);
60+
collect_trace_import_quoted([Import | Imports], Mod, Acc, Arities) ->
61+
collect_trace_import_quoted(Imports, Mod, [Import | Acc], Arities);
62+
collect_trace_import_quoted([], _Mod, Acc, Arities) ->
63+
{lists:reverse(Acc), lists:reverse(Arities)}.
5264

5365
%% Function retrieval
5466

5567
import_function(Meta, Name, Arity, E) ->
5668
Tuple = {Name, Arity},
57-
case find_dispatch(Meta, Tuple, [], E) of
69+
case find_import_by_name_arity(Meta, Tuple, [], E) of
5870
{function, Receiver} ->
5971
elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E),
6072
elixir_locals:record_import(Tuple, Receiver, ?key(E, module), ?key(E, function)),
@@ -134,15 +146,14 @@ dispatch_require(_Meta, Receiver, Name, Args, _S, _E, Callback) ->
134146
expand_import(Meta, {Name, Arity} = Tuple, Args, S, E, Extra, External) ->
135147
Module = ?key(E, module),
136148
Function = ?key(E, function),
137-
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
149+
Dispatch = find_import_by_name_arity(Meta, Tuple, Extra, E),
138150

139151
case Dispatch of
140152
{import, _} ->
141153
do_expand_import(Meta, Tuple, Args, Module, S, E, Dispatch);
142154
_ ->
143155
AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
144-
Local = AllowLocals andalso
145-
elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E),
156+
Local = AllowLocals andalso elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E),
146157

147158
case Dispatch of
148159
%% There is a local and an import. This is a conflict unless
@@ -246,15 +257,35 @@ caller(Line, E) ->
246257
required(Meta) ->
247258
lists:keyfind(required, 1, Meta) == {required, true}.
248259

249-
find_dispatch(Meta, {_Name, Arity} = Tuple, Extra, E) ->
260+
find_imports_by_name([{Mod, Imports} | ModImports], Acc, Name, Meta, E) ->
261+
NewAcc = find_imports_by_name(Name, Imports, Acc, Mod, Meta, E),
262+
find_imports_by_name(ModImports, NewAcc, Name, Meta, E);
263+
find_imports_by_name([], Acc, _Name, _Meta, _E) ->
264+
Acc.
265+
266+
find_imports_by_name(Name, [{Name, Arity} | Imports], Acc, Mod, Meta, E) ->
267+
case Acc of
268+
#{Arity := OtherMod} ->
269+
Error = {ambiguous_call, {Mod, OtherMod, Name, Arity}},
270+
elixir_errors:form_error(Meta, E, ?MODULE, Error);
271+
272+
#{} ->
273+
find_imports_by_name(Name, Imports, Acc#{Arity => Mod}, Mod, Meta, E)
274+
end;
275+
find_imports_by_name(Name, [{ImportName, _} | Imports], Acc, Mod, Meta, E) when Name > ImportName ->
276+
find_imports_by_name(Name, Imports, Acc, Mod, Meta, E);
277+
find_imports_by_name(_Name, _Imports, Acc, _Mod, _Meta, _E) ->
278+
Acc.
279+
280+
find_import_by_name_arity(Meta, {_Name, Arity} = Tuple, Extra, E) ->
250281
case is_import(Meta, Arity) of
251282
{import, _} = Import ->
252283
Import;
253284
false ->
254285
Funs = ?key(E, functions),
255286
Macs = Extra ++ ?key(E, macros),
256-
FunMatch = find_dispatch(Tuple, Funs),
257-
MacMatch = find_dispatch(Tuple, Macs),
287+
FunMatch = find_import_by_name_arity(Tuple, Funs),
288+
MacMatch = find_import_by_name_arity(Tuple, Macs),
258289

259290
case {FunMatch, MacMatch} of
260291
{[], [Receiver]} -> {macro, Receiver};
@@ -268,16 +299,16 @@ find_dispatch(Meta, {_Name, Arity} = Tuple, Extra, E) ->
268299
end
269300
end.
270301

271-
find_dispatch(Tuple, List) ->
302+
find_import_by_name_arity(Tuple, List) ->
272303
[Receiver || {Receiver, Set} <- List, is_element(Tuple, Set)].
273304

274305
is_import(Meta, Arity) ->
275306
case lists:keyfind(imports, 1, Meta) of
276307
{imports, Imports} ->
277308
case lists:keyfind(context, 1, Meta) of
278309
{context, _} ->
279-
case lists:keyfind(Arity, 2, Imports) of
280-
{Receiver, Arity} -> {import, Receiver};
310+
case lists:keyfind(Arity, 1, Imports) of
311+
{Arity, Receiver} -> {import, Receiver};
281312
false -> false
282313
end;
283314
false -> false

lib/elixir/src/elixir_lexical.erl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ trace({imported_function, _Meta, Module, Function, Arity}, #{lexical_tracker :=
7070
trace({imported_macro, _Meta, Module, Function, Arity}, #{lexical_tracker := Pid}) ->
7171
?tracker:import_dispatch(Pid, Module, {Function, Arity}, compile),
7272
ok;
73+
trace({imported_quoted, _Meta, Module, Function, Arities}, #{lexical_tracker := Pid}) ->
74+
?tracker:import_quoted(Pid, Module, Function, Arities),
75+
ok;
7376
trace({compile_env, App, Path, Return}, #{lexical_tracker := Pid}) ->
7477
?tracker:add_compile_env(Pid, App, Path, Return),
7578
ok;

lib/elixir/src/elixir_quote.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
330330
Meta;
331331

332332
Receiver ->
333-
keystore(context, keystore(imports, Meta, [{Receiver, A}]), Q#elixir_quote.context)
333+
keystore(context, keystore(imports, Meta, [{A, Receiver}]), Q#elixir_quote.context)
334334
end,
335335
do_quote_tuple('&', NewMeta, Args, Q, E);
336336

@@ -380,7 +380,7 @@ do_quote(Other, _, _) ->
380380

381381
import_meta(Meta, Name, Arity, Q, E) ->
382382
case (keyfind(import, Meta) == false) andalso
383-
elixir_dispatch:find_imports(Meta, Name, Arity, E) of
383+
elixir_dispatch:find_imports(Meta, Name, E) of
384384
[] ->
385385
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
386386
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,7 +2306,7 @@ defmodule Kernel.ExpansionTest do
23062306
end
23072307

23082308
test "expands size * unit" do
2309-
import Kernel, except: [-: 2]
2309+
import Kernel, except: [-: 1, -: 2]
23102310
import Kernel.ExpansionTarget
23112311

23122312
assert expand(quote(do: <<x::13>>)) |> clean_meta([:alignment]) ==
@@ -2338,7 +2338,7 @@ defmodule Kernel.ExpansionTest do
23382338
end
23392339

23402340
test "expands binary/bitstring specifiers" do
2341-
import Kernel, except: [-: 2]
2341+
import Kernel, except: [-: 1, -: 2]
23422342

23432343
assert expand(quote(do: <<x::binary>>)) |> clean_meta([:alignment]) ==
23442344
quote(do: <<x()::binary()>>)
@@ -2363,7 +2363,7 @@ defmodule Kernel.ExpansionTest do
23632363
end
23642364

23652365
test "expands utf* specifiers" do
2366-
import Kernel, except: [-: 2]
2366+
import Kernel, except: [-: 1, -: 2]
23672367

23682368
assert expand(quote(do: <<x::utf8>>)) |> clean_meta([:alignment]) ==
23692369
quote(do: <<x()::utf8()>>)
@@ -2386,7 +2386,7 @@ defmodule Kernel.ExpansionTest do
23862386
end
23872387

23882388
test "expands numbers specifiers" do
2389-
import Kernel, except: [-: 2]
2389+
import Kernel, except: [-: 1, -: 2]
23902390

23912391
assert expand(quote(do: <<x::integer>>)) |> clean_meta([:alignment]) ==
23922392
quote(do: <<x()::integer()>>)
@@ -2412,7 +2412,7 @@ defmodule Kernel.ExpansionTest do
24122412
end
24132413

24142414
test "expands macro specifiers" do
2415-
import Kernel, except: [-: 2]
2415+
import Kernel, except: [-: 1, -: 2]
24162416
import Kernel.ExpansionTarget
24172417

24182418
assert expand(quote(do: <<x::seventeen>>)) |> clean_meta([:alignment]) ==
@@ -2424,7 +2424,7 @@ defmodule Kernel.ExpansionTest do
24242424
end
24252425

24262426
test "expands macro in args" do
2427-
import Kernel, except: [-: 2]
2427+
import Kernel, except: [-: 1, -: 2]
24282428

24292429
before_expansion =
24302430
quote do
@@ -2442,7 +2442,7 @@ defmodule Kernel.ExpansionTest do
24422442
end
24432443

24442444
test "supports dynamic size" do
2445-
import Kernel, except: [-: 2]
2445+
import Kernel, except: [-: 1, -: 2]
24462446

24472447
before_expansion =
24482448
quote do
@@ -2471,7 +2471,7 @@ defmodule Kernel.ExpansionTest do
24712471
end
24722472

24732473
test "merges bitstrings" do
2474-
import Kernel, except: [-: 2]
2474+
import Kernel, except: [-: 1, -: 2]
24752475

24762476
assert expand(quote(do: <<x, <<y::signed-native>>, z>>)) |> clean_meta([:alignment]) ==
24772477
quote(do: <<x()::integer(), y()::integer()-native()-signed(), z()::integer()>>)
@@ -2482,7 +2482,7 @@ defmodule Kernel.ExpansionTest do
24822482
end
24832483

24842484
test "merges binaries" do
2485-
import Kernel, except: [-: 2]
2485+
import Kernel, except: [-: 1, -: 2]
24862486

24872487
assert expand(quote(do: "foo" <> x)) |> clean_meta([:alignment]) ==
24882488
quote(do: <<"foo"::binary(), x()::binary()>>)
@@ -2496,7 +2496,7 @@ defmodule Kernel.ExpansionTest do
24962496
end
24972497

24982498
test "guard expressions on size" do
2499-
import Kernel, except: [-: 2, +: 2, length: 1]
2499+
import Kernel, except: [-: 1, -: 2, +: 1, +: 2, length: 1]
25002500

25012501
# Arithmetic operations with literals and variables are valid expressions
25022502
# for bitstring size in OTP 23+
@@ -2524,7 +2524,7 @@ defmodule Kernel.ExpansionTest do
25242524
end
25252525

25262526
test "map lookup on size" do
2527-
import Kernel, except: [-: 2]
2527+
import Kernel, except: [-: 1, -: 2]
25282528

25292529
before_expansion =
25302530
quote do
@@ -2572,7 +2572,7 @@ defmodule Kernel.ExpansionTest do
25722572
# TODO: Simplify when we require Erlang/OTP 24
25732573
if System.otp_release() >= "24" do
25742574
test "16-bit floats" do
2575-
import Kernel, except: [-: 2]
2575+
import Kernel, except: [-: 1, -: 2]
25762576

25772577
assert expand(quote(do: <<12.3::float-16>>)) |> clean_meta([:alignment]) ==
25782578
quote(do: <<12.3::float()-size(16)>>)

lib/elixir/test/elixir/kernel/quote_test.exs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,12 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
566566
end
567567
end
568568

569+
defmacrop get_list_length_with_pipe do
570+
quote do
571+
'hello' |> length()
572+
end
573+
end
574+
569575
defmacrop get_list_length_with_partial do
570576
quote do
571577
(&length(&1)).('hello')
@@ -581,6 +587,7 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
581587
test "expand imports" do
582588
import Kernel, except: [length: 1]
583589
assert get_list_length() == 5
590+
assert get_list_length_with_pipe() == 5
584591
assert get_list_length_with_partial() == 5
585592
assert get_list_length_with_function() == 5
586593
end
@@ -631,6 +638,6 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
631638
test "checks the context also for variables to zero-arity functions" do
632639
import BinaryUtils
633640
{:int32, meta, __MODULE__} = quote(do: int32)
634-
assert meta[:imports] == [{BinaryUtils, 0}]
641+
assert meta[:imports] == [{0, BinaryUtils}]
635642
end
636643
end

0 commit comments

Comments
 (0)