Skip to content

Commit 6a96204

Browse files
committed
Avoid false warnings on private clauses that raise (return none())
1 parent 6df3c08 commit 6a96204

File tree

2 files changed

+37
-12
lines changed

2 files changed

+37
-12
lines changed

lib/elixir/lib/module/types.ex

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,13 @@ defmodule Module.Types do
231231
not Keyword.get(meta, :from_super, false),
232232
reduce: context do
233233
context ->
234-
{_kind, _inferred, mapping} = Map.fetch!(context.local_sigs, fun_arity)
234+
{_kind, info, mapping} = Map.fetch!(context.local_sigs, fun_arity)
235235

236236
clauses_indexes =
237-
for type_index <- pending, {clause_index, ^type_index} <- mapping, do: clause_index
237+
for type_index <- pending,
238+
not skip_unused_clause?(info, type_index),
239+
{clause_index, ^type_index} <- mapping,
240+
do: clause_index
238241

239242
Enum.reduce(clauses_indexes, context, fn clause_index, context ->
240243
{meta, _args, _guards, _body} = Enum.fetch!(clauses, clause_index)
@@ -244,6 +247,20 @@ defmodule Module.Types do
244247
end
245248
end
246249

250+
defp skip_unused_clause?(info, type_index) do
251+
case info do
252+
# If an inferred clause returns an empty type, then the reverse arrow
253+
# will never propagate its domain up, which may lead to the clause never
254+
# being invoked.
255+
{:infer, _, inferred} ->
256+
{_args_types, return} = Enum.fetch!(inferred, type_index)
257+
Descr.empty?(return)
258+
259+
_ ->
260+
false
261+
end
262+
end
263+
247264
defp local_handler(_meta, fun_arity, stack, context, finder) do
248265
case context.local_sigs do
249266
%{^fun_arity => {kind, inferred, _mapping}} ->

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -361,20 +361,22 @@ defmodule Module.Types.IntegrationTest do
361361
assert_warnings(files, warnings)
362362
end
363363

364-
test "unused generated private clauses" do
364+
test "unused overridable private clauses" do
365365
files = %{
366366
"a.ex" => """
367367
defmodule A do
368368
use B
369-
def public(x), do: private(List.to_tuple(x))
369+
def public(x), do: private(x)
370+
defp private(x), do: super(List.to_tuple(x))
370371
end
371372
""",
372373
"b.ex" => """
373374
defmodule B do
374375
defmacro __using__(_) do
375-
quote generated: true do
376+
quote do
376377
defp private({:ok, ok}), do: ok
377378
defp private(:error), do: :error
379+
defoverridable private: 1
378380
end
379381
end
380382
end
@@ -384,22 +386,28 @@ defmodule Module.Types.IntegrationTest do
384386
assert_no_warnings(files)
385387
end
386388

387-
test "unused overridable private clauses" do
389+
test "unused private clauses without warnings" do
388390
files = %{
389391
"a.ex" => """
390392
defmodule A do
391393
use B
392-
def public(x), do: private(x)
393-
defp private(x), do: super(List.to_tuple(x))
394+
395+
# Not all clauses are invoked, but do not warn since they are generated
396+
def public1(x), do: generated(List.to_tuple(x))
397+
398+
# Avoid false positives caused by inference
399+
def public2(x), do: (:ok = raising_private(x))
400+
401+
defp raising_private(true), do: :ok
402+
defp raising_private(false), do: raise "oops"
394403
end
395404
""",
396405
"b.ex" => """
397406
defmodule B do
398407
defmacro __using__(_) do
399-
quote do
400-
defp private({:ok, ok}), do: ok
401-
defp private(:error), do: :error
402-
defoverridable private: 1
408+
quote generated: true do
409+
defp generated({:ok, ok}), do: ok
410+
defp generated(:error), do: :error
403411
end
404412
end
405413
end

0 commit comments

Comments
 (0)