Skip to content

Commit a34f38e

Browse files
author
alexrp
committed
Only error on ambiguous imported function calls, not on the imports themselves. Fixes #788.
1 parent de06764 commit a34f38e

File tree

5 files changed

+52
-42
lines changed

5 files changed

+52
-42
lines changed

lib/elixir/lib/macro.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ defmodule Macro do
563563
end
564564

565565
expand = :elixir_dispatch.expand_import(line, { atom, length(args) }, args,
566-
env.module, env.function, env.requires, extra ++ env.macros, env)
566+
env.module, env.function, env.requires, extra ++ env.macros, env.functions, env)
567567
case expand do
568568
{ :ok, _, expanded } -> expanded
569569
{ :error, _ } -> original

lib/elixir/src/elixir_dispatch.erl

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
-export([default_macros/0, default_functions/0, default_requires/0,
66
dispatch_require/6, dispatch_import/5,
77
require_function/5, import_function/4,
8-
expand_import/8, expand_require/8, find_import/4,
8+
expand_import/9, expand_require/8, find_import/4,
99
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
1010
-include("elixir.hrl").
1111
-compile({parse_transform, elixir_transform}).
@@ -20,11 +20,11 @@ default_macros() ->
2020
default_requires() ->
2121
[ ?BUILTIN, 'Elixir.Kernel.Typespec' ].
2222

23-
find_import(_Meta, Name, Arity, S) ->
23+
find_import(Meta, Name, Arity, S) ->
2424
Tuple = { Name, Arity },
25-
case find_dispatch(Tuple, S#elixir_scope.functions) of
25+
case find_dispatch(Meta, S#elixir_scope.file, Tuple, S#elixir_scope.functions, S#elixir_scope.macros) of
2626
false ->
27-
case find_dispatch(Tuple, S#elixir_scope.macros) of
27+
case find_dispatch(Meta, S#elixir_scope.file, Tuple, S#elixir_scope.macros, S#elixir_scope.functions) of
2828
false -> false;
2929
Receiver -> Receiver
3030
end;
@@ -35,9 +35,9 @@ find_import(_Meta, Name, Arity, S) ->
3535

3636
import_function(Meta, Name, Arity, S) ->
3737
Tuple = { Name, Arity },
38-
case find_dispatch(Tuple, S#elixir_scope.functions) of
38+
case find_dispatch(Meta, S#elixir_scope.file, Tuple, S#elixir_scope.functions, S#elixir_scope.macros) of
3939
false ->
40-
case find_dispatch(Tuple, S#elixir_scope.macros) of
40+
case find_dispatch(Meta, S#elixir_scope.file, Tuple, S#elixir_scope.macros, S#elixir_scope.functions) of
4141
false -> { { 'fun', ?line(Meta), { function, Name, Arity } }, S };
4242
_ -> false
4343
end;
@@ -61,10 +61,10 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
6161
Arity = length(Args),
6262
Tuple = { Name, Arity },
6363

64-
case find_dispatch(Tuple, S#elixir_scope.functions) of
64+
case find_dispatch(Meta, S#elixir_scope.file, Tuple, S#elixir_scope.functions, S#elixir_scope.macros) of
6565
false ->
6666
case expand_import(Meta, Tuple, Args, Module, S#elixir_scope.function,
67-
S#elixir_scope.requires, S#elixir_scope.macros, S) of
67+
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.functions, S) of
6868
{ error, noexpansion } ->
6969
Callback();
7070
{ error, internal } ->
@@ -104,8 +104,8 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
104104

105105
%% Macros expansion
106106

107-
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, SEnv) ->
108-
case find_dispatch(Tuple, Macros) of
107+
expand_import(Meta, { Name, Arity } = Tuple, Args, Module, Function, Requires, Macros, Functions, SEnv) ->
108+
case find_dispatch(Meta, elixir_scope:filename(SEnv), Tuple, Macros, Functions) of
109109
false ->
110110
Fun = (Function /= Tuple) andalso
111111
elixir_def_local:macro_for(Tuple, true, Module),
@@ -199,13 +199,25 @@ merge_aliases(A1, A2) ->
199199
skip_requires(#elixir_scope{check_requires=false}) -> true;
200200
skip_requires(_) -> false.
201201

202-
find_dispatch(Tuple, [{ Name, Values }|T]) ->
203-
case is_element(Tuple, Values) of
204-
true -> Name;
205-
false -> find_dispatch(Tuple, T)
206-
end;
202+
find_dispatch(Meta, File, {Name, Arity} = Tuple, List1, List2) ->
203+
Matcher = fun(List) -> lists:filter(fun({_, Vals}) -> is_element(Tuple, Vals) end, List) end,
204+
Matches1 = Matcher(List1),
205+
206+
case length(Matches1) of
207+
0 -> false;
208+
1 ->
209+
Matches2 = Matcher(List2),
207210

208-
find_dispatch(_Tuple, []) -> false.
211+
case length(Matches2) of
212+
0 -> element(1, hd(Matches1));
213+
_ ->
214+
Err = {ambiguous_call, {element(1, hd(Matches1)), element(1, hd(Matches2)), Name, Arity}},
215+
elixir_errors:form_error(Meta, File, ?MODULE, Err)
216+
end;
217+
_ ->
218+
Err = {ambiguous_call, {element(1, hd(Matches1)), element(1, hd(tl(Matches1))), Name, Arity}},
219+
elixir_errors:form_error(Meta, File, ?MODULE, Err)
220+
end.
209221

210222
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
211223
[Info];
@@ -224,7 +236,11 @@ munge_stacktrace(_, [], _) ->
224236
format_error({ unrequired_module,{Receiver, Name, Arity, Required }}) ->
225237
String = string:join([elixir_errors:inspect(R) || R <- Required], ", "),
226238
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
227-
[elixir_errors:inspect(Receiver), Name, Arity, String]).
239+
[elixir_errors:inspect(Receiver), Name, Arity, String]);
240+
241+
format_error({ ambiguous_call,{Mod1, Mod2, Name, Arity }}) ->
242+
io_lib:format("function ~ts/~B imported from both ~ts and ~ts; call is ambiguous",
243+
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
228244

229245
%% INTROSPECTION
230246

@@ -398,4 +414,4 @@ in_erlang_macros() ->
398414
{'receive',1},
399415
{'try',1},
400416
{'xor',2}
401-
].
417+
].

lib/elixir/src/elixir_import.erl

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,6 @@ calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
100100
case Final of
101101
[] -> All;
102102
_ ->
103-
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
104-
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
105103
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
106104
[{ Key, Final }|All]
107105
end.
@@ -207,22 +205,6 @@ ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
207205

208206
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
209207

210-
%% Find conlicts in the given list of functions with the set of imports.
211-
%% Used internally to ensure a newly imported fun or macro does not
212-
%% conflict with an already imported set.
213-
214-
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
215-
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
216-
case Filtered of
217-
[{Name,Arity}|_] ->
218-
Tuple = { already_imported, { Key, Name, Arity } },
219-
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
220-
[] ->
221-
ensure_no_conflicts(Meta, File, Functions, T)
222-
end;
223-
224-
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
225-
226208
%% ERROR HANDLING
227209

228210
format_error({already_imported,{Receiver, Name, Arity}}) ->

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ defmodule Kernel.ErrorsTest do
108108
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
109109
end
110110

111-
test :erlang_function_conflict do
112-
assert "nofile:1: function exit/1 already imported from Kernel" ==
113-
format_rescue 'defmodule Foo do import Kernel.ErrorsTest.UnproperMacro, only: [exit: 1]\nend'
111+
test :function_import_conflict do
112+
assert "nofile:2: function exit/1 imported from both erlang and Kernel; call is ambiguous" ==
113+
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
114114
end
115115

116116
test :import_invalid_macro do
@@ -308,4 +308,4 @@ defmodule Kernel.ErrorsTest do
308308

309309
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
310310
end
311-
end
311+
end

lib/elixir/test/elixir/kernel/import_test.exs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,16 @@ defmodule Kernel.ImportMacrosTest do
109109
assert bor(0, 1) == 1
110110
assert bnot(0) == -1
111111
end
112-
end
112+
end
113+
114+
defmodule Kernel.AmbiguousImportTest do
115+
use ExUnit.Case, async: true
116+
117+
test :import_ambiguous do
118+
# Simply make sure that we can indeed import functions with
119+
# the same name and arity from different modules without the
120+
# import itself causing any errors.
121+
import List
122+
import String
123+
end
124+
end

0 commit comments

Comments
 (0)