Skip to content

Commit 3e3ce13

Browse files
committed
Deal with raising clauses in cond
1 parent 4816d37 commit 3e3ce13

2 files changed

Lines changed: 146 additions & 21 deletions

File tree

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

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,66 @@ defmodule Module.Types.Expr do
286286
of_expr(post, expected, post, stack, context)
287287
end
288288

289+
def of_expr(
290+
{:cond, meta,
291+
[
292+
[
293+
{:do,
294+
[
295+
{:->, pos_meta, [[pos_head], pos_body]},
296+
{:->, _neg_meta, [[neg_head], neg_body]}
297+
]}
298+
]
299+
]},
300+
expected,
301+
expr,
302+
stack,
303+
acc_context
304+
)
305+
when is_atom(neg_head) and neg_head not in [false, nil] do
306+
cache_result(meta, stack, acc_context, fn ->
307+
{pos_head_type, context} =
308+
of_expr(pos_head, term(), pos_head, %{stack | reverse_arrow: :cache}, acc_context)
309+
310+
# Reset the context vars, keep warnings, as we will infer with expected truthy
311+
context = Of.reset_vars(context, acc_context)
312+
313+
context =
314+
maybe_always_or_never_match_cond(pos_head_type, pos_head, pos_meta, stack, context, false)
315+
316+
{_, truthy_context} =
317+
of_expr(pos_head, @truthy, pos_head, %{stack | reverse_arrow: :use}, context)
318+
319+
# Keep the context except the warnings, and compute the body
320+
context = reset_warnings(truthy_context, context)
321+
322+
{pos_body_type, context} =
323+
of_expr(pos_body, expected, expr, stack, context)
324+
325+
# Reset the context vars once again to compute the falsy type
326+
context = Of.reset_vars(context, acc_context)
327+
328+
{_, falsy_context} =
329+
of_expr(pos_head, @falsy, pos_head, %{stack | reverse_arrow: :use}, context)
330+
331+
context = reset_warnings(falsy_context, context)
332+
333+
{neg_body_type, context} =
334+
of_expr(neg_body, expected, expr, stack, context)
335+
336+
body_type = union(pos_body_type, neg_body_type)
337+
338+
context =
339+
cond do
340+
empty?(pos_body_type) -> Of.reset_vars(context, falsy_context)
341+
empty?(neg_body_type) -> Of.reset_vars(context, truthy_context)
342+
true -> Of.reset_vars(context, acc_context)
343+
end
344+
345+
dynamic_unless_static({body_type, context}, stack)
346+
end)
347+
end
348+
289349
def of_expr({:cond, meta, [[{:do, clauses}]]}, expected, expr, stack, context) do
290350
cache_result(meta, stack, context, fn ->
291351
{body_type, acc_context} =
@@ -298,22 +358,7 @@ defmodule Module.Types.Expr do
298358
context = Of.reset_vars(context, acc_context)
299359

300360
context =
301-
if is_warning(stack) do
302-
case truthiness(head_type) do
303-
:always_true when not last? ->
304-
warning = {:badcond, "always match", head_type, head, context}
305-
warn(__MODULE__, warning, meta, stack, context)
306-
307-
:always_false ->
308-
warning = {:badcond, "never match", head_type, head, context}
309-
warn(__MODULE__, warning, meta, stack, context)
310-
311-
_ ->
312-
context
313-
end
314-
else
315-
context
316-
end
361+
maybe_always_or_never_match_cond(head_type, head, meta, stack, context, last?)
317362

318363
{_, truthy_context} =
319364
of_expr(head, @truthy, head, %{stack | reverse_arrow: :use}, context)
@@ -327,10 +372,17 @@ defmodule Module.Types.Expr do
327372
# Reset the context vars once again to compute the falsy type
328373
context = Of.reset_vars(context, acc_context)
329374

330-
{_, falsy_context} =
331-
of_expr(head, @falsy, head, %{stack | reverse_arrow: :use}, context)
375+
context =
376+
if last? do
377+
context
378+
else
379+
{_, falsy_context} =
380+
of_expr(head, @falsy, head, %{stack | reverse_arrow: :use}, context)
381+
382+
reset_warnings(falsy_context, context)
383+
end
332384

333-
{union(body_type, acc), reset_warnings(falsy_context, context)}
385+
{union(body_type, acc), context}
334386
end)
335387

336388
dynamic_unless_static({body_type, Of.reset_vars(acc_context, context)}, stack)
@@ -380,7 +432,7 @@ defmodule Module.Types.Expr do
380432
reset_warnings(context, original)
381433
end
382434

383-
{expected, context}
435+
dynamic_unless_static({expected, context}, stack)
384436
end
385437

386438
def of_expr({:case, meta, [case_expr, [do: clauses]]}, expected, _expr, stack, base_context) do
@@ -874,6 +926,25 @@ defmodule Module.Types.Expr do
874926
defp reduce_non_empty([head | tail], acc, fun),
875927
do: reduce_non_empty(tail, fun.(head, acc, false), fun)
876928

929+
defp maybe_always_or_never_match_cond(head_type, head, meta, stack, context, last?) do
930+
if is_warning(stack) do
931+
case truthiness(head_type) do
932+
:always_true when not last? ->
933+
warning = {:badcond, "always match", head_type, head, context}
934+
warn(__MODULE__, warning, meta, stack, context)
935+
936+
:always_false ->
937+
warning = {:badcond, "never match", head_type, head, context}
938+
warn(__MODULE__, warning, meta, stack, context)
939+
940+
_ ->
941+
context
942+
end
943+
else
944+
context
945+
end
946+
end
947+
877948
defp dynamic_unless_static({_, _} = output, %{mode: :static}), do: output
878949
defp dynamic_unless_static({type, context}, %{mode: _}), do: {dynamic(type), context}
879950

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2656,7 +2656,61 @@ defmodule Module.Types.ExprTest do
26562656
"""}
26572657
end
26582658

2659-
test "refines types" do
2659+
test "refines types (2 clauses)" do
2660+
assert typecheck!(
2661+
[x],
2662+
cond do
2663+
is_binary(x) -> {:first, x}
2664+
true -> {:second, x}
2665+
end
2666+
) ==
2667+
dynamic(
2668+
union(
2669+
tuple([atom([:first]), binary()]),
2670+
tuple([atom([:second]), negation(binary())])
2671+
)
2672+
)
2673+
2674+
# Negated types do not leak through
2675+
assert typecheck!(
2676+
[x],
2677+
(
2678+
cond do
2679+
is_binary(x) -> {:first, x}
2680+
true -> {:second, x}
2681+
end
2682+
2683+
x
2684+
)
2685+
) == dynamic()
2686+
2687+
# Unless one of them raise
2688+
assert typecheck!(
2689+
[x],
2690+
(
2691+
cond do
2692+
is_binary(x) -> raise "oops"
2693+
true -> :ok
2694+
end
2695+
2696+
x
2697+
)
2698+
) == dynamic(negation(binary()))
2699+
2700+
assert typecheck!(
2701+
[x],
2702+
(
2703+
cond do
2704+
is_binary(x) -> :ok
2705+
true -> raise "oops"
2706+
end
2707+
2708+
x
2709+
)
2710+
) == dynamic(binary())
2711+
end
2712+
2713+
test "refines types (3+ clauses)" do
26602714
assert typecheck!(
26612715
[x],
26622716
cond do

0 commit comments

Comments
 (0)