Skip to content

Commit

Permalink
Fix Dialyzer failures
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Nov 12, 2024
1 parent 60bd0ae commit bfebc1e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 41 deletions.
65 changes: 38 additions & 27 deletions lib/elixir/lib/module/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ defmodule Module.Types do
@no_infer [__protocol__: 1, behaviour_info: 1]

@doc false
def infer(module, file, defs, private, defmacrop, env) do
def infer(module, file, defs, private, used_private, env) do
infer_signatures? = :elixir_config.get(:infer_signatures)
defmacrop = Map.from_keys(defmacrop, [])

finder =
fn fun_arity ->
Expand Down Expand Up @@ -63,9 +62,9 @@ defmodule Module.Types do

stack = stack(:infer, file, module, {:__info__, 1}, :all, env, handler)

{types, %{local_sigs: local_sigs} = context} =
{types, %{local_sigs: reachable_sigs} = context} =
for {fun_arity, kind, meta, _clauses} = def <- defs,
kind in [:def, :defmacro] or (kind == :defmacrop and is_map_key(defmacrop, fun_arity)),
kind in [:def, :defmacro],
reduce: {[], context()} do
{types, context} ->
finder = fn _ -> {infer_mode(kind, infer_signatures?), def} end
Expand All @@ -78,20 +77,32 @@ defmodule Module.Types do
end
end

for {fun_arity, kind, meta, _clauses} = def <- defs,
kind in [:defp, :defmacrop],
reduce: context do
context ->
finder = fn _ -> {:traversal, def} end
{_kind, _inferred, context} = local_handler(meta, fun_arity, stack, context, finder)
context
end
# Now traverse all used privates to find any other private that have been used by them.
context =
%{local_sigs: used_sigs} =
for fun_arity <- used_private, reduce: context do
context ->
{_kind, _inferred, context} = local_handler([], fun_arity, stack, context, finder)
context
end

unreachable =
for {fun_arity, _kind, _meta, _defaults} = info <- private,
warn_unused_def(info, local_sigs, defmacrop, env),
not is_map_key(local_sigs, fun_arity),
do: fun_arity
{unreachable, _context} =
Enum.reduce(private, {[], context}, fn
{fun_arity, kind, _meta, _defaults} = info, {unreachable, context} ->
warn_unused_def(info, used_sigs, env)

# Find anything undefined within unused functions
{_kind, _inferred, context} = local_handler([], fun_arity, stack, context, finder)

# defp is reachable if used, defmacrop only if directly invoked
private_sigs = if kind == :defp, do: used_sigs, else: reachable_sigs

if is_map_key(private_sigs, fun_arity) do
{unreachable, context}
else
{[fun_arity | unreachable], context}
end
end)

{Map.new(types), unreachable}
end
Expand All @@ -106,25 +117,25 @@ defmodule Module.Types do
:elixir_errors.module_error(Helpers.with_span(meta, fun), env, __MODULE__, tuple)
end

defp warn_unused_def({_fun_arity, _kind, false, _}, _reachable, _used, _env) do
defp warn_unused_def({_fun_arity, _kind, false, _}, _used, _env) do
:ok
end

defp warn_unused_def({fun_arity, kind, meta, 0}, reachable, used, env) do
case is_map_key(reachable, fun_arity) or is_map_key(used, fun_arity) do
defp warn_unused_def({fun_arity, kind, meta, 0}, used, env) do
case is_map_key(used, fun_arity) do
true -> :ok
false -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_def, fun_arity, kind})
end

:ok
end

defp warn_unused_def({tuple, kind, meta, default}, reachable, used, env) when default > 0 do
defp warn_unused_def({tuple, kind, meta, default}, used, env) when default > 0 do
{name, arity} = tuple
min = arity - default
max = arity

case min_reachable_default(max, min, :none, name, reachable, used) do
case min_reachable_default(max, min, :none, name, used) do
:none -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_def, tuple, kind})
^min -> :ok
^max -> :elixir_errors.file_warn(meta, env, __MODULE__, {:unused_args, tuple})
Expand All @@ -134,16 +145,16 @@ defmodule Module.Types do
:ok
end

