Skip to content

Commit 6d7ea45

Browse files
committed
Move unreachable tracker to typing
1 parent ba4664d commit 6d7ea45

File tree

3 files changed

+103
-52
lines changed

3 files changed

+103
-52
lines changed

lib/elixir/lib/module/parallel_checker.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ defmodule Module.ParallelChecker do
4747
@doc """
4848
Spawns a process that runs the parallel checker.
4949
"""
50-
def spawn(pid_checker, module_map, log?, env) do
50+
def spawn(pid_checker, module_map, log?, private, used, env) do
5151
%{module: module, definitions: definitions, file: file} = module_map
52-
{signatures, unreachable} = Module.Types.infer(module, file, definitions, env)
52+
53+
{signatures, unreachable} =
54+
Module.Types.infer(module, file, definitions, private, used, env)
55+
5356
module_map = %{module_map | signatures: signatures, unreachable: unreachable}
5457

5558
with {pid, checker} <- pid_checker do

lib/elixir/lib/module/types.ex

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Module.Types do
1010
@no_infer [__protocol__: 1, behaviour_info: 1]
1111

1212
@doc false
13-
def infer(module, file, defs, env) do
13+
def infer(module, file, defs, private, used, env) do
1414
finder = &List.keyfind(defs, &1, 0)
1515
handler = &local_handler(&1, &2, &3, finder)
1616
stack = stack(:infer, file, module, {:__info__, 1}, :all, env, handler)
@@ -31,14 +31,55 @@ defmodule Module.Types do
3131
end
3232

3333
unreachable =
34-
for {fun_arity, kind, meta, _clauses} <- defs,
35-
kind == :defp or kind == :defmacrop,
34+
for {fun_arity, _kind, _meta, _defaults} = info <- private,
35+
maybe_warn_unused(info, local_sigs, used, env),
3636
not is_map_key(local_sigs, fun_arity),
37-
do: {fun_arity, meta}
37+
do: fun_arity
3838

3939
{Map.new(types), unreachable}
4040
end
4141

42+
defp maybe_warn_unused({_fun_arity, _kind, false, _}, _reachable, _used, _env) do
43+
:ok
44+
end
45+
46+
defp maybe_warn_unused({fun_arity, kind, meta, 0}, reachable, used, env) do
47+
case meta == false or Map.has_key?(reachable, fun_arity) or fun_arity in used do
48+
true -> :ok
49+
false -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_def, fun_arity, kind})
50+
end
51+
52+
:ok
53+
end
54+
55+
defp maybe_warn_unused({tuple, kind, meta, default}, reachable, used, env) when default > 0 do
56+
{name, arity} = tuple
57+
min = arity - default
58+
max = arity
59+
60+
case min_reachable_default(max, min, :none, name, reachable, used) do
61+
:none -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_def, tuple, kind})
62+
^min -> :ok
63+
^max -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_args, tuple})
64+
diff -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_args, tuple, diff})
65+
end
66+
67+
:ok
68+
end
69+
70+
defp min_reachable_default(max, min, last, name, reachable, used) when max >= min do
71+
fun_arity = {name, max}
72+
73+
case Map.has_key?(reachable, fun_arity) or fun_arity in used do
74+
true -> min_reachable_default(max - 1, min, max, name, reachable, used)
75+
false -> min_reachable_default(max - 1, min, last, name, reachable, used)
76+
end
77+
end
78+
79+
defp min_reachable_default(_max, _min, last, _name, _reachable, _used) do
80+
last
81+
end
82+
4283
@doc false
4384
def warnings(module, file, defs, no_warn_undefined, cache) do
4485
finder = &List.keyfind(defs, &1, 0)
@@ -247,4 +288,22 @@ defmodule Module.Types do
247288
message: "this clause of #{kind} #{fun}/#{arity} is never used"
248289
}
249290
end
291+
292+
## Module errors
293+
294+
def format_error({:unused_args, {name, arity}}),
295+
do: "default values for the optional arguments in #{name}/#{arity} are never used"
296+
297+
def format_error({:unused_args, {name, arity}, count}) when arity - count == 1,
298+
do: "the default value for the last optional argument in #{name}/#{arity} is never used"
299+
300+
def format_error({:unused_args, {name, arity}, count}),
301+
do:
302+
"the default values for the last #{arity - count} optional arguments in #{name}/#{arity} are never used"
303+
304+
def format_error({:unused_def, {name, arity}, :defp}),
305+
do: "function #{name}/#{arity} is unused"
306+
307+
def format_error({:unused_def, {name, arity}, :defmacrop}),
308+
do: "macro #{name}/#{arity} is unused"
250309
end

