diff --git a/lib/elixir/lib/kernel/lexical_tracker.ex b/lib/elixir/lib/kernel/lexical_tracker.ex index 4f7864c168..7ca3663bb0 100644 --- a/lib/elixir/lib/kernel/lexical_tracker.ex +++ b/lib/elixir/lib/kernel/lexical_tracker.ex @@ -56,8 +56,8 @@ defmodule Kernel.LexicalTracker do end @doc false - def add_import(pid, module, line, warn) do - :gen_server.cast(pid, {:add_import, module, line, warn}) + def add_import(pid, module_or_mfa, line, warn) do + :gen_server.cast(pid, {:add_import, module_or_mfa, line, warn}) end @doc false @@ -71,8 +71,8 @@ defmodule Kernel.LexicalTracker do end @doc false - def import_dispatch(pid, module) do - :gen_server.cast(pid, {:import_dispatch, module}) + def import_dispatch(pid, {module, function, arity}) do + :gen_server.cast(pid, {:import_dispatch, {module, function, arity}}) end @doc false @@ -116,8 +116,9 @@ defmodule Kernel.LexicalTracker do {:noreply, {d, dest}} end - def handle_cast({:import_dispatch, module}, {d, dest}) do + def handle_cast({:import_dispatch, {module, function, arity}}, {d, dest}) do add_dispatch(d, module, :import) + add_dispatch(d, {module, function, arity}, :import) # Always compile time because we depend # on the module at compile time add_compile(d, module, :compile) @@ -129,11 +130,19 @@ defmodule Kernel.LexicalTracker do {:noreply, {d, dest}} end - def handle_cast({:add_import, module, line, warn}, {d, dest}) do + def handle_cast({:add_import, module, line, warn}, {d, dest}) when is_atom(module) do + :ets.match_delete(d, {{:import, {module, :"_", :"_"}}, :"_"}) + add_directive(d, module, line, warn, :import) {:noreply, {d, dest}} end + def handle_cast({:add_import, {module, function, arity}, line, warn}, {d, dest}) do + add_directive(d, module, line, warn, :import) + add_directive(d, {module, function, arity}, line, warn, :import) + {:noreply, {d, dest}} + end + def handle_cast({:add_alias, module, line, warn}, {d, dest}) do add_directive(d, module, line, warn, :alias) {:noreply, {d, dest}} @@ -171,8 +180,8 @@ defmodule Kernel.LexicalTracker do defp add_compile(d, module, :runtime), do: :ets.insert_new(d, {{:mode, module}, :runtime}) defp add_compile(d, module, :compile), do: :ets.insert(d, {{:mode, module}, :compile}) - defp add_directive(d, module, line, warn, tag) do + defp add_directive(d, m_or_mfa, line, warn, tag) do marker = if warn, do: line, else: true - :ets.insert(d, {{tag, module}, marker}) + :ets.insert(d, {{tag, m_or_mfa}, marker}) end end diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index 0fc5c6ada5..b409879446 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -26,11 +26,11 @@ find_import(Meta, Name, Arity, E) -> case find_dispatch(Meta, Tuple, [], E) of {function, Receiver} -> - elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), + elixir_lexical:record_import({Receiver, Name, Arity}, ?m(E, lexical_tracker)), %% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)), Receiver; {macro, Receiver} -> - elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), + elixir_lexical:record_import({Receiver, Name, Arity}, ?m(E, lexical_tracker)), %% elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)), Receiver; _ -> @@ -43,7 +43,7 @@ import_function(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, case find_dispatch(Meta, Tuple, [], E) of {function, Receiver} -> - elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), + elixir_lexical:record_import({Receiver, Name, Arity}, ?m(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, ?m(E, module), ?m(E, function)), remote_function(Meta, Receiver, Name, Arity, E); {macro, _Receiver} -> @@ -137,12 +137,12 @@ expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) -> do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) -> case Result of {function, Receiver} -> - elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), + elixir_lexical:record_import({Receiver, Name, Arity}, ?m(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)), {ok, Receiver, Name, Args}; {macro, Receiver} -> check_deprecation(Meta, Receiver, Name, Arity, E), - elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)), + elixir_lexical:record_import({Receiver, Name, Arity}, ?m(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)), {ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)}; {import, Receiver} -> diff --git a/lib/elixir/src/elixir_import.erl b/lib/elixir/src/elixir_import.erl index 369ec647ad..036889ec3c 100644 --- a/lib/elixir/src/elixir_import.erl +++ b/lib/elixir/src/elixir_import.erl @@ -47,7 +47,14 @@ record_warn(Meta, Ref, Opts, Added, E) -> {warn, true} -> true; false -> not lists:keymember(context, 1, Meta) end, - elixir_lexical:record_import(Ref, ?line(Meta), Added and Warn, ?m(E, lexical_tracker)). + + case keyfind(only, Opts) of + {only, List} when is_list(List) -> + [elixir_lexical:record_import({Ref, Name, Arity}, ?line(Meta), Added and Warn, ?m(E, lexical_tracker)) || {Name, Arity} <- List ]; + + _ -> + elixir_lexical:record_import(Ref, ?line(Meta), Added and Warn, ?m(E, lexical_tracker)) + end. %% Calculates the imports based on only and except diff --git a/lib/elixir/src/elixir_lexical.erl b/lib/elixir/src/elixir_lexical.erl index eeb881435f..d32ab89010 100644 --- a/lib/elixir/src/elixir_lexical.erl +++ b/lib/elixir/src/elixir_lexical.erl @@ -34,14 +34,17 @@ dest(Pid) -> ?tracker:dest(Pid). record_alias(Module, Line, Warn, Ref) -> if_tracker(Ref, fun(Pid) -> ?tracker:add_alias(Pid, Module, Line, Warn), ok end). +record_import({Module, Function, Arity}, Line, Warn, Ref) -> + if_tracker(Ref, fun(Pid) -> ?tracker:add_import(Pid, {Module, Function, Arity}, Line, Warn), ok end); + record_import(Module, Line, Warn, Ref) -> if_tracker(Ref, fun(Pid) -> ?tracker:add_import(Pid, Module, Line, Warn), ok end). record_alias(Module, Ref) -> if_tracker(Ref, fun(Pid) -> ?tracker:alias_dispatch(Pid, Module), ok end). -record_import(Module, Ref) -> - if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, Module), ok end). +record_import({Module, Function, Arity}, Ref) -> + if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, {Module, Function, Arity}), ok end). record_remote(Module, Function, Ref) -> if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module, mode(Function)), ok end). @@ -57,9 +60,14 @@ if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid). %% ERROR HANDLING warn_unused_imports(File, Pid) -> + {ModuleImports, MFAImports} = + lists:partition(fun({M, _}) -> is_atom(M) end, ?tracker:collect_unused_imports(Pid)), + Modules = [M || {M, _L} <- ModuleImports], + MFAImportsFiltered = [T || {{M, _, _}, _} = T <- MFAImports, not lists:member(M, Modules)], + [begin elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_import, M}) - end || {M, L} <- ?tracker:collect_unused_imports(Pid)], + end || {M, L} <- ModuleImports ++ MFAImportsFiltered], ok. warn_unused_aliases(File, Pid) -> @@ -70,5 +78,7 @@ warn_unused_aliases(File, Pid) -> format_error({unused_alias, Module}) -> io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]); +format_error({unused_import, {Module, Function, Arity}}) -> + io_lib:format("unused import ~ts.~ts/~w", [elixir_aliases:inspect(Module), Function, Arity]); format_error({unused_import, Module}) -> io_lib:format("unused import ~ts", [elixir_aliases:inspect(Module)]). diff --git a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs index 72ead14672..ffa887f6b1 100644 --- a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs +++ b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs @@ -25,9 +25,15 @@ defmodule Kernel.LexicalTrackerTest do assert D.remotes(config[:pid]) == {[String], []} end - test "can add imports", config do + test "can add module imports", config do D.add_import(config[:pid], String, 1, true) - D.import_dispatch(config[:pid], String) + D.import_dispatch(config[:pid], {String, :upcase, 1}) + assert D.remotes(config[:pid]) == {[String], []} + end + + test "can add {module, function, arity} imports", config do + D.add_import(config[:pid], {String, :upcase, 1}, 1, true) + D.import_dispatch(config[:pid], {String, :upcase, 1}) assert D.remotes(config[:pid]) == {[String], []} end @@ -37,14 +43,33 @@ defmodule Kernel.LexicalTrackerTest do assert D.remotes(config[:pid]) == {[], []} end - test "unused imports", config do + test "unused module imports", config do D.add_import(config[:pid], String, 1, true) assert D.collect_unused_imports(config[:pid]) == [{String, 1}] end - test "used imports are not unused", config do + test "used module imports are not unused", config do D.add_import(config[:pid], String, 1, true) - D.import_dispatch(config[:pid], String) + D.import_dispatch(config[:pid], {String, :upcase, 1}) + assert D.collect_unused_imports(config[:pid]) == [] + end + + test "unused {module, function, arity} imports", config do + D.add_import(config[:pid], {String, :upcase, 1}, 1, true) + assert D.collect_unused_imports(config[:pid]) == [{String, 1}, {{String, :upcase, 1}, 1}] + end + + test "used {module, function, arity} imports are not unused", config do + D.add_import(config[:pid], {String, :upcase, 1}, 1, true) + D.add_import(config[:pid], {String, :downcase, 1}, 1, true) + D.import_dispatch(config[:pid], {String, :upcase, 1}) + assert D.collect_unused_imports(config[:pid]) == [{{String, :downcase, 1}, 1}] + end + + test "overwriting {module, function, arity} import with module import", config do + D.add_import(config[:pid], {String, :upcase, 1}, 1, true) + D.add_import(config[:pid], String, 1, true) + D.import_dispatch(config[:pid], {String, :downcase, 1}) assert D.collect_unused_imports(config[:pid]) == [] end diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index 69da0da16a..a926701d47 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -254,7 +254,7 @@ defmodule Kernel.WarningTest do assert capture_err(fn -> Code.compile_string """ defmodule Sample do - import :lists, only: [flatten: 1] + import :lists def a, do: nil end """ @@ -262,13 +262,40 @@ defmodule Kernel.WarningTest do assert capture_err(fn -> Code.compile_string """ - import :lists, only: [flatten: 1] + import :lists """ end) =~ "warning: unused import :lists" after purge Sample end + test "unused import of one of the functions in :only" do + assert capture_err(fn -> + Code.compile_string """ + defmodule Sample do + import String, only: [upcase: 1, downcase: 1, strip: 1] + def a, do: upcase("hello") + end + """ + end) == "nofile:2: warning: unused import String.downcase/1\n" <> + "nofile:2: warning: unused import String.strip/1\n" + after + purge Sample + end + + test "unused import of any of the functions in :only" do + assert capture_err(fn -> + Code.compile_string """ + defmodule Sample do + import String, only: [upcase: 1, downcase: 1] + def a, do: nil + end + """ + end) == "nofile:2: warning: unused import String\n" + after + purge Sample + end + test "unused alias" do assert capture_err(fn -> Code.compile_string """ diff --git a/lib/mix/lib/mix/dep/fetcher.ex b/lib/mix/lib/mix/dep/fetcher.ex index 64175b937e..c13593c976 100644 --- a/lib/mix/lib/mix/dep/fetcher.ex +++ b/lib/mix/lib/mix/dep/fetcher.ex @@ -7,7 +7,7 @@ defmodule Mix.Dep.Fetcher do @moduledoc false - import Mix.Dep, only: [format_dep: 1, check_lock: 1, available?: 1, ok?: 1] + import Mix.Dep, only: [format_dep: 1, check_lock: 1, available?: 1] @doc """ Fetches all dependencies. diff --git a/lib/mix/lib/mix/tasks/deps.compile.ex b/lib/mix/lib/mix/tasks/deps.compile.ex index 05361778d6..fea90b7e4f 100644 --- a/lib/mix/lib/mix/tasks/deps.compile.ex +++ b/lib/mix/lib/mix/tasks/deps.compile.ex @@ -25,7 +25,7 @@ defmodule Mix.Tasks.Deps.Compile do """ import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2, - format_dep: 1, make?: 1, mix?: 1] + make?: 1, mix?: 1] @spec run(OptionParser.argv) :: :ok def run(args) do diff --git a/lib/mix/lib/mix/tasks/new.ex b/lib/mix/lib/mix/tasks/new.ex index 0bfaea30c7..2009d7aa2a 100644 --- a/lib/mix/lib/mix/tasks/new.ex +++ b/lib/mix/lib/mix/tasks/new.ex @@ -2,7 +2,7 @@ defmodule Mix.Tasks.New do use Mix.Task import Mix.Generator - import Mix.Utils, only: [camelize: 1, underscore: 1] + import Mix.Utils, only: [camelize: 1] @shortdoc "Creates a new Elixir project"