Skip to content

Commit aed240b

Browse files
committed
Warns on redundant clauses from fn
1 parent bac6ef9 commit aed240b

3 files changed

Lines changed: 71 additions & 49 deletions

File tree

lib/elixir/lib/module/types/expr.ex

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ defmodule Module.Types.Expr do
325325
else
326326
clauses
327327
end
328-
|> of_redundant_clauses([case_type], expected, info, stack, context, none())
328+
|> of_clauses([case_type], expected, info, stack, context, none())
329329
|> dynamic_unless_static(stack)
330330
end
331331

@@ -336,10 +336,9 @@ defmodule Module.Types.Expr do
336336
domain = Enum.map(patterns, fn _ -> dynamic() end)
337337

338338
{acc, context} =
339-
of_clauses_fun(clauses, domain, @pending, nil, :fn, stack, context, [], fn
340-
trees, body, context, acc ->
341-
args = Pattern.of_domain(trees, stack, context)
342-
add_inferred(acc, args, body)
339+
of_clauses_fun(clauses, domain, @pending, :fn, stack, context, [], fn
340+
args_types, body, acc ->
341+
add_inferred(acc, args_types, body)
343342
end)
344343

345344
{fun_from_inferred_clauses(acc), context}
@@ -353,7 +352,7 @@ defmodule Module.Types.Expr do
353352
if else_block do
354353
{type, context} = of_expr(body, @pending, body, stack, original)
355354
info = {:try_else, meta, body, type}
356-
of_redundant_clauses(else_block, [type], expected, info, stack, context, none())
355+
of_clauses(else_block, [type], expected, info, stack, context, none())
357356
else
358357
of_expr(body, expected, expr, stack, original)
359358
end
@@ -378,7 +377,7 @@ defmodule Module.Types.Expr do
378377

379378
{:catch, clauses}, {acc, context} ->
380379
args = [@try_catch, dynamic()]
381-
of_redundant_clauses(clauses, args, expected, :try_catch, stack, context, acc)
380+
of_clauses(clauses, args, expected, :try_catch, stack, context, acc)
382381
end)
383382
|> dynamic_unless_static(stack)
384383

@@ -399,7 +398,7 @@ defmodule Module.Types.Expr do
399398
acc_context
400399

401400
{:do, clauses}, {acc, context} ->
402-
of_redundant_clauses(clauses, [dynamic()], expected, :receive, stack, context, acc)
401+
of_clauses(clauses, [dynamic()], expected, :receive, stack, context, acc)
403402

404403
{:after, [{:->, meta, [[timeout], body]}] = after_expr}, {acc, context} ->
405404
{timeout_type, context} = of_expr(timeout, @timeout_type, after_expr, stack, context)
@@ -427,7 +426,7 @@ defmodule Module.Types.Expr do
427426
# TODO: We need to type check against dynamic() instead of using reduce_type
428427
# because this is recursive. We need to infer the block type first.
429428
args = [dynamic()]
430-
of_redundant_clauses(block, args, expected, :for_reduce, stack, context, reduce_type)
429+
of_clauses(block, args, expected, :for_reduce, stack, context, reduce_type)
431430
else
432431
# TODO: Use the collectable protocol for the output
433432
into = Keyword.get(opts, :into, [])
@@ -683,7 +682,7 @@ defmodule Module.Types.Expr do
683682

684683
defp with_option({:else, clauses}, stack, context, _original) do
685684
{_, context} =
686-
of_clauses(clauses, [dynamic()], @pending, nil, :with_else, stack, context, none())
685+
of_clauses(clauses, [dynamic()], @pending, :with_else, stack, context, none())
687686

688687
context
689688
end
@@ -717,29 +716,12 @@ defmodule Module.Types.Expr do
717716
defp dynamic_unless_static({_, _} = output, %{mode: :static}), do: output
718717
defp dynamic_unless_static({type, context}, %{mode: _}), do: {dynamic(type), context}
719718

