Skip to content

Commit 0f67706

Browse files
committed
Optimize or/and/orelse/andalso by unnesting traversals
1 parent 5b4ee56 commit 0f67706

1 file changed

Lines changed: 51 additions & 24 deletions

File tree

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

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ defmodule Module.Types.Pattern do
873873
Of.refine_body_var(var, expected, expr, stack, context)
874874
end
875875

876-
defp of_remote(fun, _meta, [left, right], call, expected, stack, context)
876+
defp of_remote(fun, _meta, _args, call, expected, stack, context)
877877
when fun in [:and, :or, :andalso, :orelse] do
878878
{both_domain, abort_domain, always_rhs?} =
879879
case fun do
@@ -883,29 +883,39 @@ defmodule Module.Types.Pattern do
883883
:or -> {@atom_false, @atom_true, true}
884884
end
885885

886+
# If we have multiple operations in a row,
887+
# we unpack them into a single pass, to avoid
888+
# building nested conditional environments.
889+
[left | right] = unpack_op(call, fun, [])
890+
886891
# For example, if the expected type is true for andalso, then it can
887892
# only be true if both clauses are executed, so we know the first
888893
# argument has to be true and the second has to be expected.
889894
if subtype?(expected, both_domain) do
890-
of_logical_both(left, both_domain, right, expected, abort_domain, call, stack, context)
895+
of_logical_all([left | right], true, both_domain, abort_domain, stack, context)
891896
else
892897
cond_context = %{context | conditional_vars: %{}}
893898

894899
# Compute the sure types, which are stored directly in the context
895-
{_type, context} = of_guard(left, boolean(), call, stack, context)
900+
{_type, context} = of_guard(left, boolean(), left, stack, context)
896901

897902
# andalso/orelse may not execute the rhs, so we cannot get sure types from it
898903
context =
899904
case always_rhs? do
900905
true ->
901-
{_, context} = of_guard(right, boolean(), call, stack, context)
902-
context
906+
Enum.reduce(right, context, fn expr, context ->
907+
{_, context} = of_guard(expr, boolean(), expr, stack, context)
908+
context
909+
end)
903910

904911
false ->
905912
context
906913
end
907914

908-
of_logical_cond(left, right, expected, abort_domain, call, stack, context, cond_context)
915+
{type, vars_conds} =
916+
of_logical_cond([left | right], true, expected, abort_domain, stack, cond_context, [])
917+
918+
{type, of_cond_vars(vars_conds, call, stack, context)}
909919
end
910920
end
911921

@@ -919,33 +929,50 @@ defmodule Module.Types.Pattern do
919929
Apply.remote_apply(info, :erlang, fun, args_types, call, stack, context)
920930
end
921931

922-
defp of_logical_both(left, left_domain, right, right_domain, to_abort, call, stack, context) do
923-
{left_type, context} = of_guard(left, left_domain, call, stack, context)
924-
{right_type, context} = of_guard(right, right_domain, call, stack, context)
932+
defp unpack_op({{:., _, [:erlang, fun]}, _, [left, right]}, fun, acc) do
933+
unpack_op(left, fun, unpack_op(right, fun, acc))
934+
end
925935

926-
if disjoint?(left_type, to_abort) do
927-
{right_type, context}
928-
else
929-
{union(to_abort, right_type), context}
936+
defp unpack_op(other, _fun, acc) do
937+
[other | acc]
938+
end
939+
940+
defp of_logical_all([head], disjoint?, expected, to_abort, stack, context) do
941+
{type, context} = of_guard(head, expected, head, stack, context)
942+
943+
case disjoint? do
944+
true -> {type, context}
945+
false -> {union(to_abort, type), context}
930946
end
931947
end
932948

933-
defp of_logical_cond(left, right, expected, to_abort, call, stack, context, cond_context) do
934-
{left_type, left_context} = of_guard(left, expected, call, stack, cond_context)
935-
{right_type, right_context} = of_guard(right, expected, call, stack, cond_context)
949+
defp of_logical_all([head | tail], disjoint?, expected, to_abort, stack, context) do
950+
{type, context} = of_guard(head, expected, head, stack, context)
951+
disjoint? = disjoint? and disjoint?(type, to_abort)
952+
of_logical_all(tail, disjoint?, expected, to_abort, stack, context)
953+
end
954+
955+
defp of_logical_cond([head], disjoint?, expected, to_abort, stack, context, acc) do
956+
{type, %{vars: vars, conditional_vars: cond_vars}} =
957+
of_guard(head, expected, head, stack, context)
936958

937-
%{vars: left_vars, conditional_vars: left_cond} = left_context
938-
%{vars: right_vars, conditional_vars: right_cond} = right_context
939-
vars_conds = [{left_vars, left_cond}, {right_vars, right_cond}]
940-
context = of_cond_vars(vars_conds, call, stack, context)
959+
acc = [{vars, cond_vars} | acc]
941960

942-
if disjoint?(left_type, to_abort) do
943-
{right_type, context}
944-
else
945-
{union(to_abort, right_type), context}
961+
case disjoint? do
962+
true -> {type, acc}
963+
false -> {union(to_abort, type), acc}
946964
end
947965
end
948966

967+
defp of_logical_cond([head | tail], disjoint?, expected, to_abort, stack, context, acc) do
968+
{type, %{vars: vars, conditional_vars: cond_vars}} =
969+
of_guard(head, expected, head, stack, context)
970+
971+
disjoint? = disjoint? and disjoint?(type, to_abort)
972+
acc = [{vars, cond_vars} | acc]
973+
of_logical_cond(tail, disjoint?, expected, to_abort, stack, context, acc)
974+
end
975+
949976
defp of_cond_vars([{vars, cond} | vars_conds], expr, stack, context) do
950977
Enum.reduce(Map.keys(cond), context, fn version, context ->
951978
if Enum.all?(vars_conds, fn {_vars, cond} -> is_map_key(cond, version) end) do

0 commit comments

Comments
 (0)