1
1
# 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 .
4
4
#
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.
7
45
defmodule Kernel.LocalsTracker do
8
46
@ moduledoc false
9
47
@@ -12,15 +50,41 @@ defmodule Kernel.LocalsTracker do
12
50
13
51
# Public API
14
52
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
+
15
71
@ doc """
16
72
Returns all locals that are reachable.
17
73
18
74
By default, all public functions are reachable.
19
75
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.
21
77
"""
22
78
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 )
24
88
end
25
89
26
90
defp to_pid ( pid ) when is_pid ( pid ) , do: pid
@@ -55,30 +119,64 @@ defmodule Kernel.LocalsTracker do
55
119
:gen_server . cast ( pid , { :add_local , from , to } )
56
120
end
57
121
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
+
58
156
# Collect all unused definitions based on the private
59
157
# given also accounting the expected amount of default
60
158
# clauses a private function have.
61
159
@ doc false
62
- def collect_unused ( pid , private ) do
160
+ def collect_unused_locals ( pid , private ) do
63
161
# Add a vertex for each private given
64
162
lc { tuple , kind , _defaults } in list private do
65
163
add_definition ( pid , kind , tuple )
66
164
end
67
165
68
166
# Process all unused
69
167
reachable = reachable ( pid )
70
- :lists . foldl ( collect_unused ( & 1 , & 2 , reachable ) , [ ] , private )
168
+ :lists . foldl ( collect_unused_locals ( & 1 , & 2 , reachable ) , [ ] , private )
71
169
end
72
170
73
- defp collect_unused ( { tuple , kind , 0 } , acc , reachable ) do
171
+ defp collect_unused_locals ( { tuple , kind , 0 } , acc , reachable ) do
74
172
if :lists . member ( tuple , reachable ) do
75
173
acc
76
174
else
77
175
[ { :unused_def , tuple , kind } | acc ]
78
176
end
79
177
end
80
178
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
82
180
{ name , arity } = tuple
83
181
min = arity - default
84
182
max = arity
@@ -105,13 +203,15 @@ defmodule Kernel.LocalsTracker do
105
203
# Callbacks
106
204
107
205
def init ( [ ] ) do
108
- d = :digraph . new
206
+ d = :digraph . new ( [ :protected ] )
109
207
:digraph . add_vertex ( d , :local )
208
+ :digraph . add_vertex ( d , :import )
209
+ :digraph . add_vertex ( d , :warn )
110
210
{ :ok , d }
111
211
end
112
212
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 }
115
215
end
116
216
117
217
def handle_call ( _request , _from , d ) do
@@ -124,13 +224,37 @@ defmodule Kernel.LocalsTracker do
124
224
125
225
def handle_cast ( { :add_local , from , to } , d ) do
126
226
: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
128
252
{ :noreply , d }
129
253
end
130
254
131
255
def handle_cast ( { :add_definition , public , tuple } , d ) when public in [ :def , :defmacro ] do
132
256
:digraph . add_vertex ( d , tuple )
133
- :digraph . add_edge ( d , :local , tuple )
257
+ replace_edge ( d , :local , tuple )
134
258
{ :noreply , d }
135
259
end
136
260
@@ -155,12 +279,9 @@ defmodule Kernel.LocalsTracker do
155
279
{ :ok , d }
156
280
end
157
281
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
165
286
end
166
287
end
0 commit comments