Skip to content

Commit fefad03

Browse files
author
José Valim
committed
Ensure warnings are proper with yanked and reattached overridables
1 parent f76aeba commit fefad03

File tree

4 files changed

+84
-20
lines changed

4 files changed

+84
-20
lines changed

lib/elixir/lib/module.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,12 @@ defmodule Module do
374374
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
375375
clause ->
376376
:elixir_def.delete_definition(module, tuple)
377+
neighbours = Module.DispatchTracker.yank(module, tuple)
377378

378379
old = get_attribute(module, :__overridable)
379-
new = [ { tuple, { 1, clause, false } } ]
380-
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
380+
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
381+
{ count + 1, clause, neighbours, false }
382+
end, { 1, clause, neighbours, false }, old)
381383

382384
put_attribute(module, :__overridable, merged)
383385
end

lib/elixir/lib/module/dispatch_tracker.ex

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ defmodule Module.DispatchTracker do
8888
end
8989

9090
defp to_pid(pid) when is_pid(pid), do: pid
91-
defp to_pid(mod) when is_atom(mod), do: Module.get(mod, :__locals_tracker)
91+
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
9292

9393
# Internal API
9494

@@ -135,7 +135,7 @@ defmodule Module.DispatchTracker do
135135

136136
# Collect all unused imports where warn has been set to true.
137137
def collect_unused_imports(pid) do
138-
d = :gen_server.call(pid, :digraph)
138+
d = :gen_server.call(pid, :digraph, @timeout)
139139
warnable = :digraph.out_neighbours(d, :warn)
140140

141141
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
@@ -153,10 +153,22 @@ defmodule Module.DispatchTracker do
153153
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
154154
end
155155

156+
# Yanks a local node. Returns its in and out vertices in a tuple.
157+
@doc false
158+
def yank(pid, local) do
159+
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
160+
end
161+
162+
def reattach(pid, kind, tuple, neighbours) do
163+
pid = to_pid(pid)
164+
add_definition(pid, kind, tuple)
165+
:gen_server.cast(pid, { :reattach, tuple, neighbours })
166+
end
167+
156168
# Collecting all conflicting imports with the given functions
157169
@doc false
158170
def collect_imports_conflicts(pid, all_defined) do
159-
d = :gen_server.call(to_pid(pid), :digraph, @timeout)
171+
d = :gen_server.call(pid, :digraph, @timeout)
160172

161173
lc { name, arity } inlist all_defined,
162174
n = :digraph.out_neighbours(d, { :import, name, arity }),
@@ -216,6 +228,13 @@ defmodule Module.DispatchTracker do
216228
{ :ok, d }
217229
end
218230

231+
def handle_call({ :yank, local }, _from, d) do
232+
in_vertices = :digraph.in_neighbours(d, local)
233+
out_vertices = :digraph.out_neighbours(d, local)
234+
:digraph.del_vertex(d, local)
235+
{ :reply, { in_vertices, out_vertices }, d }
236+
end
237+
219238
def handle_call(:digraph, _from, d) do
220239
{ :reply, d, d }
221240
end
@@ -230,24 +249,24 @@ defmodule Module.DispatchTracker do
230249

231250
def handle_cast({ :add_local, from, to }, d) do
232251
:digraph.add_vertex(d, to)
233-
replace_edge(d, from, to)
252+
replace_edge!(d, from, to)
234253
{ :noreply, d }
235254
end
236255

237256
def handle_cast({ :add_import, module, { name, arity } }, d) do
238257
:digraph.add_vertex(d, module)
239-
replace_edge(d, :import, module)
258+
replace_edge!(d, :import, module)
240259

241260
tuple = { :import, name, arity }
242261
:digraph.add_vertex(d, tuple)
243-
replace_edge(d, tuple, module)
262+
replace_edge!(d, tuple, module)
244263

245264
{ :noreply, d }
246265
end
247266

248267
def handle_cast({ :add_warnable, module, warn, line }, d) do
249268
:digraph.add_vertex(d, module)
250-
replace_edge(d, :import, module)
269+
replace_edge!(d, :import, module)
251270

252271
if warn do
253272
:digraph.add_edge(d, :warn, module, line)
@@ -260,7 +279,7 @@ defmodule Module.DispatchTracker do
260279

261280
def handle_cast({ :add_definition, public, tuple }, d) when public in [:def, :defmacro] do
262281
:digraph.add_vertex(d, tuple)
263-
replace_edge(d, :local, tuple)
282+
replace_edge!(d, :local, tuple)
264283
{ :noreply, d }
265284
end
266285

