diff options
author | Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com> | 2019-07-16 17:10:43 +0200 |
---|---|---|
committer | Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com> | 2019-07-16 17:10:43 +0200 |
commit | ba8ed0c037a94f4094c37a6b6ca7fbd49d400a6c (patch) | |
tree | 759f22394ecfd47f1c041fbee2d10a0d183c4177 | |
parent | 4ad5749d00b2e607a976feb1445b1635ff99f1dc (diff) | |
download | elixir-emj/lt-remove-dispatches.tar.gz |
Remove dispatch tracking in LexicalTrackeremj/lt-remove-dispatches
-rw-r--r-- | lib/elixir/lib/kernel/lexical_tracker.ex | 64 | ||||
-rw-r--r-- | lib/elixir/src/elixir_dispatch.erl | 12 | ||||
-rw-r--r-- | lib/elixir/src/elixir_expand.erl | 6 | ||||
-rw-r--r-- | lib/elixir/src/elixir_lexical.erl | 19 | ||||
-rw-r--r-- | lib/elixir/src/elixir_map.erl | 2 | ||||
-rw-r--r-- | lib/elixir/test/elixir/kernel/lexical_tracker_test.exs | 220 | ||||
-rw-r--r-- | lib/mix/lib/mix/compilers/elixir.ex | 22 | ||||
-rw-r--r-- | lib/mix/lib/mix/tasks/xref.ex | 142 | ||||
-rw-r--r-- | lib/mix/test/mix/tasks/xref_test.exs | 258 |
9 files changed, 84 insertions, 661 deletions
diff --git a/lib/elixir/lib/kernel/lexical_tracker.ex b/lib/elixir/lib/kernel/lexical_tracker.ex index 24cb2bee1..6378fc5e7 100644 --- a/lib/elixir/lib/kernel/lexical_tracker.ex +++ b/lib/elixir/lib/kernel/lexical_tracker.ex @@ -16,13 +16,6 @@ defmodule Kernel.LexicalTracker do :gen_server.call(pid, :remote_references, @timeout) end - @doc """ - Returns all remote dispatches in this lexical scope. - """ - def remote_dispatches(pid) do - :gen_server.call(pid, :remote_dispatches, @timeout) - end - # Internal API # Starts the tracker and returns its PID. @@ -47,23 +40,18 @@ defmodule Kernel.LexicalTracker do end @doc false - def remote_reference(pid, module, mode) when is_atom(module) do - :gen_server.cast(pid, {:remote_reference, module, mode}) + def remote_dispatch(pid, module, mode) when is_atom(module) do + :gen_server.cast(pid, {:remote_dispatch, module, mode}) end @doc false - def remote_dispatch(pid, module, fa, line, mode) when is_atom(module) do - :gen_server.cast(pid, {:remote_dispatch, module, fa, line, mode}) + def remote_struct(pid, module) when is_atom(module) do + :gen_server.cast(pid, {:remote_struct, module}) end @doc false - def remote_struct(pid, module, line) when is_atom(module) do - :gen_server.cast(pid, {:remote_struct, module, line}) - end - - @doc false - def import_dispatch(pid, module, fa, line, mode) when is_atom(module) do - :gen_server.cast(pid, {:import_dispatch, module, fa, line, mode}) + def import_dispatch(pid, module, fa) when is_atom(module) do + :gen_server.cast(pid, {:import_dispatch, module, fa}) end @doc false @@ -113,8 +101,6 @@ defmodule Kernel.LexicalTracker do state = %{ directives: %{}, references: %{}, - compile: %{}, - runtime: %{}, structs: %{}, cache: %{}, file: nil @@ -138,10 +124,6 @@ defmodule Kernel.LexicalTracker do {:reply, {compile, :maps.keys(state.structs), runtime}, state} end - def handle_call(:remote_dispatches, _from, state) do - {:reply, {state.compile, state.runtime}, state} - end - def handle_call({:read_cache, key}, _from, %{cache: cache} = state) do {:reply, :maps.get(key, cache), state} end @@ -154,27 +136,18 @@ defmodule Kernel.LexicalTracker do {:noreply, %{state | cache: :maps.put(key, value, cache)}} end - def handle_cast({:remote_reference, module, mode}, state) do - {:noreply, %{state | references: add_reference(state.references, module, mode)}} - end - - def handle_cast({:remote_struct, module, line}, state) do - state = add_remote_dispatch(state, module, {:__struct__, 0}, line, :compile) + def handle_cast({:remote_struct, module}, state) do structs = :maps.put(module, true, state.structs) {:noreply, %{state | structs: structs}} end - def handle_cast({:remote_dispatch, module, fa, line, mode}, state) do + def handle_cast({:remote_dispatch, module, mode}, state) do references = add_reference(state.references, module, mode) - state = add_remote_dispatch(state, module, fa, line, mode) {:noreply, %{state | references: references}} end - def handle_cast({:import_dispatch, module, {function, arity} = fa, line, mode}, state) do - state = - state - |> add_import_dispatch(module, function, arity) - |> add_remote_dispatch(module, fa, line, mode) + def handle_cast({:import_dispatch, module, {function, arity}}, state) do + state = add_import_dispatch(state, module, function, arity) {:noreply, state} end @@ -245,16 +218,6 @@ defmodule Kernel.LexicalTracker do end end - defp add_remote_dispatch(state, module, fa, line, mode) when is_atom(module) do - location = location(state.file, line) - - map_update(mode, %{module => %{fa => [location]}}, state, fn mode_dispatches -> - map_update(module, %{fa => [location]}, mode_dispatches, fn module_dispatches -> - map_update(fa, [location], module_dispatches, &[location | List.delete(&1, location)]) - end) - end) - end - defp location(nil, line), do: line defp location(file, line), do: {file, line} @@ -281,11 +244,4 @@ defmodule Kernel.LexicalTracker do defp add_dispatch(directives, module_or_mfa, tag) do :maps.put({tag, module_or_mfa}, true, directives) end - - defp map_update(key, initial, map, fun) do - case :maps.find(key, map) do - {:ok, val} -> :maps.put(key, fun.(val), map) - :error -> :maps.put(key, initial, map) - end - end end diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index b15e6dddd..96d8411e7 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -26,10 +26,10 @@ find_import(Meta, Name, Arity, E) -> case find_dispatch(Meta, Tuple, [], E) of {function, Receiver} -> - elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, function), ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, lexical_tracker)), Receiver; {macro, Receiver} -> - elixir_lexical:record_import(Receiver, Name, Arity, nil, ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, lexical_tracker)), Receiver; _ -> false @@ -41,7 +41,7 @@ import_function(Meta, Name, Arity, E) -> Tuple = {Name, Arity}, case find_dispatch(Meta, Tuple, [], E) of {function, Receiver} -> - elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, function), ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, ?key(E, module), ?key(E, function)), remote_function(Meta, Receiver, Name, Arity, E); {macro, _Receiver} -> @@ -139,12 +139,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, Name, Arity, ?key(E, function), ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)), {ok, Receiver, Name, Args}; {macro, Receiver} -> check_deprecated(Meta, Receiver, Name, Arity, E), - elixir_lexical:record_import(Receiver, Name, Arity, nil, ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_import(Receiver, Name, Arity, ?key(E, lexical_tracker)), elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)), {ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)}; {import, Receiver} -> @@ -167,7 +167,7 @@ expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) -> case is_element(Tuple, get_macros(Receiver, Required)) of true when Required -> - elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_remote(Receiver, nil, ?key(E, lexical_tracker)), {ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)}; true -> Info = {unrequired_module, {Receiver, Name, length(Args)}}, diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl index adea4a684..5470603fc 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -526,7 +526,7 @@ expand_fn_capture(Meta, Arg, E) -> case elixir_fn:capture(Meta, Arg, E) of {{remote, Remote, Fun, Arity}, EE} -> is_atom(Remote) andalso - elixir_lexical:record_remote(Remote, Fun, Arity, ?key(E, function), ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_remote(Remote, ?key(E, function), ?key(E, lexical_tracker)), AttachedMeta = attach_context_module(Remote, Meta, E), {{'&', AttachedMeta, [{'/', [], [{{'.', [], [Remote, Fun]}, [], []}, Arity]}]}, EE}; {{local, Fun, Arity}, #{function := nil}} -> @@ -796,10 +796,8 @@ expand_local(Meta, Name, Args, #{module := Module, function := Function} = E) -> expand_remote(Receiver, DotMeta, Right, Meta, Args, #{context := Context} = E, EL) when is_atom(Receiver) or is_tuple(Receiver) -> assert_no_clauses(Right, Meta, Args, E), - Arity = length(Args), is_atom(Receiver) andalso - elixir_lexical:record_remote(Receiver, Right, Arity, - ?key(E, function), ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_remote(Receiver, ?key(E, function), ?key(E, lexical_tracker)), AttachedDotMeta = attach_context_module(Receiver, DotMeta, E), {EArgs, EA} = expand_args(Args, E), case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs) of diff --git a/lib/elixir/src/elixir_lexical.erl b/lib/elixir/src/elixir_lexical.erl index 56ff0b6a4..319dae8b8 100644 --- a/lib/elixir/src/elixir_lexical.erl +++ b/lib/elixir/src/elixir_lexical.erl @@ -2,9 +2,9 @@ -module(elixir_lexical). -export([run/2, set_file/2, reset_file/1, record_alias/4, record_alias/2, - record_import/6, record_import/5, - record_remote/3, record_remote/6, - record_struct/3, format_error/1 + record_import/4, record_import/5, + record_remote/3, record_struct/2, + format_error/1 ]). -include("elixir.hrl"). @@ -38,17 +38,14 @@ record_import(Module, FAs, Line, Warn, Ref) -> record_alias(Module, Ref) -> if_tracker(Ref, fun(Pid) -> ?tracker:alias_dispatch(Pid, Module), ok end). -record_import(Module, Function, Arity, EnvFunction, Line, Ref) -> - if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, Module, {Function, Arity}, Line, mode(EnvFunction)), 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, EnvFunction, Ref) -> - if_tracker(Ref, fun(Pid) -> ?tracker:remote_reference(Pid, Module, mode(EnvFunction)), ok end). + if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module, mode(EnvFunction)), ok end). -record_remote(Module, Function, Arity, EnvFunction, Line, Ref) -> - if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module, {Function, Arity}, Line, mode(EnvFunction)), ok end). - -record_struct(Module, Line, Ref) -> - if_tracker(Ref, fun(Pid) -> ?tracker:remote_struct(Pid, Module, Line), ok end). +record_struct(Module, Ref) -> + if_tracker(Ref, fun(Pid) -> ?tracker:remote_struct(Pid, Module), ok end). %% EXTERNAL SOURCES diff --git a/lib/elixir/src/elixir_map.erl b/lib/elixir/src/elixir_map.erl index c8ceeaa5a..e3d087ee0 100644 --- a/lib/elixir/src/elixir_map.erl +++ b/lib/elixir/src/elixir_map.erl @@ -22,7 +22,7 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, #{context := Context} = E) true when is_atom(ELeft) -> %% We always record structs when they are expanded %% as they expect the reference at compile time. - elixir_lexical:record_struct(ELeft, ?line(Meta), ?key(E, lexical_tracker)), + elixir_lexical:record_struct(ELeft, ?key(E, lexical_tracker)), case extract_struct_assocs(Meta, ERight, E) of {expand, MapMeta, Assocs} when Context /= match -> %% Expand diff --git a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs index 71380e2e4..15c574da1 100644 --- a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs +++ b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs @@ -10,121 +10,40 @@ defmodule Kernel.LexicalTrackerTest do {:ok, [pid: pid]} end - test "can add remote references", config do - D.remote_reference(config[:pid], String, :runtime) + test "can add remote dispatch", config do + D.remote_dispatch(config[:pid], String, :runtime) assert D.remote_references(config[:pid]) == {[], [], [String]} - D.remote_reference(config[:pid], String, :compile) + D.remote_dispatch(config[:pid], String, :compile) assert D.remote_references(config[:pid]) == {[String], [], []} - D.remote_reference(config[:pid], String, :runtime) + D.remote_dispatch(config[:pid], String, :runtime) assert D.remote_references(config[:pid]) == {[String], [], []} end test "can add remote structs", config do - D.remote_struct(config[:pid], URI, 3) + D.remote_struct(config[:pid], URI) assert D.remote_references(config[:pid]) == {[], [URI], []} - D.remote_reference(config[:pid], URI, :runtime) + D.remote_dispatch(config[:pid], URI, :runtime) assert D.remote_references(config[:pid]) == {[], [URI], [URI]} - D.remote_reference(config[:pid], URI, :compile) + D.remote_dispatch(config[:pid], URI, :compile) assert D.remote_references(config[:pid]) == {[URI], [URI], []} end - test "can add remote dispatches with {function, arity} and line", config do - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :runtime) - assert D.remote_dispatches(config[:pid]) == {%{}, %{String => %{{:upcase, 1} => [1]}}} - assert D.remote_references(config[:pid]) == {[], [], [String]} - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :compile) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [1]}}, - %{String => %{{:upcase, 1} => [1]}} - } - - assert D.remote_references(config[:pid]) == {[String], [], []} - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :runtime) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [1]}}, - %{String => %{{:upcase, 1} => [1]}} - } - - assert D.remote_references(config[:pid]) == {[String], [], []} - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 2, :runtime) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [1]}}, - %{String => %{{:upcase, 1} => [2, 1]}} - } - - assert D.remote_references(config[:pid]) == {[String], [], []} - end - - test "can add remote dispatches for external files", config do - D.set_file(config[:pid], "lib/foo.ex") - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :runtime) - - assert D.remote_dispatches(config[:pid]) == - {%{}, %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}}} - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :compile) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}}, - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}} - } - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 1, :runtime) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}}, - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}} - } - - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 2, :runtime) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}}, - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 2}, {"lib/foo.ex", 1}]}} - } - - D.reset_file(config[:pid]) - D.remote_dispatch(config[:pid], String, {:upcase, 1}, 2, :runtime) - - assert D.remote_dispatches(config[:pid]) == - { - %{String => %{{:upcase, 1} => [{"lib/foo.ex", 1}]}}, - %{String => %{{:upcase, 1} => [2, {"lib/foo.ex", 2}, {"lib/foo.ex", 1}]}} - } - end - test "can add module imports", config do D.add_import(config[:pid], String, [], 1, true) - D.import_dispatch(config[:pid], String, {:upcase, 1}, 1, :compile) + D.import_dispatch(config[:pid], String, {:upcase, 1}) assert D.remote_references(config[:pid]) == {[String], [], []} - assert D.remote_dispatches(config[:pid]) == {%{String => %{{:upcase, 1} => [1]}}, %{}} - D.import_dispatch(config[:pid], String, {:upcase, 1}, 1, :runtime) + D.import_dispatch(config[:pid], String, {:upcase, 1}) assert D.remote_references(config[:pid]) == {[String], [], []} - - assert D.remote_dispatches(config[:pid]) == - {%{String => %{{:upcase, 1} => [1]}}, %{String => %{{:upcase, 1} => [1]}}} end test "can add module with {function, arity} imports", config do D.add_import(config[:pid], String, [upcase: 1], 1, true) - D.import_dispatch(config[:pid], String, {:upcase, 1}, 1, :compile) + D.import_dispatch(config[:pid], String, {:upcase, 1}) assert D.remote_references(config[:pid]) == {[String], [], []} end @@ -141,7 +60,7 @@ defmodule Kernel.LexicalTrackerTest do test "used module imports are not unused", config do D.add_import(config[:pid], String, [], 1, true) - D.import_dispatch(config[:pid], String, {:upcase, 1}, 1, :compile) + D.import_dispatch(config[:pid], String, {:upcase, 1}) assert D.collect_unused_imports(config[:pid]) == [] end @@ -153,14 +72,14 @@ defmodule Kernel.LexicalTrackerTest do 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}, 1, :compile) + 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}, 1, :compile) + D.import_dispatch(config[:pid], String, {:downcase, 1}) assert D.collect_unused_imports(config[:pid]) == [] end @@ -186,7 +105,7 @@ defmodule Kernel.LexicalTrackerTest do end test "does not tag aliases nor types" do - {{{compile, _structs, runtime}, {compile_dispatches, runtime_dispatches}}, _binding} = + {{compile, _structs, runtime}, _binding} = Code.eval_string(""" defmodule Kernel.LexicalTrackerTest.Typespecs do alias Foo.Bar, as: Bar, warn: false @@ -197,123 +116,14 @@ defmodule Kernel.LexicalTrackerTest do @macrocallback foo2(Foo.Bar.t) :: Foo.Bar.t @spec foo(bar3) :: Foo.Bar.t def foo(_), do: :bar - refs = Kernel.LexicalTracker.remote_references(__ENV__.lexical_tracker) - dispatches = Kernel.LexicalTracker.remote_dispatches(__ENV__.lexical_tracker) - {refs, dispatches} + Kernel.LexicalTracker.remote_references(__ENV__.lexical_tracker) end |> elem(3) """) refute Elixir.Bar in runtime - refute Map.has_key?(runtime_dispatches, Elixir.Bar) refute Elixir.Bar in compile - refute Map.has_key?(compile_dispatches, Elixir.Bar) refute Foo.Bar in runtime - refute Map.has_key?(runtime_dispatches, Foo.Bar) refute Foo.Bar in compile - refute Map.has_key?(compile_dispatches, Foo.Bar) - end - - test "remote dispatches" do - {{compile_remote_calls, runtime_remote_calls}, []} = - Code.eval_string(""" - defmodule Kernel.LexicalTrackerTest.RemoteDispatches do - import Record - require Integer - alias Remote, as: R - - def a do - _ = extract(1, 2) - _ = is_record(1) - _ = Integer.is_even(2) - NotAModule - Remote.func() - R.func() - _ = &extract/2 - _ = &is_record/1 - _ = &R.func/0 - _ = &Remote.func/0 - _ = &Integer.is_even/1 - %Macro.Env{} - end - - _ = &extract/2 - _ = &is_record/1 - _ = &R.func/0 - _ = &Remote.func/0 - _ = &Integer.is_even/1 - _ = &is_record/1; def b(a), do: is_record(a) # both runtime and compile - %Macro.Env{} - - Kernel.LexicalTracker.remote_dispatches(__ENV__.lexical_tracker) - end |> elem(3) - """) - - compile_remote_calls = unroll_dispatches(compile_remote_calls) - assert {6, Kernel, :def, 2} in compile_remote_calls - assert {8, Record, :is_record, 1} in compile_remote_calls - assert {9, Integer, :is_even, 1} in compile_remote_calls - assert {14, Record, :is_record, 1} in compile_remote_calls - assert {17, Integer, :is_even, 1} in compile_remote_calls - assert {18, Macro.Env, :__struct__, 0} in compile_remote_calls - assert {21, Record, :extract, 2} in compile_remote_calls - assert {22, Record, :is_record, 1} in compile_remote_calls - assert {23, Remote, :func, 0} in compile_remote_calls - assert {24, Remote, :func, 0} in compile_remote_calls - assert {25, Integer, :is_even, 1} in compile_remote_calls - assert {26, Kernel, :def, 2} in compile_remote_calls - assert {26, Record, :is_record, 1} in compile_remote_calls - assert {27, Macro.Env, :__struct__, 0} in compile_remote_calls - assert {29, Kernel.LexicalTracker, :remote_dispatches, 1} in compile_remote_calls - - runtime_remote_calls = unroll_dispatches(runtime_remote_calls) - assert {7, Record, :extract, 2} in runtime_remote_calls - assert {8, :erlang, :is_tuple, 1} in runtime_remote_calls - assert {11, Remote, :func, 0} in runtime_remote_calls - assert {12, Remote, :func, 0} in runtime_remote_calls - assert {13, Record, :extract, 2} in runtime_remote_calls - assert {14, :erlang, :is_tuple, 1} in runtime_remote_calls - assert {15, Remote, :func, 0} in runtime_remote_calls - assert {16, Remote, :func, 0} in runtime_remote_calls - assert {17, :erlang, :==, 2} in runtime_remote_calls - assert {26, :erlang, :is_tuple, 1} in runtime_remote_calls - end - - test "remote dispatches with external source" do - {{compile_remote_calls, runtime_remote_calls}, []} = - Code.eval_string( - """ - defmodule Kernel.LexicalTrackerTest.RemoteDispatchesWithExternalSource do - def foo do - String.upcase("foo") - end - - @file "lib/bar.ex" - def bar do - String.upcase("bar") - end - - String.upcase("foo") - - Kernel.LexicalTracker.remote_dispatches(__ENV__.lexical_tracker) - end |> elem(3) - """, - [], - file: "lib/remote_dispatches.ex" - ) - - runtime_remote_calls = unroll_dispatches(runtime_remote_calls) - assert {3, String, :upcase, 1} in runtime_remote_calls - assert {{"lib/bar.ex", 8}, String, :upcase, 1} in runtime_remote_calls - - compile_remote_calls = unroll_dispatches(compile_remote_calls) - assert {11, String, :upcase, 1} in compile_remote_calls - end - - defp unroll_dispatches(dispatches) do - for {module, fals} <- dispatches, - {{func, arity}, lines} <- fals, - line <- lines, - do: {line, module, func, arity} end end diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex index 49ee16d79..d31dfed3f 100644 --- a/lib/mix/lib/mix/compilers/elixir.ex +++ b/lib/mix/lib/mix/compilers/elixir.ex @@ -1,7 +1,7 @@ defmodule Mix.Compilers.Elixir do @moduledoc false - @manifest_vsn 3 + @manifest_vsn 4 import Record @@ -13,8 +13,6 @@ defmodule Mix.Compilers.Elixir do compile_references: [], struct_references: [], runtime_references: [], - compile_dispatches: [], - runtime_dispatches: [], external: [], warnings: [], modules: [] @@ -332,32 +330,20 @@ defmodule Mix.Compilers.Elixir do {compile_references, struct_references, runtime_references} = Kernel.LexicalTracker.remote_references(lexical) - {elixir_references, compile_references} = - Enum.split_with(compile_references, &match?("elixir_" <> _, Atom.to_string(&1))) + compile_references = + Enum.reject(compile_references, &match?("elixir_" <> _, Atom.to_string(&1))) source(modules: source_modules) = source compile_references = compile_references -- source_modules struct_references = struct_references -- source_modules runtime_references = runtime_references -- source_modules - {compile_dispatches, runtime_dispatches} = Kernel.LexicalTracker.remote_dispatches(lexical) - - compile_dispatches = - compile_dispatches - |> Map.drop(elixir_references) - |> Enum.to_list() - - runtime_dispatches = - runtime_dispatches - |> Enum.to_list() source = source( source, compile_references: compile_references, struct_references: struct_references, - runtime_references: runtime_references, - compile_dispatches: compile_dispatches, - runtime_dispatches: runtime_dispatches + runtime_references: runtime_references ) put_compiler_info({modules, structs, [source | sources], pending_modules, pending_structs}) diff --git a/lib/mix/lib/mix/tasks/xref.ex b/lib/mix/lib/mix/tasks/xref.ex index e01cf11a5..0450aca38 100644 --- a/lib/mix/lib/mix/tasks/xref.ex +++ b/lib/mix/lib/mix/tasks/xref.ex @@ -24,12 +24,9 @@ defmodule Mix.Tasks.Xref do ### callers CALLEE - Prints all callers of the given `CALLEE`, which can be one of: `Module`, - `Module.function`, or `Module.function/arity`. Examples: + Prints all callers of the given `MODULE`. Example: mix xref callers MyMod - mix xref callers MyMod.fun - mix xref callers MyMod.fun/3 ### graph @@ -149,60 +146,29 @@ defmodule Mix.Tasks.Xref do end @doc """ - Returns a list of information of all the function calls in the project. + Deprecated API to retrieve all the function calls in the project. - Each item in the list is a map with the following keys: - - * `:callee` - a tuple containing the module, function, and arity of the call - * `:line` - an integer representing the line where the function is called - * `:file` - a binary representing the file where the function is called - - This function returns an empty list when used at the root of an umbrella - project because there is no compile manifest to extract the function call - information from. To get the function calls of each child in an umbrella, - execute the function at the root of each individual application. + From Elixir v1.10, the xref pass is performed as the code is compiled, + therefore no information is kept during compilation. """ - @spec calls(keyword()) :: [ - %{ - callee: {module(), atom(), arity()}, - line: integer(), - file: String.t() - } - ] - def calls(opts \\ []) do - for manifest <- manifests(opts), - source( - runtime_dispatches: runtime_nested, - compile_dispatches: compile_nested, - source: rel_file - ) <- read_manifest(manifest), - call <- - dispatches_to_function_calls(rel_file, runtime_nested) ++ - dispatches_to_function_calls(rel_file, compile_nested), - do: call - end - - defp dispatches_to_function_calls(file, dispatches) do - for {module, function_calls} <- dispatches, - {{function, arity}, lines} <- function_calls, - line <- lines do - %{ - callee: {module, function, arity}, - file: file, - line: line - } - end + @deprecated "No alternative is available as xref is now done as the code is compiled" + def calls(_opts \\ []) do + [] end ## Modes defp callers(callee, opts) do - callee - |> filter_for_callee() - |> source_callers(opts) - |> merge_entries(:all) - |> sort_entries() - |> print_calls() + module = parse_callee(callee) + + file_callers = + for source <- sources(opts), + reference = reference(module, source), + do: {source(source, :source), reference} + + for {file, type} <- Enum.sort(file_callers) do + Mix.shell().info([file, " (", type, ")"]) + end :ok end @@ -215,45 +181,19 @@ defmodule Mix.Tasks.Xref do ## Callers - defp source_callers(filter, opts) do - for source <- sources(opts), - file = source(source, :source), - {module, func_arity_locations} <- dispatches(source), - {{func, arity}, locations} <- func_arity_locations, - filter.({module, func, arity}), - do: {{module, func, arity}, absolute_locations(locations, file)} - end - - defp print_calls(calls) do - Enum.each(calls, &print_call/1) - calls - end - - defp print_call({{module, func, arity}, locations}) do - shell = Mix.shell() - - for {file, line} <- locations do - shell.info([ - Exception.format_file_line(file, line, " "), - Exception.format_mfa(module, func, arity) - ]) + defp parse_callee(callee) do + case Mix.Utils.parse_mfa(callee) do + {:ok, [module]} -> module + _ -> Mix.raise("xref callers MODULE expects a MODULE, got: " <> callee) end end - defp filter_for_callee(callee) do - case Mix.Utils.parse_mfa(callee) do - {:ok, mfa_list} -> - mfa_list_length = length(mfa_list) - - fn {module, function, arity} -> - mfa_list == Enum.take([module, function, arity], mfa_list_length) - end - - :error -> - Mix.raise( - "xref callers CALLEE expects Module, Module.function, or Module.function/arity, " <> - "got: " <> callee - ) + defp reference(module, source) do + cond do + module in source(source, :compile_references) -> "compile" + module in source(source, :struct_references) -> "struct" + module in source(source, :runtime_references) -> "runtime" + true -> nil end end @@ -471,32 +411,4 @@ defmodule Mix.Tasks.Xref do [Path.join(Mix.Project.manifest_path(), @manifest) | siblings] end - - defp merge_entries(entries, filter) do - Enum.reduce(entries, %{}, fn {type, locations}, merged_entries -> - if filter == :all or filter.(elem(type, 0)) do - locations = MapSet.new(locations) - Map.update(merged_entries, type, locations, &MapSet.union(&1, locations)) - else - merged_entries - end - end) - end - - defp sort_entries(entries) do - entries - |> Enum.map(fn {type, locations} -> {type, Enum.sort(locations)} end) - |> Enum.sort() - end - - defp absolute_locations(locations, base) do - Enum.map(locations, &absolute_location(&1, base)) - end - - defp absolute_location({_, _} = location, _), do: location - defp absolute_location(line, base), do: {base, line} - - defp dispatches(source) do - source(source, :runtime_dispatches) ++ source(source, :compile_dispatches) - end end diff --git a/lib/mix/test/mix/tasks/xref_test.exs b/lib/mix/test/mix/tasks/xref_test.exs index bef9e355f..37cee56b8 100644 --- a/lib/mix/test/mix/tasks/xref_test.exs +++ b/lib/mix/test/mix/tasks/xref_test.exs @@ -16,212 +16,16 @@ defmodule Mix.Tasks.XrefTest do :ok end - test "calls: returns all function calls" do - files = %{ - "lib/a.ex" => """ - defmodule A do - def a, do: A.a() - def a(arg), do: A.a(arg) - def c, do: B.a() - end - """, - "lib/b.ex" => """ - defmodule B do - def a, do: nil - end - """ - } - - output = [ - %{callee: {A, :a, 0}, file: "lib/a.ex", line: 2}, - %{callee: {A, :a, 1}, file: "lib/a.ex", line: 3}, - %{callee: {B, :a, 0}, file: "lib/a.ex", line: 4}, - %{callee: {Kernel, :def, 2}, file: "lib/a.ex", line: 4}, - %{callee: {Kernel, :def, 2}, file: "lib/a.ex", line: 3}, - %{callee: {Kernel, :def, 2}, file: "lib/a.ex", line: 2}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/a.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, file: "lib/a.ex", line: 1}, - %{callee: {Kernel, :def, 2}, file: "lib/b.ex", line: 2}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/b.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, file: "lib/b.ex", line: 1} - ] - - assert_all_calls(files, output) - end - - test "calls: returns macro call" do - files = %{ - "lib/a.ex" => """ - defmodule A do - defmacro a_macro, do: :ok - end - """, - "lib/b.ex" => """ - defmodule B do - require A - def a, do: A.a_macro() - end - """ - } - - output = [ - %{callee: {A, :a_macro, 0}, file: "lib/b.ex", line: 3}, - %{callee: {Kernel, :def, 2}, file: "lib/b.ex", line: 3}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/b.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, line: 1, file: "lib/b.ex"}, - %{callee: {Kernel, :defmacro, 2}, file: "lib/a.ex", line: 2}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/a.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, line: 1, file: "lib/a.ex"} - ] - - assert_all_calls(files, output) - end - - test "calls: returns function call inside macro" do - files = %{ - "lib/a.ex" => """ - defmodule A do - defmacro a_macro(x) do - quote do - A.b(unquote(x)) - end - end - - def b(x), do: x - end - """, - "lib/b.ex" => """ - defmodule B do - require A - def a, do: A.a_macro(1) - end - """ - } - - output = [ - %{callee: {A, :b, 1}, file: "lib/b.ex", line: 3}, - %{callee: {A, :a_macro, 1}, file: "lib/b.ex", line: 3}, - %{callee: {Kernel, :def, 2}, file: "lib/b.ex", line: 3}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/b.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, file: "lib/b.ex", line: 1}, - %{callee: {Kernel, :def, 2}, file: "lib/a.ex", line: 8}, - %{callee: {Kernel, :defmacro, 2}, file: "lib/a.ex", line: 2}, - %{callee: {Kernel, :defmodule, 2}, file: "lib/a.ex", line: 1}, - %{callee: {Kernel.LexicalTracker, :read_cache, 2}, file: "lib/a.ex", line: 1} - ] - - assert_all_calls(files, output) - end - - defp assert_all_calls(files, expected) do - in_fixture("no_mixfile", fn -> - generate_files(files) - - Mix.Task.run("compile") - assert Enum.sort(Mix.Tasks.Xref.calls()) == Enum.sort(expected) - end) - end - - ## Callers - test "callers: prints callers of specified Module" do files = %{ "lib/a.ex" => """ defmodule A do - def a, do: A.a() - def a(arg), do: A.a(arg) - def b, do: A.b() - def c, do: B.a() - - @file "lib/external_source.ex" - def d, do: A.a() - end - """ - } - - output = """ - Compiling 2 files (.ex) - Generated sample app - lib/a.ex:2: A.a/0 - lib/external_source.ex:8: A.a/0 - lib/a.ex:3: A.a/1 - lib/a.ex:4: A.b/0 - """ - - assert_callers("A", files, output) - end - - test "callers: prints callers of specified Module.func" do - files = %{ - "lib/a.ex" => """ - defmodule A do - def a, do: A.a() - def a(arg), do: A.a(arg) - def b, do: A.b() - def c, do: B.a() - - @file "lib/external_source.ex" - def d, do: A.a() - end - """ - } - - output = """ - Compiling 2 files (.ex) - Generated sample app - lib/a.ex:2: A.a/0 - lib/external_source.ex:8: A.a/0 - lib/a.ex:3: A.a/1 - """ - - assert_callers("A.a", files, output) - end - - test "callers: prints callers of specified Module.func/arity" do - files = %{ - "lib/a.ex" => """ - defmodule A do - def a, do: A.a() - def a(arg), do: A.a(arg) - def b, do: A.b() - def c, do: B.a() - - @file "lib/external_source.ex" - def d, do: A.a() - end - """ - } - - output = """ - Compiling 2 files (.ex) - Generated sample app - lib/a.ex:2: A.a/0 - lib/external_source.ex:8: A.a/0 - """ - - assert_callers("A.a/0", files, output) - end - - test "callers: lists compile calls and macros" do - files = %{ - "lib/a.ex" => """ - defmodule A do - defmacro a_macro, do: :ok def a, do: :ok end """, "lib/b.ex" => """ defmodule B do - require A - - A.a_macro() - A.a() - - @file "lib/external_source.ex" - def b do - A.a_macro() - A.a() - end + def b, do: A.a() end """ } @@ -229,10 +33,7 @@ defmodule Mix.Tasks.XrefTest do output = """ Compiling 2 files (.ex) Generated sample app - lib/b.ex:5: A.a/0 - lib/external_source.ex:10: A.a/0 - lib/b.ex:4: A.a_macro/0 - lib/external_source.ex:9: A.a_macro/0 + lib/b.ex (runtime) """ assert_callers("A", files, output) @@ -244,8 +45,6 @@ defmodule Mix.Tasks.XrefTest do defmodule A do alias Enum, as: E - E.map([], &E.flatten/1) - def a(a, b), do: E.map(a, b) @file "lib/external_source.ex" @@ -260,11 +59,7 @@ defmodule Mix.Tasks.XrefTest do output = """ Compiling 2 files (.ex) Generated sample app - lib/a.ex:4: Enum.flatten/1 - lib/external_source.ex:11: Enum.flatten/1 - lib/a.ex:4: Enum.map/2 - lib/a.ex:6: Enum.map/2 - lib/external_source.ex:11: Enum.map/2 + lib/a.ex (runtime) """ assert_callers("Enum", files, output) @@ -275,28 +70,13 @@ defmodule Mix.Tasks.XrefTest do "lib/a.ex" => ~S""" defmodule A do import Integer - &is_even/1 - &parse/1 - - _ = is_even(Enum.random([1])) - _ = parse("2") - - def a(a), do: is_even(a) - def b(a), do: parse(a) - _ = is_even(Enum.random([1])); def c(a), do: is_even(a) end """, "lib/b.ex" => ~S""" defmodule B do - &Integer.parse/1 - - @file "lib/external_source.ex" - def a(a) do - import Integer - parse(1) - is_even(a) - end + import Integer + parse("1") end """ } @@ -304,16 +84,8 @@ defmodule Mix.Tasks.XrefTest do output = """ Compiling 2 files (.ex) Generated sample app - lib/a.ex:4: Integer.is_even/1 - lib/a.ex:7: Integer.is_even/1 - lib/a.ex:10: Integer.is_even/1 - lib/a.ex:12: Integer.is_even/1 - lib/external_source.ex:8: Integer.is_even/1 - lib/a.ex:5: Integer.parse/1 - lib/a.ex:8: Integer.parse/1 - lib/a.ex:11: Integer.parse/1 - lib/b.ex:2: Integer.parse/1 - lib/external_source.ex:7: Integer.parse/1 + lib/a.ex (compile) + lib/b.ex (compile) """ assert_callers("Integer", files, output) @@ -331,8 +103,7 @@ defmodule Mix.Tasks.XrefTest do test "callers: gives nice error for quotable but invalid callers spec" do in_fixture("no_mixfile", fn -> - message = - "xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: Module.func(arg)" + message = "xref callers MODULE expects a MODULE, got: Module.func(arg)" assert_raise Mix.Error, message, fn -> Mix.Task.run("xref", ["callers", "Module.func(arg)"]) @@ -342,8 +113,7 @@ defmodule Mix.Tasks.XrefTest do test "callers: gives nice error for unquotable callers spec" do in_fixture("no_mixfile", fn -> - message = - "xref callers CALLEE expects Module, Module.function, or Module.function/arity, got: %" + message = "xref callers MODULE expects a MODULE, got: %" assert_raise Mix.Error, message, fn -> Mix.Task.run("xref", ["callers", "%"]) @@ -655,10 +425,10 @@ defmodule Mix.Tasks.XrefTest do * lib/foo.ex (1) """ - Mix.Tasks.Xref.run(["callers", "Foo.foo"]) + Mix.Tasks.Xref.run(["callers", "Foo"]) assert receive_until_no_messages([]) == """ - lib/bar.ex:3: Foo.foo/0 + lib/bar.ex (runtime) """ end) end) @@ -674,10 +444,4 @@ defmodule Mix.Tasks.XrefTest do 0 -> IO.iodata_to_binary(acc) end end - - defp generate_files(files) do - for {file, contents} <- files do - File.write!(file, contents) - end - end end |