Skip to content

Commit c7e86b0

Browse files
author
José Valim
committed
Start refactoring LocalsTracker to support more vertices
1 parent 99ce925 commit c7e86b0

File tree

3 files changed

+62
-20
lines changed

3 files changed

+62
-20
lines changed

lib/elixir/lib/kernel/locals_tracker.ex

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,55 @@ defmodule Kernel.LocalsTracker do
1010
@timeout 30_000
1111
@behavior :gen_server
1212

13+
# Public API
14+
15+
@doc """
16+
Returns all locals that are reachable.
17+
18+
By default, all public functions are reachable.
19+
A private function is only reachable if it has
20+
a public function that invokes it directly.
21+
"""
22+
def reachable(pid) do
23+
:gen_server.call(to_pid(pid), :reachable, @timeout)
24+
end
25+
26+
defp to_pid(pid) when is_pid(pid), do: pid
27+
defp to_pid(mod) when is_atom(mod), do: Module.get(mod, :__locals_tracker)
28+
29+
# Internal API
30+
31+
# Starts the tracker and returns its pid.
32+
@doc false
1333
def start_link do
1434
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
1535
pid
1636
end
1737

38+
# Adds a definition into the tracker. A public
39+
# definition is connected with the :local node
40+
# while a private one is left unreachable until
41+
# a call is made to.
42+
@doc false
1843
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
19-
:gen_server.cast(pid, { :vertex, kind, tuple })
44+
:gen_server.cast(pid, { :add_definition, kind, tuple })
2045
end
2146

22-
def add_dispatch(pid, from, to) do
23-
:gen_server.cast(pid, { :edge, from, to })
47+
# Adds a local dispatch to the given target.
48+
def add_local(pid, to) when is_tuple(to) do
49+
:gen_server.cast(pid, { :add_local, :local, to })
2450
end
2551

26-
def reachable(pid) do
27-
:gen_server.call(pid, :reachable, @timeout)
52+
# Adds a local dispatch from-to the given target.
53+
@doc false
54+
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
55+
:gen_server.cast(pid, { :add_local, from, to })
2856
end
2957

58+
# Collect all unused definitions based on the private
59+
# given also accounting the expected amount of default
60+
# clauses a private function have.
61+
@doc false
3062
def collect_unused(pid, private) do
3163
# Add a vertex for each private given
3264
lc { tuple, kind, _defaults } inlist private do
@@ -64,6 +96,8 @@ defmodule Kernel.LocalsTracker do
6496
end
6597
end
6698

99+
# Stops the gen server
100+
@doc false
67101
def stop(pid) do
68102
:gen_server.cast(pid, :stop)
69103
end
@@ -72,12 +106,12 @@ defmodule Kernel.LocalsTracker do
72106

73107
def init([]) do
74108
d = :digraph.new
75-
:digraph.add_vertex(d, :root)
109+
:digraph.add_vertex(d, :local)
76110
{ :ok, d }
77111
end
78112

79113
def handle_call(:reachable, _from, d) do
80-
{ :reply, reduce_reachable(d, :root, []), d }
114+
{ :reply, reduce_reachable(d, :local, []), d }
81115
end
82116

83117
def handle_call(_request, _from, d) do
@@ -88,19 +122,19 @@ defmodule Kernel.LocalsTracker do
88122
{ :noreply, d }
89123
end
90124

91-
def handle_cast({ :edge, from, to }, d) do
125+
def handle_cast({ :add_local, from, to }, d) do
92126
:digraph.add_vertex(d, to)
93127
[:"$e"|_] = :digraph.add_edge(d, from, to)
94128
{ :noreply, d }
95129
end
96130

97-
def handle_cast({ :vertex, public, tuple }, d) when public in [:def, :defmacro] do
131+
def handle_cast({ :add_definition, public, tuple }, d) when public in [:def, :defmacro] do
98132
:digraph.add_vertex(d, tuple)
99-
:digraph.add_edge(d, :root, tuple)
133+
:digraph.add_edge(d, :local, tuple)
100134
{ :noreply, d }
101135
end
102136

103-
def handle_cast({ :vertex, private, tuple }, d) when private in [:defp, :defmacrop] do
137+
def handle_cast({ :add_definition, private, tuple }, d) when private in [:defp, :defmacrop] do
104138
:digraph.add_vertex(d, tuple)
105139
{ :noreply, d }
106140
end

