Skip to content

Commit 5b4ee56

Browse files
committed
Analyze conditional on :erlang.or/2 and :erlang.and/2
1 parent c35f651 commit 5b4ee56

2 files changed

Lines changed: 81 additions & 13 deletions

File tree

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -874,11 +874,13 @@ defmodule Module.Types.Pattern do
874874
end
875875

876876
defp of_remote(fun, _meta, [left, right], call, expected, stack, context)
877-
when fun in [:andalso, :orelse] do
878-
{both_domain, abort_domain} =
877+
when fun in [:and, :or, :andalso, :orelse] do
878+
{both_domain, abort_domain, always_rhs?} =
879879
case fun do
880-
:andalso -> {@atom_true, @atom_false}
881-
:orelse -> {@atom_false, @atom_true}
880+
:andalso -> {@atom_true, @atom_false, false}
881+
:orelse -> {@atom_false, @atom_true, false}
882+
:and -> {@atom_true, @atom_false, true}
883+
:or -> {@atom_false, @atom_true, true}
882884
end
883885

884886
# For example, if the expected type is true for andalso, then it can
@@ -887,7 +889,23 @@ defmodule Module.Types.Pattern do
887889
if subtype?(expected, both_domain) do
888890
of_logical_both(left, both_domain, right, expected, abort_domain, call, stack, context)
889891
else
890-
of_logical_cond(left, right, expected, abort_domain, call, stack, context)
892+
cond_context = %{context | conditional_vars: %{}}
893+
894+
# Compute the sure types, which are stored directly in the context
895+
{_type, context} = of_guard(left, boolean(), call, stack, context)
896+
897+
# andalso/orelse may not execute the rhs, so we cannot get sure types from it
898+
context =
899+
case always_rhs? do
900+
true ->
901+
{_, context} = of_guard(right, boolean(), call, stack, context)
902+
context
903+
904+
false ->
905+
context
906+
end
907+
908+
of_logical_cond(left, right, expected, abort_domain, call, stack, context, cond_context)
891909
end
892910
end
893911

@@ -912,13 +930,7 @@ defmodule Module.Types.Pattern do
912930
end
913931
end
914932

915-
defp of_logical_cond(left, right, expected, to_abort, call, stack, context) do
916-
cond_context = %{context | conditional_vars: %{}}
917-
918-
# First we do pass to find the surely types, which are stored directly in the context
919-
{_left_type, context} = of_guard(left, boolean(), call, stack, context)
920-
921-
# Now we find the conditional ones
933+
defp of_logical_cond(left, right, expected, to_abort, call, stack, context, cond_context) do
922934
{left_type, left_context} = of_guard(left, expected, call, stack, cond_context)
923935
{right_type, right_context} = of_guard(right, expected, call, stack, cond_context)
924936

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ defmodule Module.Types.PatternTest do
486486
dynamic(tuple([term(), term()]))
487487
end
488488

489-
test "conditional checks" do
489+
test "conditional checks (andalso/orelse)" do
490490
assert typecheck!([x], is_binary(x) or is_atom(x), x) == dynamic(union(binary(), atom()))
491491

492492
assert typecheck!([x], is_binary(x) or map_size(x) >= 0, x) ==
@@ -495,6 +495,9 @@ defmodule Module.Types.PatternTest do
495495
assert typecheck!([x, y], is_binary(x) or is_atom(y), {x, y}) ==
496496
dynamic(tuple([term(), term()]))
497497

498+
assert typecheck!([x, y], is_binary(x) or map_size(y) >= 0, {x, y}) ==
499+
dynamic(tuple([term(), term()]))
500+
498501
assert typecheck!([x], not (is_pid(x) and is_atom(x)), x) |> equal?(dynamic(term()))
499502

500503
assert typecheck!([x, y], not (is_pid(x) and is_atom(y)), {x, y}) ==
@@ -534,6 +537,59 @@ defmodule Module.Types.PatternTest do
534537
"""
535538
end
536539

540+
test "conditional checks (and/or)" do
541+
assert typecheck!([x], :erlang.or(is_binary(x), is_atom(x)), x) ==
542+
dynamic(union(binary(), atom()))
543+
544+
assert typecheck!([x], :erlang.or(is_binary(x), map_size(x) >= 0), x) ==
545+
dynamic(open_map())
546+
547+
assert typecheck!([x, y], :erlang.or(is_binary(x), is_atom(y)), {x, y}) ==
548+
dynamic(tuple([term(), term()]))
549+
550+
assert typecheck!([x, y], :erlang.or(is_binary(x), map_size(y) >= 0), {x, y}) ==
551+
dynamic(tuple([term(), open_map()]))
552+
553+
assert typecheck!([x], not :erlang.and(is_pid(x), is_atom(x)), x) |> equal?(dynamic(term()))
554+
555+
assert typecheck!([x, y], not :erlang.and(is_pid(x), is_atom(y)), {x, y}) ==
556+
dynamic(tuple([term(), term()]))
557+
558+
# Error
559+
assert typeerror!([x], :erlang.and(is_pid(x), is_atom(x)), x) == ~l"""
560+
this guard will never succeed:
561+
562+
:erlang.and(is_pid(x), is_atom(x))
563+
564+
because it returns type:
565+
566+
false
567+
568+
where "x" was given the type:
569+
570+
# type: pid()
571+
# from: types_test.ex:LINE
572+
is_pid(x)
573+
"""
574+
575+
assert typeerror!([x], :erlang.and(:erlang.or(is_binary(x), is_atom(x)), is_pid(x)), x) ==
576+
~l"""
577+
this guard will never succeed:
578+
579+
:erlang.and(:erlang.or(is_binary(x), is_atom(x)), is_pid(x))
580+
581+
because it returns type:
582+
583+
false
584+
585+
where "x" was given the type:
586+
587+
# type: dynamic(atom() or binary())
588+
# from: types_test.ex:LINE-1
589+
:erlang.or(is_binary(x), is_atom(x))
590+
"""
591+
end
592+
537593
test "domain checks" do
538594
# Regular domain check
539595
assert typecheck!([x], length(x) == 3, x) == dynamic(list(term()))

0 commit comments

Comments
 (0)