Skip to content

Commit d19f6be

Browse files
author
José Valim
committed
Provide source information to all dispatches and complement the API
1 parent da84a79 commit d19f6be

File tree

6 files changed

+128
-52
lines changed

6 files changed

+128
-52
lines changed

lib/elixir/lib/module/dispatch_tracker.ex

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,68 @@ defmodule Module.DispatchTracker do
4848
@timeout 30_000
4949
@behavior :gen_server
5050

51+
@type ref :: pid | module
52+
@type name :: atom
53+
@type name_arity :: { name, arity }
54+
55+
@type local :: { name, arity }
56+
@type import :: { :import, name, arity }
57+
@type remote :: { :remote, name, arity }
58+
5159
# Public API
5260

61+
@doc """
62+
Receives a dispatch or a module and returns all dispatches
63+
that calls it.
64+
65+
In case the argument is a module, the response will be
66+
made by import and remote dispatches.
67+
68+
In case the argument is another dispatch, the response
69+
will be made by local dispatches.
70+
71+
This function is not recursive, so if A dispatches to
72+
B which dispatches to C, A does not appear in the result,
73+
only B.
74+
"""
75+
@spec dispatches_to(ref, module) :: [import | remote]
76+
@spec dispatches_to(ref, local | import | remote) :: [local]
77+
def dispatches_to(ref, dispatch) do
78+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
79+
:digraph.in_neighbours(d, dispatch) |> only_tuples
80+
end
81+
82+
@doc """
83+
Receives a local and returns all dispatches from that local.
84+
85+
This function is not recursive, so if A dispatches to
86+
B which dispatches to C, C does not appear in the result,
87+
only B.
88+
"""
89+
@spec dispatches_from(ref, local) :: [local | import | remote]
90+
def dispatches_from(ref, { name, arity }) do
91+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
92+
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
93+
end
94+
5395
@doc """
5496
Returns all the modules which were imported.
5597
All external dependencies to a module is the sum
5698
of imports and remotes.
5799
"""
58-
def imports(pid) do
59-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
100+
@spec imports(ref) :: [module]
101+
def imports(ref) do
102+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
60103
:digraph.out_neighbours(d, :import)
61104
end
62105

63106
@doc """
64107
Returns all imported modules that had the given
65108
`{ name, arity }` invoked.
66109
"""
67-
def imports_with_dispatch(pid, { name, arity }) do
68-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
110+
@spec imports_with_dispatch(ref, name_arity) :: [module]
111+
def imports_with_dispatch(ref, { name, arity }) do
112+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
69113
:digraph.out_neighbours(d, { :import, name, arity })
70114
end
71115

@@ -74,17 +118,19 @@ defmodule Module.DispatchTracker do
74118
to. All external dependencies to a module is the sum
75119
of imports and remotes.
76120
"""
77-
def remotes(pid) do
78-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
121+
@spec remotes(ref) :: [module]
122+
def remotes(ref) do
123+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
79124
:digraph.out_neighbours(d, :remote)
80125
end
81126

82127
@doc """
83128
Returns all modules that had the given `{ name, arity }`
84129
invoked remotely.
85130
"""
86-
def remotes_with_dispatch(pid, { name, arity }) do
87-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
131+
@spec remotes_with_dispatch(ref, name_arity) :: [module]
132+
def remotes_with_dispatch(ref, { name, arity }) do
133+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
88134
:digraph.out_neighbours(d, { :remote, name, arity })
89135
end
90136

@@ -95,8 +141,9 @@ defmodule Module.DispatchTracker do
95141
A private function is only reachable if it has
96142
a public function that it invokes directly.
97143
"""
98-
def reachable(pid) do
99-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
144+
@spec reachable(ref) :: [local]
145+
def reachable(ref) do
146+
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
100147
reduce_reachable(d, :local, [])
101148
end
102149

@@ -110,6 +157,10 @@ defmodule Module.DispatchTracker do
110157
defp to_pid(pid) when is_pid(pid), do: pid
111158
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
112159

160+
defp only_tuples(list) do
161+
lc x inlist list, is_tuple(x), do: x
162+
end
163+
113164
# Internal API
114165

115166
# Starts the tracker and returns its pid.
@@ -141,14 +192,14 @@ defmodule Module.DispatchTracker do
141192

