Skip to content

Commit c3e7543

Browse files
author
José Valim
committed
Extract imports handling to locals tracker (soon to be renamed)
1 parent c7e86b0 commit c3e7543

File tree

7 files changed

+307
-174
lines changed

7 files changed

+307
-174
lines changed

lib/elixir/lib/kernel/locals_tracker.ex

Lines changed: 143 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
11
# This is a module Elixir responsible for tracking
2-
# local calls in order to emit proper warnings when
3-
# any local private function is unused.
2+
# calls in order to extract Elixir modules' behaviour
3+
# during compilation time.
44
#
5-
# Since this is required for bootstrap, we can't use
6-
# any of the GenServer.Behaviour conveniences.
5+
# ## Implementation
6+
#
7+
# The implementation uses the digraph module to track
8+
# all dependencies. The graph starts with three main
9+
# vertices:
10+
#
11+
# * `:local` - points to local functions
12+
# * `:import` - points to imported modules
13+
# * `:warn` - points to imported modules that should be warned
14+
# * `:remote` - points to remote modules
15+
#
16+
# Besides those, we have can the following vertices:
17+
#
18+
# * `Module` - a module that was invoked via an import or remotely
19+
# * `{ name, arity }` - a local function/arity pair
20+
# * `{ :import, name, arity }` - an invoked function/arity import
21+
# * `{ :remote, name, arity }` - an remotely invoked function/arity
22+
#
23+
# Each of those vertices can associate to other vertices
24+
# as described below:
25+
#
26+
# * `Module`
27+
# * in neighbours: `:import`, `:remote`, `:warn`,
28+
# `{ :import, name, arity }` and `{ :remote, name arity }`
29+
# * out neighbours: `:warn`
30+
#
31+
# * `{ name, arity }`
32+
# * in neighbours: `:local`, `{ name, arity }`
33+
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
34+
#
35+
# * `{ :import, name, arity }`
36+
# * in neighbours: `{ name, arity }`
37+
# * out neighbours: `Module`
38+
#
39+
# * `{ :remote, name, arity }`
40+
# * in neighbours: `{ name, arity }`
41+
# * out neighbours: `Module`
42+
#
43+
# Note that since this is required for bootstrap, we can't use
44+
# any of the `GenServer.Behaviour` conveniences.
745
defmodule Kernel.LocalsTracker do
846
@moduledoc false
947

@@ -12,15 +50,41 @@ defmodule Kernel.LocalsTracker do
1250

1351
# Public API
1452

53+
@doc """
54+
Returns all the modules which were imported and
55+
made a call to.
56+
"""
57+
def imports(pid) do
58+
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
59+
:digraph.out_neighbours(d, :import)
60+
end
61+
62+
@doc """
63+
Returns all imported modules that had the given
64+
{ name, arity } invoked.
65+
"""
66+
def imports_with_dispatch(pid, { name, arity }) do
67+
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
68+
:digraph.out_neighbours(d, { :import, name, arity })
69+
end
70+
1571
@doc """
1672
Returns all locals that are reachable.
1773
1874
By default, all public functions are reachable.
1975
A private function is only reachable if it has
20-
a public function that invokes it directly.
76+
a public function that it invokes directly.
2177
"""
2278
def reachable(pid) do
23-
:gen_server.call(to_pid(pid), :reachable, @timeout)
79+
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
80+
reduce_reachable(d, :local, [])
81+
end
82+
83+
defp reduce_reachable(d, vertex, vertices) do
84+
neighbours = :digraph.out_neighbours(d, vertex)
85+
remaining = neighbours -- vertices
86+
vertices = neighbours ++ vertices
87+
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
2488
end
2589

2690
defp to_pid(pid) when is_pid(pid), do: pid
@@ -55,30 +119,64 @@ defmodule Kernel.LocalsTracker do
55119
:gen_server.cast(pid, { :add_local, from, to })
56120
end
57121