defp min_reachable_default(max, min, last, name, reachable, used) when max >= min do
defp min_reachable_default(max, min, last, name, used) when max >= min do
fun_arity = {name, max}

case is_map_key(reachable, fun_arity) or is_map_key(used, fun_arity) do
true -> min_reachable_default(max - 1, min, max, name, reachable, used)
false -> min_reachable_default(max - 1, min, last, name, reachable, used)
case is_map_key(used, fun_arity) do
true -> min_reachable_default(max - 1, min, max, name, used)
false -> min_reachable_default(max - 1, min, last, name, used)
end
end

defp min_reachable_default(_max, _min, last, _name, _reachable, _used) do
defp min_reachable_default(_max, _min, last, _name, _used) do
last
end

Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/src/elixir_def.erl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ invoke_local(Meta, Module, ErlName, Args, External) ->

track_defmacrop(Module, FunArity) ->
{_, Bag} = elixir_module:data_tables(Module),
ets:insert(Bag, {defmacrop_calls, FunArity}).
ets:insert(Bag, {used_private, FunArity}).

invoke_external(Meta, Mod, Name, Args, E) ->
is_map(E) andalso elixir_env:trace({require, Meta, Mod, []}, E),
Expand Down
24 changes: 11 additions & 13 deletions lib/elixir/src/elixir_module.erl
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
{AllDefinitions, Private} = elixir_def:fetch_definitions(Module, E),

OnLoadAttribute = lists:keyfind(on_load, 1, Attributes),
NewPrivate = validate_on_load_attribute(OnLoadAttribute, AllDefinitions, Private, Line, E),
validate_on_load_attribute(OnLoadAttribute, AllDefinitions, DataBag, Line, E),

DialyzerAttribute = lists:keyfind(dialyzer, 1, Attributes),
validate_dialyzer_attribute(DialyzerAttribute, AllDefinitions, Line, E),
Expand All @@ -187,8 +187,8 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
case elixir_config:is_bootstrap() of
true -> {#{}, []};
false ->
Defmacrop = bag_lookup_element(DataBag, defmacrop_calls, 2),
'Elixir.Module.Types':infer(Module, File, AllDefinitions, NewPrivate, Defmacrop, E)
UsedPrivate = bag_lookup_element(DataBag, used_private, 2),
'Elixir.Module.Types':infer(Module, File, AllDefinitions, Private, UsedPrivate, E)
end,

RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2),
Expand Down Expand Up @@ -278,7 +278,7 @@ validate_inlines([Inline | Inlines], Defs, Unreachable, Acc) ->
case lists:keyfind(Inline, 1, Defs) of
false ->
{error, {undefined_function, {compile, inline}, Inline}};
{_Def, Type, _Meta, _Clauses} when Type == defmacro; Type == defmacrop ->
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
{error, {bad_macro, {compile, inline}, Inline}};
_ ->
case lists:member(Inline, Unreachable) of
Expand All @@ -288,18 +288,16 @@ validate_inlines([Inline | Inlines], Defs, Unreachable, Acc) ->
end;
validate_inlines([], _Defs, _Unreachable, Acc) -> {ok, Acc}.

validate_on_load_attribute({on_load, Def}, Defs, Private, Line, E) ->
validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
case lists:keyfind(Def, 1, Defs) of
false ->
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def}),
Private;
{_Def, Type, _Meta, _Clauses} when Type == defmacro; Type == defmacrop ->
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def}),
Private;
_ ->
lists:keydelete(Def, 1, Private)
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
{_Def, Kind, _Meta, _Clauses} ->
(Kind == defp) andalso ets:insert(Bag, {used_private, Def})
end;
validate_on_load_attribute(false, _Defs, Private, _Line, _E) -> Private.
validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.

validate_dialyzer_attribute({dialyzer, Dialyzer}, Defs, Line, E) ->
[validate_definition({dialyzer, Key}, Fun, Defs, Line, E)
Expand Down

0 comments on commit bfebc1e

Please sign in to comment.