Skip to content

Commit 3c151d5

Browse files
author
José Valim
committed
Add Kernel.LexicalTracker
1 parent d92175a commit 3c151d5

File tree

15 files changed

+410
-302
lines changed

15 files changed

+410
-302
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
defmodule Kernel.LexicalTracker do
2+
@timeout 30_000
3+
@behavior :gen_server
4+
5+
@import 2
6+
@alias 3
7+
8+
@doc """
9+
Returns all remotes linked to in this lexical scope.
10+
"""
11+
def remotes(pid) do
12+
ets = :gen_server.call(to_pid(pid), :ets, @timeout)
13+
:ets.match(ets, { :"$1", :_, :_ }) |> List.flatten
14+
end
15+
16+
defp to_pid(pid) when is_pid(pid), do: pid
17+
defp to_pid(mod) when is_atom(mod) do
18+
table = :elixir_module.data_table(mod)
19+
[{ _, val }] = :ets.lookup(table, :__lexical_tracker)
20+
val
21+
end
22+
23+
# Internal API
24+
25+
# Starts the tracker and returns its pid.
26+
@doc false
27+
def start_link do
28+
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
29+
pid
30+
end
31+
32+
@doc false
33+
def stop(pid) do
34+
:gen_server.cast(pid, :stop)
35+
end
36+
37+
@doc false
38+
def add_import(pid, module, line, warn) do
39+
:gen_server.cast(pid, { :add_import, module, line, warn })
40+
end
41+
42+
@doc false
43+
def add_alias(pid, module, line, warn) do
44+
:gen_server.cast(pid, { :add_alias, module, line, warn })
45+
end
46+
47+
@doc false
48+
def remote_dispatch(pid, module) do
49+
:gen_server.cast(pid, { :remote_dispatch, module })
50+
end
51+
52+
@doc false
53+
def import_dispatch(pid, module) do
54+
:gen_server.cast(pid, { :import_dispatch, module })
55+
end
56+
57+
@doc false
58+
def alias_dispatch(pid, module) do
59+
:gen_server.cast(pid, { :alias_dispatch, module })
60+
end
61+
62+
@doc false
63+
def collect_unused_imports(pid) do
64+
unused(pid, @import)
65+
end
66+
67+
@doc false
68+
def collect_unused_aliases(pid) do
69+
unused(pid, @alias)
70+
end
71+
72+
defp unused(pid, pos) do
73+
ets = :gen_server.call(pid, :ets, @timeout)
74+
:ets.foldl(fn
75+
{ module, _, _ } = tuple, acc when is_integer(:erlang.element(pos, tuple)) ->
76+
[{ module, :erlang.element(pos, tuple) }|acc]
77+
_, acc ->
78+
acc
79+
end, [], ets) |> Enum.sort
80+
end
81+
82+
# Callbacks
83+
84+
85+
def init([]) do
86+
{ :ok, :ets.new(:lexical, [:protected]) }
87+
end
88+
89+
def handle_call(:ets, _from, d) do
90+
{ :reply, d, d }
91+
end
92+
93+
def handle_call(_request, _from, d) do
94+
{ :noreply, d }
95+
end
96+
97+
def handle_cast({ :remote_dispatch, module }, d) do
98+
add_module(d, module)
99+
{ :noreply, d }
100+
end
101+
102+
def handle_cast({ :import_dispatch, module }, d) do
103+
add_dispatch(d, module, @import)
104+
{ :noreply, d }
105+
end
106+
107+
def handle_cast({ :alias_dispatch, module }, d) do
108+
add_dispatch(d, module, @alias)
109+
{ :noreply, d }
110+
end
111+
112+
def handle_cast({ :add_import, module, line, warn }, d) do
113+
add_directive(d, module, line, warn, @import)
114+
{ :noreply, d }
115+
end
116+
117+
def handle_cast({ :add_alias, module, line, warn }, d) do
118+
add_directive(d, module, line, warn, @alias)
119+
{ :noreply, d }
120+
end
121+
122+
def handle_cast(:stop, d) do
123+
{ :stop, :normal, d }
124+
end
125+
126+
def handle_cast(_msg, d) do
127+
{ :noreply, d }
128+
end
129+
130+
def handle_info(_msg, d) do
131+
{ :noreply, d }
132+
end
133+
134+
def terminate(_reason, _d) do
135+
:ok
136+
end
137+
138+
def code_change(_old, d, _extra) do
139+
{ :ok, d }
140+
end
141+
142+
# Callbacks helpers
143+
144+
# In the table we keep imports and aliases.
145+
# If the value is false, it was not imported/aliased
146+
# If the value is true, it was imported/aliased
147+
# If the value is a line, it was imported/aliased and has a pending warning
148+
defp add_module(d, module) do
149+
:ets.insert_new(d, { module, false, false })
150+
end
151+
152+
defp add_dispatch(d, module, pos) do
153+
:ets.update_element(d, module, { pos, true })
154+
end
155+
156+
defp add_directive(d, module, line, warn, pos) do
157+
add_module(d, module)
158+
marker = if warn, do: line, else: true
159+
:ets.update_element(d, module, { pos, marker })
160+
end
161+
end