142193
# Adds a remote dispatch to the given target.
143194
@doc false
144-
def add_remote(pid, module, target) when is_atom(module) and is_tuple(target) do
145-
:gen_server.cast(pid, { :add_remote, module, target })
195+
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
196+
:gen_server.cast(pid, { :add_remote, function, module, target })
146197
end
147198

148199
# Adds a import dispatch to the given target.
149200
@doc false
150-
def add_import(pid, module, target) when is_atom(module) and is_tuple(target) do
151-
:gen_server.cast(pid, { :add_import, module, target })
201+
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
202+
:gen_server.cast(pid, { :add_import, function, module, target })
152203
end
153204

154205
# Associates a module with a warn. This adds the given
@@ -280,13 +331,13 @@ defmodule Module.DispatchTracker do
280331
{ :noreply, d }
281332
end
282333

283-
def handle_cast({ :add_remote, module, { name, arity } }, d) do
284-
record_import_or_remote(d, :remote, module, name, arity)
334+
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
335+
record_import_or_remote(d, :remote, function, module, name, arity)
285336
{ :noreply, d }
286337
end
287338

288-
def handle_cast({ :add_import, module, { name, arity } }, d) do
289-
record_import_or_remote(d, :import, module, name, arity)
339+
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
340+
record_import_or_remote(d, :import, function, module, name, arity)
290341
{ :noreply, d }
291342
end
292343

@@ -336,13 +387,17 @@ defmodule Module.DispatchTracker do
336387
{ :ok, d }
337388
end
338389

339-
defp record_import_or_remote(d, kind, module, name, arity) do
390+
defp record_import_or_remote(d, kind, function, module, name, arity) do
340391
:digraph.add_vertex(d, module)
341392
replace_edge!(d, kind, module)
342393

343394
tuple = { kind, name, arity }
344395
:digraph.add_vertex(d, tuple)
345396
replace_edge!(d, tuple, module)
397+
398+
if function != nil do
399+
replace_edge!(d, function, tuple)
400+
end
346401
end
347402

348403
defp replace_edge!(d, from, to) do

lib/elixir/src/elixir_def_local.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
77

8-
macro_for(Tuple, All, #elixir_scope{module=Module} = S) ->
8+
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function} = S) ->
99
try elixir_def:lookup_definition(Module, Tuple) of
1010
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
11-
elixir_tracker:record_local(Tuple, S),
11+
elixir_tracker:record_local(Tuple, Module, Function),
1212
get_function(Line, Module, Clauses);
1313
_ ->
1414
false

lib/elixir/src/elixir_dispatch.erl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ import_function(Meta, Name, Arity, S) ->
3434
Tuple = { Name, Arity },
3535
case find_dispatch(Meta, Tuple, S) of
3636
{ function, Receiver } ->
37-
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module),
37+
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
3838
remote_function(Meta, Receiver, Name, Arity, S);
3939
{ macro, _Receiver } ->
4040
false;
4141
{ import, Receiver } ->
4242
require_function(Meta, Receiver, Name, Arity, S);
4343
nomatch ->
44-
elixir_tracker:record_local(Tuple, S),
44+
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
4545
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
4646
end.
4747

@@ -51,7 +51,7 @@ require_function(Meta, Receiver, Name, Arity, S) ->
5151
case is_element(Tuple, get_optional_macros(Receiver)) of
5252
true -> false;
5353
false ->
54-
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module),
54+
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
5555
remote_function(Meta, Receiver, Name, Arity, S)
5656
end.
5757

@@ -64,21 +64,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
6464

6565
case find_dispatch(Meta, Tuple, S) of
6666
{ function, Receiver } ->
67-
elixir_tracker:record_import(Tuple, Receiver, Module),
67+
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
6868
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
6969
true -> erlang;
7070
false -> Receiver
7171
end,
7272
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
7373
{ import, Receiver } ->
74-
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module),
74+
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
7575
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
7676
Result ->
7777
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
7878
{ error, noexpansion } ->
7979
Callback();
8080
{ error, internal } ->
81-
elixir_tracker:record_import(Tuple, ?builtin, Module),
81+
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
8282
elixir_macros:translate({ Name, Meta, Args }, S);
8383
{ ok, _Receiver, Tree } ->
8484
translate_expansion(Meta, Tree, S)
@@ -92,15 +92,15 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
9292