lib/elixir/src/elixir_module.erl

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
147147
NifsAttribute = lists:keyfind(nifs, 1, Attributes),
148148
validate_nifs_attribute(NifsAttribute, AllDefinitions, Line, E),
149149

150-
Unreachable = elixir_locals:warn_unused_local(Module, AllDefinitions, NewPrivate, E),
150+
% Unreachable = elixir_locals:warn_unused_local(Module, AllDefinitions, NewPrivate, E),
151151
elixir_locals:ensure_no_undefined_local(Module, AllDefinitions, E),
152152
elixir_locals:ensure_no_import_conflict(Module, AllDefinitions, E),
153153

@@ -160,37 +160,35 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
160160
'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
161161

162162
RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2),
163-
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, Line, E),
163+
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Line, E),
164164
Impls = bag_lookup_element(DataBag, impls, 2),
165165

166166
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
167167
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
168168
{VerifyMod, VerifyFun} <- AfterVerify],
169169

170-
%% Compute signatures only if the module is valid.
171-
case ets:member(DataSet, {elixir, taint}) of
172-
true -> elixir_errors:compile_error(E);
173-
false -> ok
174-
end,
175-
176-
ModuleMapWithoutSignatures = #{
170+
PartialModuleMap = #{
177171
struct => get_struct(DataSet),
178172
module => Module,
179173
anno => Anno,
180174
file => File,
181175
relative_file => elixir_utils:relative_to_cwd(File),
182176
attributes => Attributes,
183177
definitions => AllDefinitions,
184-
unreachable => Unreachable,
185178
after_verify => AfterVerify,
186179
compile_opts => CompileOpts,
187180
deprecated => get_deprecated(DataBag),
188181
defines_behaviour => defines_behaviour(DataBag),
189182
impls => Impls,
183+
unreachable => [],
190184
signatures => #{}
191185
},
192186

193-
ModuleMap = spawn_parallel_checker(CheckerInfo, ModuleMapWithoutSignatures, CallbackE),
187+
%% Compute signatures only if the module is valid.
188+
compile_error_if_tainted(DataSet, E),
189+
ModuleMap = spawn_parallel_checker(DataBag, CheckerInfo, PartialModuleMap, NewPrivate, E),
190+
compile_error_if_tainted(DataSet, E),
191+
194192
Binary = elixir_erl:compile(ModuleMap),
195193
Autoload = proplists:get_value(autoload, CompileOpts, true),
196194
{Binary, PersistedAttributes, Autoload}
@@ -223,41 +221,33 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
223221
elixir_code_server:call({undefmodule, Ref})
224222
end.
225223

226-
validate_compile_opts(Opts, Defs, Unreachable, Line, E) ->
227-
lists:flatmap(fun (Opt) -> validate_compile_opt(Opt, Defs, Unreachable, Line, E) end, Opts).
224+
compile_error_if_tainted(DataSet, E) ->
225+
case ets:member(DataSet, {elixir, taint}) of
226+
true -> elixir_errors:compile_error(E);
227+
false -> ok
228+
end.
229+
230+
validate_compile_opts(Opts, Defs, Line, E) ->
231+
lists:flatmap(fun (Opt) -> validate_compile_opt(Opt, Defs, Line, E) end, Opts).
228232