720-
defp of_clauses(clauses, domain, expected, expr, info, stack, context, acc) do
721-
fun = fn _trees, result, _context, acc -> union(result, acc) end
722-
of_clauses_fun(clauses, domain, expected, expr, info, stack, context, acc, fun)
719+
defp of_clauses(clauses, domain, expected, clause_info, stack, context, acc) do
720+
fun = fn _args_types, result, acc -> union(result, acc) end
721+
of_clauses_fun(clauses, domain, expected, clause_info, stack, context, acc, fun)
723722
end
724723

725-
defp of_clauses_fun(clauses, domain, expected, expr, info, stack, original, acc, fun) do
726-
%{failed: failed?} = original
727-
728-
Enum.reduce(clauses, {acc, original}, fn {:->, meta, [head, body]}, {acc, context} ->
729-
{failed?, context} = reset_failed(context, failed?)
730-
{patterns, guards} = extract_head(head)
731-
732-
{trees, _precise?, context} =
733-
Pattern.of_head(patterns, guards, domain, info, meta, stack, context)
734-
735-
{result, context} = of_expr(body, expected, expr || body, stack, context)
736-
737-
{fun.(trees, result, context, acc),
738-
context |> set_failed(failed?) |> Of.reset_vars(original)}
739-
end)
740-
end
741-
742-
defp of_redundant_clauses(clauses, domain, expected, clause_info, stack, original, acc) do
724+
defp of_clauses_fun(clauses, domain, expected, clause_info, stack, original, acc, fun) do
743725
%{failed: failed?} = original
744726

745727
{result, _previous, context} =
@@ -766,24 +748,24 @@ defmodule Module.Types.Expr do
766748
{trees, precise?, context}
767749
end
768750

751+
args_types =
752+
Enum.map(trees, fn {tree, _, _} ->
753+
Pattern.of_pattern_tree(tree, stack, context)
754+
end)
755+
769756
{previous, context} =
770757
if context.failed do
771758
{previous, context}
772759
else
773-
clause_type =
774-
Enum.map(trees, fn {tree, _, _} ->
775-
tree
776-
|> Pattern.of_pattern_tree(stack, context)
777-
|> upper_bound()
778-
end)
760+
upper_types = Enum.map(args_types, &upper_bound/1)
779761

780762
cond do
781763
stack.mode != :infer and previous != [] and
782-
Pattern.args_subtype?(clause_type, previous) ->
764+
Pattern.args_subtype?(upper_types, previous) ->
783765
{previous, Pattern.badpattern_warn(clause, info, stack, context)}
784766

785767
precise? ->
786-
{[clause_type | previous], context}
768+
{[upper_types | previous], context}
787769

788770
true ->
789771
{previous, context}
@@ -792,7 +774,7 @@ defmodule Module.Types.Expr do
792774

793775
{result, context} = of_expr(body, expected, body, stack, context)
794776

795-
{union(result, acc), previous,
777+
{fun.(args_types, result, acc), previous,
796778
context |> set_failed(failed?) |> Of.reset_vars(original)}
797779
end)
798780

lib/elixir/lib/module/types/pattern.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,9 +1375,9 @@ defmodule Module.Types.Pattern do
13751375
# $ type tag = head_pattern() or match_pattern()
13761376
#
13771377
# $ typep head_pattern =
1378-
# :with_else or :fn or :default or
1378+
# :fn or :default or
13791379
# {{:case | :try_else, meta, expr, type}, [arg], [previous]} or
1380-
# {:for_reduce | :receive | :try_catch, [arg], [previous]}
1380+
# {:for_reduce | :receive | :try_catch | :with_else | :fn, [arg], [previous]}
13811381
#
13821382
# $ typep match_pattern =
13831383
# :with or :for or {:match, type}
@@ -1515,7 +1515,8 @@ defmodule Module.Types.Pattern do
15151515
end
15161516
end
15171517