122+
# Adds a import dispatch to the given target.
123+
@doc false
124+
def add_import(pid, module, target) when is_atom(module) and is_tuple(target) do
125+
:gen_server.cast(pid, { :add_import, module, target })
126+
end
127+
128+
# Associates a module with a warn. This adds the given
129+
# module and associates it with the `:import` vertex
130+
# permanently, even if warn is false.
131+
@doc false
132+
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
133+
:gen_server.cast(pid, { :add_warnable, module, warn, line })
134+
end
135+
136+
# Collect all unused imports where warn has been set to true.
137+
def collect_unused_imports(pid) do
138+
d = :gen_server.call(pid, :digraph)
139+
warnable = :digraph.out_neighbours(d, :warn)
140+
141+
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
142+
{ mod, line }
143+
end
144+
end
145+
146+
defp get_warn_line(d, mod) do
147+
[edge] = :digraph.out_edges(d, mod)
148+
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
149+
line
150+
end
151+
152+
defp has_imports?(d, mod) do
153+
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
154+
end
155+
58156
# Collect all unused definitions based on the private
59157
# given also accounting the expected amount of default
60158
# clauses a private function have.
61159
@doc false
62-
def collect_unused(pid, private) do
160+
def collect_unused_locals(pid, private) do
63161
# Add a vertex for each private given
64162
lc { tuple, kind, _defaults } inlist private do
65163
add_definition(pid, kind, tuple)
66164
end
67165

68166
# Process all unused
69167
reachable = reachable(pid)
70-
:lists.foldl(collect_unused(&1, &2, reachable), [], private)
168+
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
71169
end
72170

73-
defp collect_unused({ tuple, kind, 0 }, acc, reachable) do
171+
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
74172
if :lists.member(tuple, reachable) do
75173
acc
76174
else
77175
[{ :unused_def, tuple, kind }|acc]
78176
end
79177
end
80178

81-
defp collect_unused({ tuple, kind, default }, acc, reachable) when default > 0 do
179+
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
82180
{ name, arity } = tuple
83181
min = arity - default
84182
max = arity
@@ -105,13 +203,15 @@ defmodule Kernel.LocalsTracker do
105203
# Callbacks
106204

107205
def init([]) do
108-
d = :digraph.new
206+
d = :digraph.new([:protected])
109207
:digraph.add_vertex(d, :local)
208+
:digraph.add_vertex(d, :import)
209+
:digraph.add_vertex(d, :warn)
110210
{ :ok, d }
111211
end
112212

113-
def handle_call(:reachable, _from, d) do
114-
{ :reply, reduce_reachable(d, :local, []), d }
213+
def handle_call(:digraph, _from, d) do
214+
{ :reply, d, d }
115215
end
116216

117217
def handle_call(_request, _from, d) do
@@ -124,13 +224,37 @@ defmodule Kernel.LocalsTracker do
124224

125225
def handle_cast({ :add_local, from, to }, d) do
126226
:digraph.add_vertex(d, to)
127-
[:"$e"|_] = :digraph.add_edge(d, from, to)
227+
replace_edge(d, from, to)
228+
{ :noreply, d }
229+
end
230+
231+
def handle_cast({ :add_import, module, { name, arity } }, d) do
232+
:digraph.add_vertex(d, module)
233+
replace_edge(d, :import, module)
234+
235+
tuple = { :import, name, arity }
236+
:digraph.add_vertex(d, tuple)
237+
replace_edge(d, tuple, module)
238+
239+
{ :noreply, d }
240+
end
241+
242+
def handle_cast({ :add_warnable, module, warn, line }, d) do
243+
:digraph.add_vertex(d, module)
244+
replace_edge(d, :import, module)
245+
246+
if warn do
247+
:digraph.add_edge(d, :warn, module, line)
248+
:digraph.add_edge(d, module, :warn, line)
249+
else
250+
:digraph.del_path(d, :warn, module)
251+
end
128252
{ :noreply, d }
129253
end
130254

131255
def handle_cast({ :add_definition, public, tuple }, d) when public in [:def, :defmacro] do
132256
:digraph.add_vertex(d, tuple)
133-
:digraph.add_edge(d, :local, tuple)
257+
replace_edge(d, :local, tuple)
134258
{ :noreply, d }
135259
end
136260

@@ -155,12 +279,9 @@ defmodule Kernel.LocalsTracker do
155279
{ :ok, d }
156280
end
157281

158-
# Helpers
159-
160-
defp reduce_reachable(d, vertex, vertices) do
161-
neighbours = :digraph.out_neighbours(d, vertex)
162-
remaining = neighbours -- vertices
163-
vertices = neighbours ++ vertices
164-
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
282+
defp replace_edge(d, from, to) do
283+
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
284+
[:"$e"|_] = :digraph.add_edge(d, from, to)
285+
end
165286
end
166287
end

0 commit comments

Comments
 (0)