229233
%% TODO: Make this an error on v2.0
230-
validate_compile_opt({parse_transform, Module} = Opt, _Defs, _Unreachable, Line, E) ->
234+
validate_compile_opt({parse_transform, Module} = Opt, _Defs, Line, E) ->
231235
elixir_errors:file_warn([{line, Line}], E, ?MODULE, {parse_transform, Module}),
232236
[Opt];
233-
validate_compile_opt({inline, Inlines}, Defs, Unreachable, Line, E) ->
234-
case validate_inlines(Inlines, Defs, Unreachable, []) of
235-
{ok, []} ->
236-
[];
237-
{ok, FilteredInlines} ->
238-
[{inline, FilteredInlines}];
239-
{error, Reason} ->
240-
elixir_errors:module_error([{line, Line}], E, ?MODULE, Reason),
241-
[]
242-
end;
243-
validate_compile_opt(Opt, Defs, Unreachable, Line, E) when is_list(Opt) ->
244-
validate_compile_opts(Opt, Defs, Unreachable, Line, E);
245-
validate_compile_opt(Opt, _Defs, _Unreachable, _Line, _E) ->
246-
[Opt].
247-
248-
validate_inlines([Inline | Inlines], Defs, Unreachable, Acc) ->
249-
case lists:keyfind(Inline, 1, Defs) of
237+
validate_compile_opt({inline, Inlines} = Opt, Defs, Line, E) ->
238+
[case lists:keyfind(Inline, 1, Defs) of
250239
false ->
251-
{error, {undefined_function, {compile, inline}, Inline}};
240+
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, {compile, inline}, Inline});
252241
{_Def, Type, _Meta, _Clauses} when Type == defmacro; Type == defmacrop ->
253-
{error, {bad_macro, {compile, inline}, Inline}};
242+
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, {compile, inline}, Inline});
254243
_ ->
255-
case lists:member(Inline, Unreachable) of
256-
true -> validate_inlines(Inlines, Defs, Unreachable, Acc);
257-
false -> validate_inlines(Inlines, Defs, Unreachable, [Inline | Acc])
258-
end
259-
end;
260-
validate_inlines([], _Defs, _Unreachable, Acc) -> {ok, Acc}.
244+
ok
245+
end || Inline <- Inlines],
246+
[Opt];
247+
validate_compile_opt(Opt, Defs, Line, E) when is_list(Opt) ->
248+
validate_compile_opts(Opt, Defs, Line, E);
249+
validate_compile_opt(Opt, _Defs, _Line, _E) ->
250+
[Opt].
261251

262252
validate_on_load_attribute({on_load, Def}, Defs, Private, Line, E) ->
263253
case lists:keyfind(Def, 1, Defs) of
@@ -533,19 +523,18 @@ checker_info() ->
533523
_ -> 'Elixir.Module.ParallelChecker':get()
534524
end.
535525

536-
spawn_parallel_checker(CheckerInfo, ModuleMap, E) ->
526+
spawn_parallel_checker(DataBag, CheckerInfo, ModuleMap, Private, E) ->
537527
Log =
538528
case erlang:get(elixir_code_diagnostics) of
539529
{_, false} -> false;
540530
_ -> true
541531
end,
542532

543-
if
544-
%% We need this clause for bootstrap reasons
545-
CheckerInfo /= nil ->
546-
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, ModuleMap, Log, E);
547-
true ->
548-
ModuleMap
533+
Used = bag_lookup_element(DataBag, macro_private_calls, 2),
534+
535+
case elixir_config:is_bootstrap() of
536+
true -> ModuleMap;
537+
false -> 'Elixir.Module.ParallelChecker':spawn(CheckerInfo, ModuleMap, Log, Private, Used, E)
549538
end.
550539

551540
make_module_available(Module, Binary) ->

0 commit comments

Comments
 (0)