1518-
defp badpattern({op, args, previous}, _, _) when op in [:receive, :try_catch, :for_reduce] do
1518+
defp badpattern({op, args, previous}, _, _)
1519+
when op in [:receive, :try_catch, :for_reduce, :with_else, :fn] do
15191520
{args,
15201521
"""
15211522
the following clause is redundant:

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

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ defmodule Module.Types.ExprTest do
187187
[map],
188188
(fn
189189
%{a: a} = data -> %{data | b: a}
190-
data -> data
190+
%{} = data -> data
191191
end).(map)
192192
) == dynamic()
193193

@@ -202,6 +202,22 @@ defmodule Module.Types.ExprTest do
202202
) == dynamic(atom([:ok, :error]))
203203
end
204204

205+
test "warns on redundant clauses" do
206+
assert typewarn!(fn
207+
x when is_binary(x) -> x
208+
"foo" -> "bar"
209+
end)
210+
|> elem(1) == """
211+
the following clause is redundant:
212+
213+
"foo" ->
214+
215+
previous clauses have already matched on the following types:
216+
217+
binary()
218+
"""
219+
end
220+
205221
test "bad function" do
206222
assert typeerror!([%x{}, a1, a2], x.(a1, a2)) == ~l"""
207223
expected a 2-arity function on function call:
@@ -1782,7 +1798,7 @@ defmodule Module.Types.ExprTest do
17821798
atom([:non_empty_map, :maybe_empty_map])
17831799
end
17841800

1785-
test "reports error from redundant clauses" do
1801+
test "warns on redundant clauses" do
17861802
assert typeerror!(
17871803
[x],
17881804
case System.get_env(x) do
@@ -2044,7 +2060,7 @@ defmodule Module.Types.ExprTest do
20442060
) == union(atom([:ok]), dynamic(tuple([atom([:other]), negation(binary())])))
20452061
end
20462062

2047-
test "errors on redundant clauses" do
2063+
test "warns on redundant clauses" do
20482064
assert typewarn!(
20492065
receive do
20502066
x when is_binary(x) -> x
@@ -2161,7 +2177,7 @@ defmodule Module.Types.ExprTest do
21612177
) == union(atom([:ok]), dynamic(tuple([atom([:other]), negation(binary())])))
21622178
end
21632179

2164-
test "catch: errors on redundant clauses" do
2180+
test "catch: warns on redundant clauses" do
21652181
assert typewarn!(
21662182
try do
21672183
flunk("whatever")
@@ -2195,7 +2211,7 @@ defmodule Module.Types.ExprTest do
21952211
union(atom([:ok, :unused]), dynamic(tuple([atom([:other]), negation(binary())])))
21962212
end
21972213

2198-
test "else: errors on redundant clauses" do
2214+
test "else: warns on redundant clauses" do
21992215
assert typewarn!(
22002216
try do
22012217
Process.get(:x)
@@ -2583,7 +2599,7 @@ defmodule Module.Types.ExprTest do
25832599
) == dynamic(integer())
25842600
end
25852601

2586-
test ":reduce errors on redundant clauses" do
2602+
test ":reduce warns on redundant clauses" do
25872603
assert typewarn!(
25882604
[list, x],
25892605
for _ <- list, reduce: x do
@@ -2603,6 +2619,29 @@ defmodule Module.Types.ExprTest do
26032619
end
26042620
end
26052621

2622+
describe "with" do
2623+
test "warns on redundant clauses in else" do
2624+
assert typewarn!(
2625+
[x],
2626+
with false <- x do
2627+
x
2628+
else
2629+
x when is_binary(x) -> x
2630+
"foo" -> "bar"
2631+
end
2632+
)
2633+
|> elem(1) == ~l"""
2634+
the following clause is redundant:
2635+
2636+
"foo" ->
2637+
2638+
previous clauses have already matched on the following types:
2639+
2640+
binary()
2641+
"""
2642+
end
2643+
end
2644+
26062645
describe "info" do
26072646
test "__info__/1" do
26082647
assert typecheck!(GenServer.__info__(:functions)) == list(tuple([atom(), integer()]))

0 commit comments

Comments
 (0)