lib/elixir/lib/kernel/parallel_compiler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ defmodule Kernel.ParallelCompiler do
113113
# list because someone invoked try/rescue UndefinedFunctionError
114114
new_waiting = List.keydelete(waiting, child, 0)
115115
spawn_compilers(files, original, output, callbacks, new_waiting, new_queued, schedulers, result)
116-
{ :module_available, child, file, module, binary } ->
116+
{ :module_available, child, ref, file, module, binary } ->
117117
if callback = Keyword.get(callbacks, :each_module) do
118118
callback.(file, module, binary)
119119
end
120120

121121
# Release the module loader which is waiting for an ack
122-
child <- { self, :ack }
122+
child <- { ref, :ack }
123123

124124
new_waiting = release_waiting_processes(module, waiting)
125125
new_result = [module|result]

lib/elixir/lib/macro.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,15 +536,15 @@ defmodule Macro do
536536
defp expand_once({ :__aliases__, _, _ } = original, env, cache) do
537537
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
538538
receiver when is_atom(receiver) ->
539-
:elixir_tracker.record_alias(receiver, env.module)
539+
:elixir_lexical.record_alias(env.file, receiver)
540540
{ receiver, true, cache }
541541
aliases ->
542542
aliases = lc alias inlist aliases, do: elem(expand_once(alias, env, cache), 0)
543543

544544
case :lists.all(&is_atom/1, aliases) do
545545
true ->
546546
receiver = :elixir_aliases.concat(aliases)
547-
:elixir_tracker.record_alias(receiver, env.module)
547+
:elixir_lexical.record_alias(env.file, receiver)
548548
{ receiver, true, cache }
549549
false -> { original, false, cache }
550550
end

lib/elixir/lib/module/dispatch_tracker.ex

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
# ## Implementation
66
#
77
# The implementation uses the digraph module to track
8-
# all dependencies. The graph starts with three main
9-
# vertices:
8+
# all dependencies. The graph starts with one main vertice:
109
#
1110
# * `: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
1511
#
16-
# Besides those, we have can the following vertices:
12+
# We also have can the following vertices:
1713
#
1814
# * `Module` - a module that was invoked via an import or remotely
1915
# * `{ name, arity }` - a local function/arity pair
@@ -24,9 +20,7 @@
2420
# as described below:
2521
#
2622
# * `Module`
27-
# * in neighbours: `:import`, `:remote`, `:warn`,
28-
# `{ :import, name, arity }` and `{ :remote, name arity }`
29-
# * out neighbours: `:warn`
23+
# * in neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
3024
#
3125
# * `{ name, arity }`
3226
# * in neighbours: `:local`, `{ name, arity }`
@@ -92,15 +86,6 @@ defmodule Module.DispatchTracker do
9286
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
9387
end
9488

95-
@doc """
96-
Returns all the modules which were imported.
97-
"""
98-
@spec imports(ref) :: [module]
99-
def imports(ref) do
100-
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
101-
:digraph.out_neighbours(d, :import)
102-
end
103-
10489
@doc """
10590
Returns all imported modules that had the given
10691
`{ name, arity }` invoked.
@@ -111,24 +96,6 @@ defmodule Module.DispatchTracker do
11196
:digraph.out_neighbours(d, { :import, name, arity })
11297
end
11398

114-
@doc """
115-
Returns all the aliases defined in the given module.
116-
"""
117-
@spec aliases(ref) :: [module]
118-
def aliases(ref) do
119-
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
120-
:digraph.out_neighbours(d, :alias)
121-
end
122-
123-
@doc """
124-
Returns all the modules which were remotely dispatched to.
125-
"""
126-
@spec remotes(ref) :: [module]
127-
def remotes(ref) do
128-
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
129-
:digraph.out_neighbours(d, :remote)
130-
end
131-
13299
@doc """
133100
Returns all modules that had the given `{ name, arity }`
134101
invoked remotely.
@@ -206,12 +173,6 @@ defmodule Module.DispatchTracker do
206173
:gen_server.cast(pid, { :add_local, from, to })
207174
end
208175

