Skip to content

Commit 34fda89

Browse files
committed
Recompose multiple orelse as Kernel.in for performance, closes #15108
1 parent 13846bd commit 34fda89

2 files changed

Lines changed: 71 additions & 34 deletions

File tree

lib/elixir/lib/module/types/apply.ex

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ defmodule Module.Types.Apply do
367367
right_literal? = Macro.quoted_literal?(right)
368368

369369
case {left_literal?, right_literal?} do
370-
{true, false} -> custom_compare(name, right, left, expected, expr, stack, context, of_fun)
371-
{false, true} -> custom_compare(name, left, right, expected, expr, stack, context, of_fun)
370+
{true, false} -> literal_compare(name, right, left, expected, expr, stack, context, of_fun)
371+
{false, true} -> literal_compare(name, left, right, expected, expr, stack, context, of_fun)
372372
{literal?, _} -> compare(name, left, right, literal?, expr, stack, context, of_fun)
373373
end
374374
end
@@ -495,7 +495,7 @@ defmodule Module.Types.Apply do
495495
when (fun in [:length, :map_size] and is_integer(literal) and literal >= 0) or
496496
(fun in [:tuple_size] and literal in 0..15)
497497

498-
defp custom_compare(
498+
defp literal_compare(
499499
name,
500500
{{:., _, [:erlang, fun]}, _, [arg]} = left,
501501
literal,
@@ -547,14 +547,18 @@ defmodule Module.Types.Apply do
547547
end
548548
end
549549

550-
defp custom_compare(name, arg, literal, expected, expr, stack, context, of_fun) do
550+
defp literal_compare(name, arg, literal, expected, expr, stack, context, of_fun) do
551+
{type, context} = of_fun.(literal, term(), expr, stack, context)
552+
literal_compare(name, arg, type, singleton?(type), expected, expr, stack, context, of_fun)
553+
end
554+
555+
def literal_compare(name, arg, type, singleton?, expected, expr, stack, context, of_fun) do
551556
case booleaness(expected) do
552557
booleaness when booleaness in [:maybe_both, :none] ->
553-
compare(name, arg, literal, false, expr, stack, context, of_fun)
558+
{arg_type, context} = of_fun.(arg, term(), expr, stack, context)
559+
return_compare(name, arg_type, type, boolean(), false, expr, stack, context)
554560

555561
{boolean, _maybe_or_always} ->
556-
{literal_type, context} = of_fun.(literal, term(), expr, stack, context)
557-
558562
{polarity, return} =
559563
case boolean do
560564
true -> {name in [:==, :"=:="], @atom_true}
@@ -563,28 +567,28 @@ defmodule Module.Types.Apply do
563567

564568
# This logic mirrors the code in `Pattern.of_pattern_tree`
565569
# If it is a singleton, we can always be precise
566-
if singleton?(literal_type) do
567-
expected = if polarity, do: literal_type, else: negation(literal_type)
570+
if singleton? do
571+
expected = if polarity, do: type, else: negation(type)
568572
{arg_type, context} = of_fun.(arg, expected, expr, stack, context)
569573
result = if subtype?(arg_type, expected), do: return, else: boolean()
570574

571575
# Because reverse polarity means we will infer negated types
572576
# (which are naturally disjoint), we skip checks in such cases
573577
skip_check? = not polarity
574-
return_compare(name, arg_type, literal_type, result, skip_check?, expr, stack, context)
578+
return_compare(name, arg_type, type, result, skip_check?, expr, stack, context)
575579
else
576580
expected =
577581
cond do
578582
# We are checking for `not x == 1` or similar, we can't say anything about x
579583
polarity == false -> term()
580584
# We are checking for `x == 1`, make sure x is integer or float
581-
name in [:==, :"/="] -> numberize(literal_type)
585+
name in [:==, :"/="] -> numberize(type)
582586
# Otherwise we have the literal type as is
583-
true -> literal_type
587+
true -> type
584588
end
585589

586590
{arg_type, context} = of_fun.(arg, expected, expr, stack, context)
587-
return_compare(name, arg_type, literal_type, boolean(), false, expr, stack, context)
591+
return_compare(name, arg_type, type, boolean(), false, expr, stack, context)
588592
end
589593
end
590594
end

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

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,26 @@ defmodule Module.Types.Pattern do
10461046
of_remote(fun, args, call, expected, stack, context)
10471047
end
10481048

1049+
# This is reconstructed as part of orelse
1050+
def of_guard({{:., _, [Kernel, :in]}, _meta, [left, right]} = call, expected, _, stack, context) do
1051+
{right, context} = Enum.map_reduce(right, context, &of_guard(&1, term(), call, stack, &2))
1052+
{singleton, non_singleton} = Enum.split_with(right, &singleton?/1)
1053+
1054+
fun = fn
1055+
[], _singleton?, context ->
1056+
{none(), context}
1057+
1058+
types, singleton?, context ->
1059+
type = Enum.reduce(types, &union/2)
1060+
fun = &of_guard/5
1061+
Apply.literal_compare(:"=:=", left, type, singleton?, expected, call, stack, context, fun)
1062+
end
1063+
1064+
{singleton_type, context} = fun.(singleton, true, context)
1065+
{non_singleton_type, context} = fun.(non_singleton, false, context)
1066+
{union(singleton_type, non_singleton_type), context}
1067+
end
1068+
10491069
# var
10501070
def of_guard({_, meta, _} = var, expected, expr, stack, context) when is_var(var) do
10511071
version = Keyword.fetch!(meta, :version)
@@ -1136,27 +1156,10 @@ defmodule Module.Types.Pattern do
11361156
# If we have multiple operations in a row,
11371157
# we unpack them into a single pass, to avoid
11381158
# building nested conditional environments.
1139-
[left | right] = unpack_op(call, fun, [])
1140-
1141-
# In case the right side is a sequence of comparisons with imprecise literals, collapse them.
1142-
right =
1143-
with {{:., _, [:erlang, comp_op]}, _, [{var_name, _, var_ctx}, literal]}
1144-
when comp_op in @comp_op and is_atom(var_name) and is_atom(var_ctx) and
1145-
(is_number(literal) or is_binary(literal)) <- left,
1146-
true <-
1147-
Enum.all?(right, fn
1148-
{{:., _, [:erlang, ^comp_op]}, _, [{^var_name, _, ^var_ctx}, other_literal]}
1149-
when is_integer(literal) and is_integer(other_literal)
1150-
when is_float(literal) and is_float(other_literal)
1151-
when is_binary(literal) and is_binary(other_literal) ->
1152-
true
1153-
1154-
_ ->
1155-
false
1156-
end) do
1157-
[]
1158-
else
1159-
_ -> right
1159+
[left | right] =
1160+
case unpack_op(call, fun, []) do
1161+
entries when fun == :orelse -> reconstruct_kernel_in(entries)
1162+
entries -> entries
11601163
end
11611164

11621165
# For example, if the expected type is true for andalso, then it can
@@ -1213,6 +1216,36 @@ defmodule Module.Types.Pattern do
12131216
[other | acc]
12141217
end
12151218

1219+
defp reconstruct_kernel_in([head | tail]) do
1220+
with {{:., dot_meta, [:erlang, :"=:="]}, meta, [left, right]} <- head,
1221+
true <- Macro.quoted_literal?(right),
1222+
false <- data_size_op?(left),
1223+
{[_ | _] = entries, tail} <- reconstruct_kernel_in(tail, left, []) do
1224+
in_args = [left, [right | entries]]
1225+
[{{:., dot_meta, [Kernel, :in]}, meta, in_args} | reconstruct_kernel_in(tail)]
1226+
else
1227+
_ -> [head | reconstruct_kernel_in(tail)]
1228+
end
1229+
end
1230+
1231+
defp reconstruct_kernel_in([]), do: []
1232+
1233+
defp reconstruct_kernel_in(list, left, acc) do
1234+
with [{{:., _, [:erlang, :"=:="]}, _, [^left, right]} | tail] <- list,
1235+
true <- Macro.quoted_literal?(right) do
1236+
reconstruct_kernel_in(tail, left, [right | acc])
1237+
else
1238+
_ -> {Enum.reverse(acc), list}
1239+
end
1240+
end
1241+
1242+
defp data_size_op?({{:., _, [:erlang, op]}, _, [_]})
1243+
when op in [:length, :tuple_size, :map_size],
1244+
do: true
1245+
1246+
defp data_size_op?(_),
1247+
do: false
1248+
12161249
defp of_logical_all([head], disjoint?, expected, to_abort, stack, context) do
12171250
{type, context} = of_guard(head, expected, head, stack, context)
12181251

0 commit comments

Comments
 (0)