9393
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
9494
true ->
95-
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module),
95+
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
9696
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
9797
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
9898
false ->
9999
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
100100
{ error, noexpansion } ->
101101
Callback();
102102
{ error, internal } ->
103-
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module),
103+
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
104104
elixir_macros:translate({ Name, Meta, Args }, S);
105105
{ ok, _Receiver, Tree } ->
106106
translate_expansion(Meta, Tree, S)
@@ -119,11 +119,11 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
119119
case is_element(Tuple, in_erlang_macros()) of
120120
true -> { error, internal };
121121
false ->
122-
elixir_tracker:record_import(Tuple, ?builtin, Module),
122+
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
123123
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
124124
end;
125125
{ macro, Receiver } ->
126-
elixir_tracker:record_import(Tuple, Receiver, Module),
126+
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
127127
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
128128
{ import, Receiver } ->
129129
expand_require(Meta, Receiver, Tuple, Args, Module, S);
@@ -143,7 +143,7 @@ expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
143143
false ->
144144
case is_element(Tuple, in_elixir_macros()) of
145145
true ->
146-
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module),
146+
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
147147
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
148148
false ->
149149
{ error, noexpansion }
@@ -158,7 +158,7 @@ expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
158158
false ->
159159
case is_element(Tuple, get_optional_macros(Receiver)) of
160160
true ->
161-
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module),
161+
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
162162
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
163163
false -> { error, noexpansion }
164164
end;

lib/elixir/src/elixir_tracker.erl

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
-module(elixir_tracker).
44
-export([
55
setup/1, cleanup/1,
6-
record_local/2, record_import/3, record_remote/3,
6+
record_local/2, record_local/3,
7+
record_import/4, record_remote/4,
78
record_warn/4, record_definition/3,
89
ensure_no_import_conflict/4, ensure_all_imports_used/3,
910
warn_unused_local/3, format_error/1
@@ -28,10 +29,10 @@ record_local(Tuple, Module) when is_atom(Module) ->
2829
if_tracker(Module, fun(Pid) ->
2930
?tracker:add_local(Pid, Tuple),
3031
true
31-
end);
32-
record_local(Tuple, #elixir_scope{function=Function})
32+
end).
33+
record_local(Tuple, _Module, Function)
3334
when Function == nil; Function == Tuple -> false;
34-
record_local(Tuple, #elixir_scope{module=Module, function=Function}) ->
35+
record_local(Tuple, Module, Function) ->
3536
if_tracker(Module, fun(Pid) ->
3637
?tracker:add_local(Pid, Function, Tuple),
3738
true
@@ -43,22 +44,22 @@ record_warn(Ref, Warn, Line, Module) ->
4344
true
4445
end).
4546

46-
record_import(_Tuple, Receiver, Module)
47+
record_import(_Tuple, Receiver, Module, _Function)
4748
when Module == nil; Module == Receiver -> false;
48-
record_import(Tuple, Receiver, Module) ->
49+
record_import(Tuple, Receiver, Module, Function) ->
4950
try
5051
Pid = ets:lookup_element(Module, ?attr, 2),
51-
?tracker:add_import(Pid, Receiver, Tuple)
52+
?tracker:add_import(Pid, Function, Receiver, Tuple)
5253
catch
5354
error:badarg -> false
5455
end.
5556

56-
record_remote(_Tuple, Receiver, Module)
57+
record_remote(_Tuple, Receiver, Module, _Function)
5758
when Module == nil; Module == Receiver -> false;
58-
record_remote(Tuple, Receiver, Module) ->
59+
record_remote(Tuple, Receiver, Module, Function) ->
5960
try
6061
Pid = ets:lookup_element(Module, ?attr, 2),
61-
?tracker:add_remote(Pid, Receiver, Tuple)
62+
?tracker:add_remote(Pid, Function, Receiver, Tuple)
6263
catch
6364
error:badarg -> false
6465
end.

lib/elixir/src/elixir_translator.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,8 +577,8 @@ translate_fn(Meta, Clauses, S) ->
577577

578578
%% Locals
579579

580-
translate_local(Meta, Name, Args, #elixir_scope{local=nil} = S) ->
581-
elixir_tracker:record_local({ Name, length(Args) }, S),
580+
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
581+
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
582582
Line = ?line(Meta),
583583
{ TArgs, NS } = translate_args(Args, S),
584584
{ { call, Line, { atom, Line, Name }, TArgs }, NS };

0 commit comments

Comments
 (0)