lib/elixir/src/elixir_locals.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ record(Tuple, #elixir_scope{function=Function})
2525
record(Tuple, #elixir_scope{module=Module, function=Function, function_kind=Kind}) ->
2626
if_tracker(Module, fun(Pid) ->
2727
?tracker:add_definition(Pid, Kind, Function),
28-
?tracker:add_dispatch(Pid, Function, Tuple),
28+
?tracker:add_local(Pid, Function, Tuple),
2929
true
3030
end).
3131

3232
record_root(Module, Tuple) ->
3333
if_tracker(Module, fun(Pid) ->
34-
?tracker:add_dispatch(Pid, root, Tuple),
34+
?tracker:add_local(Pid, Tuple),
3535
true
3636
end).
3737

lib/elixir/test/elixir/kernel/locals_tracker_test.exs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ defmodule Kernel.LocalsTrackerTest do
1919
D.add_definition(config[:pid], :defp, { :bar, 1 })
2020
end
2121

22-
test "can add dispatches", config do
22+
test "can add locals", config do
2323
D.add_definition(config[:pid], :def, { :foo, 1 })
24-
D.add_dispatch(config[:pid], { :foo, 1 }, { :bar, 1 })
24+
D.add_local(config[:pid], { :foo, 1 }, { :bar, 1 })
2525
end
2626

2727
test "public definitions are always reachable", config do
@@ -40,12 +40,20 @@ defmodule Kernel.LocalsTrackerTest do
4040
refute { :private, 2 } in D.reachable(config[:pid])
4141
end
4242

43+
test "private definitions are reachable when connected to local", config do
44+
D.add_definition(config[:pid], :defp, { :private, 1 })
45+
refute { :private, 1 } in D.reachable(config[:pid])
46+
47+
D.add_local(config[:pid], { :private, 1 })
48+
assert { :private, 1 } in D.reachable(config[:pid])
49+
end
50+
4351
test "private definitions are reachable when connected through a public one", config do
4452
D.add_definition(config[:pid], :defp, { :private, 1 })
4553
refute { :private, 1 } in D.reachable(config[:pid])
4654

4755
D.add_definition(config[:pid], :def, { :public, 1 })
48-
D.add_dispatch(config[:pid], { :public, 1 }, { :private, 1 })
56+
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
4957
assert { :private, 1 } in D.reachable(config[:pid])
5058
end
5159

@@ -59,7 +67,7 @@ defmodule Kernel.LocalsTrackerTest do
5967
unused = D.collect_unused(config[:pid], @unused)
6068
assert unused == [{ :unused_def, { :private, 1 }, :defp }]
6169

62-
D.add_dispatch(config[:pid], { :public, 1 }, { :private, 1 })
70+
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
6371
unused = D.collect_unused(config[:pid], @unused)
6472
refute unused == [{ :unused_def, { :private, 1 }, :defp }]
6573
end
@@ -74,21 +82,21 @@ defmodule Kernel.LocalsTrackerTest do
7482
unused = D.collect_unused(config[:pid], @unused)
7583
assert unused == [{ :unused_def, { :private, 3 }, :defp }]
7684

77-
D.add_dispatch(config[:pid], { :public, 1 }, { :private, 3 })
85+
D.add_local(config[:pid], { :public, 1 }, { :private, 3 })
7886
unused = D.collect_unused(config[:pid], @unused)
7987
assert unused == [{ :unused_args, { :private, 3 }}]
8088
end
8189

8290
test "private definitions with some unused default arguments", config do
8391
D.add_definition(config[:pid], :def, { :public, 1 })
84-
D.add_dispatch(config[:pid], { :public, 1 }, { :private, 1 })
92+
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
8593
unused = D.collect_unused(config[:pid], @unused)
8694
assert unused == [{ :unused_args, { :private, 3 }, 1}]
8795
end
8896

8997
test "private definitions with all used default arguments", config do
9098
D.add_definition(config[:pid], :def, { :public, 1 })
91-
D.add_dispatch(config[:pid], { :public, 1 }, { :private, 0 })
99+
D.add_local(config[:pid], { :public, 1 }, { :private, 0 })
92100
unused = D.collect_unused(config[:pid], @unused)
93101
assert unused == []
94102
end

0 commit comments

Comments
 (0)