209-
# Adds an alias.
210-
@doc false
211-
def add_alias(pid, module) when is_atom(module) do
212-
:gen_server.cast(pid, { :add_alias, module })
213-
end
214-
215176
# Adds a remote dispatch to the given target.
216177
@doc false
217178
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
@@ -224,40 +185,14 @@ defmodule Module.DispatchTracker do
224185
:gen_server.cast(pid, { :add_external, :import, function, module, target })
225186
end
226187

227-
# Associates a module with a warn. This adds the given
228-
# module and associates it with the `:import` vertex
229-
# permanently, even if warn is false.
230-
@doc false
231-
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
232-
:gen_server.cast(pid, { :add_warnable, module, warn, line })
233-
end
234-
235-
# Collect all unused imports where warn has been set to true.
236-
def collect_unused_imports(pid) do
237-
d = :gen_server.call(pid, :digraph, @timeout)
238-
warnable = :digraph.out_neighbours(d, :warn)
239-
240-
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
241-
{ mod, line }
242-
end
243-
end
244-
245-
defp get_warn_line(d, mod) do
246-
[edge] = :digraph.out_edges(d, mod)
247-
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
248-
line
249-
end
250-
251-
defp has_imports?(d, mod) do
252-
Enum.any?(:digraph.in_neighbours(d, mod), &match?({ :import, _, _ }, &1))
253-
end
254-
255188
# Yanks a local node. Returns its in and out vertices in a tuple.
256189
@doc false
257190
def yank(pid, local) do
258191
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
259192
end
260193

194+
# Reattach a previously yanked node
195+
@doc false
261196
def reattach(pid, kind, tuple, neighbours) do
262197
pid = to_pid(pid)
263198
add_definition(pid, kind, tuple)
@@ -322,10 +257,6 @@ defmodule Module.DispatchTracker do
322257
def init([]) do
323258
d = :digraph.new([:protected])
324259
:digraph.add_vertex(d, :local)
325-
:digraph.add_vertex(d, :alias)
326-
:digraph.add_vertex(d, :import)
327-
:digraph.add_vertex(d, :remote)
328-
:digraph.add_vertex(d, :warn)
329260
{ :ok, d }
330261
end
331262

@@ -353,30 +284,11 @@ defmodule Module.DispatchTracker do
353284
{ :noreply, d }
354285
end
355286

356-
def handle_cast({ :add_alias, module }, d) do
357-
:digraph.add_vertex(d, module)
358-
replace_edge!(d, :alias, module)
359-
{ :noreply, d }
360-
end
361-
362287
def handle_cast({ :add_external, kind, function, module, { name, arity } }, d) do
363288
handle_import_or_remote(d, kind, function, module, name, arity)
364289
{ :noreply, d }
365290
end
366291

367-
def handle_cast({ :add_warnable, module, warn, line }, d) do
368-
:digraph.add_vertex(d, module)
369-
replace_edge!(d, :import, module)
370-
371-
if warn do
372-
:digraph.add_edge(d, :warn, module, line)
373-
:digraph.add_edge(d, module, :warn, line)
374-
else
375-
:digraph.del_path(d, :warn, module)
376-
end
377-
{ :noreply, d }
378-
end
379-
380292
def handle_cast({ :add_definition, kind, tuple }, d) do
381293
handle_add_definition(d, kind, tuple)
382294
{ :noreply, d }
@@ -414,7 +326,6 @@ defmodule Module.DispatchTracker do
414326

415327
defp handle_import_or_remote(d, kind, function, module, name, arity) do
416328
:digraph.add_vertex(d, module)
417-
replace_edge!(d, kind, module)
418329

419330
tuple = { kind, name, arity }
420331
:digraph.add_vertex(d, tuple)

0 commit comments

Comments
 (0)