summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>2019-07-16 17:10:43 +0200
committerEric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>2019-07-16 17:10:43 +0200
commitba8ed0c037a94f4094c37a6b6ca7fbd49d400a6c (patch)
tree759f22394ecfd47f1c041fbee2d10a0d183c4177
parent4ad5749d00b2e607a976feb1445b1635ff99f1dc (diff)
downloadelixir-emj/lt-remove-dispatches.tar.gz
Remove dispatch tracking in LexicalTrackeremj/lt-remove-dispatches
-rw-r--r--lib/elixir/lib/kernel/lexical_tracker.ex64
-rw-r--r--lib/elixir/src/elixir_dispatch.erl12
-rw-r--r--lib/elixir/src/elixir_expand.erl6
-rw-r--r--lib/elixir/src/elixir_lexical.erl19
-rw-r--r--lib/elixir/src/elixir_map.erl2
-rw-r--r--lib/elixir/test/elixir/kernel/lexical_tracker_test.exs220
-rw-r--r--lib/mix/lib/mix/compilers/elixir.ex22
-rw-r--r--lib/mix/lib/mix/tasks/xref.ex142
-rw-r--r--lib/mix/test/mix/tasks/xref_test.exs258
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