Skip to content

Commit 7caa954

Browse files
committed
Compute redundant clauses for receive
1 parent 4c20342 commit 7caa954

3 files changed

Lines changed: 50 additions & 10 deletions

File tree

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,9 @@ defmodule Module.Types.Expr do
306306
|> dynamic_unless_static(stack)
307307
end
308308

309-
def of_expr({:case, meta, [case_expr, [{:do, clauses}]]}, expected, expr, stack, context) do
309+
def of_expr({:case, meta, [case_expr, [{:do, clauses}]]}, expected, _expr, stack, context) do
310310
{case_type, context} = of_expr(case_expr, @pending, case_expr, stack, context)
311+
info = {:case, meta, case_expr}
311312

312313
added_meta =
313314
if Macro.quoted_literal?(case_expr) do
@@ -324,7 +325,7 @@ defmodule Module.Types.Expr do
324325
else
325326
clauses
326327
end
327-
|> of_case_clauses(case_type, expected, meta, case_expr, expr, stack, context)
328+
|> of_redundant_clauses(case_type, expected, info, stack, context, none())
328329
|> dynamic_unless_static(stack)
329330
end
330331

@@ -398,7 +399,7 @@ defmodule Module.Types.Expr do
398399
acc_context
399400

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

403404
{:after, [{:->, meta, [[timeout], body]}] = after_expr}, {acc, context} ->
404405
{timeout_type, context} = of_expr(timeout, @timeout_type, after_expr, stack, context)
@@ -737,16 +738,16 @@ defmodule Module.Types.Expr do
737738
end)
738739
end
739740

740-
defp of_case_clauses(clauses, domain, expected, case_meta, case_expr, expr, stack, original) do
741+
defp of_redundant_clauses(clauses, domain, expected, clause_info, stack, original, acc) do
741742
%{failed: failed?} = original
742743

743744
{result, _previous, context} =
744-
Enum.reduce(clauses, {none(), none(), original}, fn
745+
Enum.reduce(clauses, {acc, none(), original}, fn
745746
{:->, meta, [[clause] = head, body]}, {acc, previous, context} ->
746747
{failed?, context} = reset_failed(context, failed?)
747748
{patterns, guards} = extract_head(head)
748749

749-
info = {:case, case_meta, domain, case_expr, previous}
750+
info = {clause_info, [domain], [previous]}
750751

751752
{trees, precise?, context} =
752753
Pattern.of_head(patterns, guards, [domain], [previous], info, meta, stack, context)
@@ -755,7 +756,7 @@ defmodule Module.Types.Expr do
755756
# The current clause will be easier to understand, so we prefer that
756757
{[{clause_tree, _, _}], precise?, context} =
757758
if context.failed and previous != none() do
758-
info = {:case, case_meta, domain, case_expr, nil}
759+
info = {clause_info, [domain], [none()]}
759760

760761
case Pattern.of_head(patterns, guards, [domain], info, meta, stack, context) do
761762
{_, _, %{failed: true}} = result -> result
@@ -784,7 +785,7 @@ defmodule Module.Types.Expr do
784785
end
785786
end
786787

787-
{result, context} = of_expr(body, expected, expr || body, stack, context)
788+
{result, context} = of_expr(body, expected, body, stack, context)
788789

789790
{union(result, acc), previous,
790791
context |> set_failed(failed?) |> Of.reset_vars(original)}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@ defmodule Module.Types.Pattern do
13471347
#
13481348
# $ typep head_pattern =
13491349
# :for_reduce or :with_else or :receive or :try_catch or :fn or :default or
1350-
# {:try_else, type} or {:case, meta, type, expr, previous_type}
1350+
# {:try_else, type} or {{:case, meta, expr}, [domain], [previous_type]}
13511351
#
13521352
# $ typep match_pattern =
13531353
# :with or :for or {:match, type}
@@ -1383,7 +1383,7 @@ defmodule Module.Types.Pattern do
13831383
"""}
13841384
end
13851385

1386-
defp badpattern({:case, meta, type, expr, previous_type}, pattern, _) do
1386+
defp badpattern({{:case, meta, expr}, [type], [previous_type]}, pattern, _) do
13871387
cond do
13881388
meta[:type_check] == :expr ->
13891389
error_type = if previous_type == none(), do: type, else: previous_type
@@ -1445,6 +1445,19 @@ defmodule Module.Types.Pattern do
14451445
end
14461446
end
14471447

1448+
defp badpattern({:receive, [_type], [previous_type]}, pattern, _) do
1449+
{pattern,
1450+
"""
1451+
the following clause is redundant:
1452+
1453+
#{expr_to_string(pattern) |> indent(4)} ->
1454+
1455+
previous clauses have already matched on the following types:
1456+
1457+
#{to_quoted_string(previous_type) |> indent(4)}
1458+
"""}
1459+
end
1460+
14481461
defp badpattern({:match, type}, expr, _) do
14491462
{expr,
14501463
"""

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,32 @@ defmodule Module.Types.ExprTest do
19561956
) == dynamic()
19571957
end
19581958

1959+
test "computess difference across clauses" do
1960+
assert typecheck!(
1961+
receive do
1962+
x when is_binary(x) -> :ok
1963+
y -> {:other, y}
1964+
end
1965+
) == union(atom([:ok]), dynamic(tuple([atom([:other]), negation(binary())])))
1966+
end
1967+
1968+
test "errors on redundant clauses" do
1969+
assert typeerror!(
1970+
receive do
1971+
x when is_binary(x) -> x
1972+
"foo" -> "bar"
1973+
end
1974+
) == """
1975+
the following clause is redundant:
1976+
1977+
"foo" ->
1978+
1979+
previous clauses have already matched on the following types:
1980+
1981+
binary()
1982+
"""
1983+
end
1984+
19591985
test "errors on bad timeout" do
19601986
assert typeerror!(
19611987
[x = :timeout],

0 commit comments

Comments
 (0)