@@ -269,6 +288,12 @@ defmodule Module.DispatchTracker do
269288
{ :noreply, d }
270289
end
271290

291+
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
292+
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
293+
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
294+
{ :noreply, d }
295+
end
296+
272297
def handle_cast(:stop, d) do
273298
{ :stop, :normal, d }
274299
end
@@ -285,9 +310,15 @@ defmodule Module.DispatchTracker do
285310
{ :ok, d }
286311
end
287312

288-
defp replace_edge(d, from, to) do
313+
defp replace_edge!(d, from, to) do
289314
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
290315
[:"$e"|_] = :digraph.add_edge(d, from, to)
291316
end
292317
end
318+
319+
defp replace_edge(d, from, to) do
320+
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
321+
:digraph.add_edge(d, from, to)
322+
end
323+
end
293324
end

lib/elixir/src/elixir_def_overridable.erl

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ overridable(Module, Value) ->
1515
is_defined(Module, Tuple) ->
1616
Overridable = overridable(Module),
1717
case orddict:find(Tuple, Overridable) of
18-
{ ok, { _, _, _ } } -> true;
18+
{ ok, { _, _, _, _ } } -> true;
1919
_ -> false
2020
end.
2121

2222
ensure_defined(Meta, Module, Tuple, S) ->
2323
case is_defined(Module, Tuple) of
24-
true -> [];
24+
true -> ok;
2525
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
2626
end.
2727

@@ -53,24 +53,26 @@ name(Module, Function) ->
5353
name(Module, Function, overridable(Module)).
5454

5555
name(_Module, { Name, _ } = Function, Overridable) ->
56-
{ Count, _, _ } = orddict:fetch(Function, Overridable),
56+
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
5757
?atom_concat([Name, " (overridable ", Count, ")"]).
5858

5959
%% Store
6060

6161
store(Module, Function, GenerateName) ->
6262
Overridable = overridable(Module),
6363
case orddict:fetch(Function, Overridable) of
64-
{ _Count, _Clause, true } -> ok;
65-
{ Count, Clause, false } ->
66-
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
64+
{ _Count, _Clause, _Neighbours, true } -> ok;
65+
{ Count, Clause, Neighbours, false } ->
66+
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
6767
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
6868

6969
{ FinalKind, FinalName } = case GenerateName of
7070
true -> { defp, name(Module, Function, Overridable) };
7171
false -> { Kind, Name }
7272
end,
7373

74+
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
75+
7476
Def = { function, Line, FinalName, Arity, Clauses },
7577
elixir_def:store_each(false, FinalKind, File, Location,
7678
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
@@ -79,13 +81,13 @@ store(Module, Function, GenerateName) ->
7981
%% Store pending declarations that were not manually made concrete.
8082

8183
store_pending(Module) ->
82-
[store(Module, X, false) || { X, { _, _, false } } <- overridable(Module),
84+
[store(Module, X, false) || { X, { _, _, _, false } } <- overridable(Module),
8385
not 'Elixir.Module':'defines?'(Module, X)].
8486

8587
%% Error handling
8688

8789
format_error({ no_super, Module, { Name, Arity } }) ->
88-
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
90+
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
8991
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
9092
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
9193
[Name, Arity, elixir_errors:inspect(Module), Joined]).

lib/elixir/test/elixir/kernel/warning_test.exs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ defmodule Kernel.WarningTest do
7575
purge Sample
7676
end
7777

78-
test :unsued_default_args do
78+
test :unused_default_args do
7979
assert capture_io(fn ->
8080
Code.eval_string """
8181
defmodule Sample1 do
@@ -171,6 +171,35 @@ defmodule Kernel.WarningTest do
171171
purge Sample
172172
end
173173

174+
test :unused_with_local_with_overridable do
175+
assert capture_io(fn ->
176+
Code.eval_string """
177+
defmodule Sample do
178+
def hello, do: world
179+
defp world, do: :ok
180+
defoverridable [hello: 0]
181+
def hello, do: :ok
182+
end
183+
"""
184+
end) =~ %r"function world/0 is unused"
185+
after
186+
purge Sample
187+
end
188+
189+
test :used_with_local_with_reattached_overridable do
190+
assert nil? capture_io(fn ->
191+
Code.eval_string """
192+
defmodule Sample do
193+
def hello, do: world
194+
defp world, do: :ok
195+
defoverridable [hello: 0, world: 0]
196+
end
197+
"""
198+
end)
199+
after
200+
purge Sample
201+
end
202+
174203
defp purge(list) when is_list(list) do
175204
Enum.each list, purge(&1)
176205
end

0 commit comments

Comments
 (0)