Skip to content

Commit d4495ae

Browse files
committed
Use different strategy to remove negations in map access
1 parent 09c90ee commit d4495ae

1 file changed

Lines changed: 100 additions & 191 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 100 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -3254,25 +3254,88 @@ defmodule Module.Types.Descr do
32543254
end
32553255

32563256
{tag, fields, negs}, acc ->
3257-
{fst, snd} = map_pop_key(tag, fields, key)
3257+
{value, bdd} = map_pop_key_bdd(tag, fields, key)
32583258

3259-
case map_split_negative_key(negs, key) do
3260-
:empty ->
3261-
acc
3262-
3263-
negative ->
3264-
negative
3265-
|> pair_make_disjoint()
3266-
|> pair_eliminate_negations_fst(fst, snd)
3267-
|> union(acc)
3268-
end
3259+
negs
3260+
|> map_split_negative_key(key, value, bdd)
3261+
|> Enum.reduce(acc, fn {value, _}, acc -> union(value, acc) end)
32693262
end)
32703263
catch
32713264
:open -> {true, term()}
32723265
else
32733266
value -> pop_optional_static(value)
32743267
end
32753268

3269+
defp map_split_negative_key(negs, key, value, bdd) do
3270+
map_split_negative(negs, value, bdd, fn neg_tag, neg_fields ->
3271+
case fields_take(key, neg_fields) do
3272+
{neg_value, neg_fields} -> {true, neg_value, map_new(neg_tag, neg_fields)}
3273+
:error -> {false, map_key_tag_to_type(neg_tag), map_new(neg_tag, neg_fields)}
3274+
end
3275+
end)
3276+
end
3277+
3278+
# Remove negatives:
3279+
# {t, s} \ {t₁, s₁} = {t \ t₁, s} ∪ {t ∩ t₁, s \ s₁}
3280+
defp map_split_negative(negs, value, bdd, take_fun) do
3281+
Enum.reduce(negs, [{value, bdd}], fn
3282+
{:open, empty}, _acc when is_fields_empty(empty) ->
3283+
throw(:empty)
3284+
3285+
{neg_tag, neg_fields}, acc ->
3286+
{found?, neg_value, neg_bdd} = take_fun.(neg_tag, neg_fields)
3287+
3288+
if not found? and neg_tag == :open do
3289+
# In case the map is open, t \ t₁ is always empty,
3290+
# so we just need to deal with the bdd.
3291+
Enum.reduce(acc, [], fn {value, bdd}, acc ->
3292+
diff_bdd = map_difference(bdd, neg_bdd)
3293+
3294+
if map_empty?(diff_bdd) do
3295+
acc
3296+
else
3297+
[{value, diff_bdd} | acc]
3298+
end
3299+
end)
3300+
else
3301+
Enum.reduce(acc, [], fn {value, bdd}, acc ->
3302+
# If the negative tag is closed, then they are likely disjoint,
3303+
# so we can drastically cut down the amount of operations.
3304+
if neg_tag == :closed and map_empty?(map_intersection(bdd, neg_bdd)) do
3305+
[{value, bdd} | acc]
3306+
else
3307+
diff_bdd = map_difference(bdd, neg_bdd)
3308+
3309+
cond do
3310+
value == neg_value or subtype?(value, neg_value) ->
3311+
if map_empty?(diff_bdd), do: acc, else: [{value, diff_bdd} | acc]
3312+
3313+
map_empty?(diff_bdd) ->
3314+
prepend_pair_unless_empty_diff(value, neg_value, bdd, acc)
3315+
3316+
true ->
3317+
prepend_pair_unless_empty_diff(value, neg_value, bdd, [{value, diff_bdd} | acc])
3318+
end
3319+
end
3320+
end)
3321+
end
3322+
end)
3323+
catch
3324+
:empty -> []
3325+
end
3326+
3327+
defp map_pop_key_bdd(tag, fields, key) do
3328+
case fields_take(key, fields) do
3329+
{value, fields} -> {value, map_new(tag, fields)}
3330+
:error -> {map_key_tag_to_type(tag), map_new(tag, fields)}
3331+
end
3332+
end
3333+
3334+
defp prepend_pair_unless_empty_diff(value, neg_value, bdd, acc) do
3335+
diff_value = difference(value, neg_value)
3336+
if empty?(diff_value), do: acc, else: [{diff_value, bdd} | acc]
3337+
end
3338+
32763339
@doc """
32773340
Returns the map converted into a list.
32783341
"""
@@ -3645,29 +3708,26 @@ defmodule Module.Types.Descr do
36453708
# Note: if initial is nil, it means the value is not required.
36463709
# So we don't compute it for performance.
36473710
defp map_dnf_pop_key_static(dnf, key, initial) do
3648-
{value, descr} =
3649-
Enum.reduce(dnf, {initial, none()}, fn
3711+
{value, bdd} =
3712+
Enum.reduce(dnf, {initial, :bdd_bot}, fn
36503713
# Optimization: if there are no negatives, we can directly remove the key.
3651-
{tag, fields, []}, {value, map} ->
3652-
{fst, snd} = map_pop_key(tag, fields, key)
3653-
{maybe_union(value, fn -> fst end), union(map, snd)}
3654-
3655-
{tag, fields, negs}, {value, map} ->
3656-
{fst, snd} = map_pop_key(tag, fields, key)
3714+
{tag, fields, []}, {value, bdd} ->
3715+
{fst, snd} = map_pop_key_bdd(tag, fields, key)
3716+
{maybe_union(value, fn -> fst end), map_union(bdd, snd)}
36573717

3658-
case map_split_negative_key(negs, key) do
3659-
:empty ->
3660-
{value, map}
3718+
{tag, fields, negs}, {value, bdd} ->
3719+
{fst, snd} = map_pop_key_bdd(tag, fields, key)
3720+
pairs = map_split_negative_key(negs, key, fst, snd)
36613721

3662-
negative ->
3663-
disjoint = pair_make_disjoint(negative)
3664-
3665-
{maybe_union(value, fn -> pair_eliminate_negations_fst(disjoint, fst, snd) end),
3666-
disjoint |> pair_eliminate_negations_snd(fst, snd) |> union(map)}
3667-
end
3722+
{maybe_union(value, fn -> Enum.reduce(pairs, none(), &union(elem(&1, 0), &2)) end),
3723+
Enum.reduce(pairs, bdd, &map_union(elem(&1, 1), &2))}
36683724
end)
36693725

3670-
{value, descr}
3726+
if bdd == :bdd_bot do
3727+
{value, %{}}
3728+
else
3729+
{value, %{map: bdd}}
3730+
end
36713731
end
36723732

36733733
# For keys with `not :foo`, we generate an approximation
@@ -3990,18 +4050,13 @@ defmodule Module.Types.Descr do
39904050
map_domain_tag_to_type(tag, domain_key) |> union(acc)
39914051

39924052
{tag_or_domains, fields, negs}, acc ->
3993-
{fst, snd} = map_pop_domain_no_optional(tag_or_domains, fields, domain_key)
3994-
3995-
case map_split_negative_domain(negs, domain_key) do
3996-
:empty ->
3997-
acc
4053+
{_found, value, bdd} = map_pop_domain_bdd(tag_or_domains, fields, domain_key)
39984054

3999-
negative ->
4000-
negative
4001-
|> pair_make_disjoint()
4002-
|> pair_eliminate_negations_fst(fst, snd)
4003-
|> union(acc)
4004-
end
4055+
negs
4056+
|> map_split_negative(value, bdd, fn neg_tag, neg_fields ->
4057+
map_pop_domain_bdd(neg_tag, neg_fields, domain_key)
4058+
end)
4059+
|> Enum.reduce(acc, fn {value, _}, acc -> union(value, acc) end)
40054060
end)
40064061
end
40074062

@@ -4258,42 +4313,17 @@ defmodule Module.Types.Descr do
42584313
end)
42594314
end
42604315

4261-
defp map_pop_key(tag, fields, key) do
4262-
case fields_take(key, fields) do
4263-
{value, fields} -> {value, %{map: map_new(tag, fields)}}
4264-
:error -> {map_key_tag_to_type(tag), %{map: map_new(tag, fields)}}
4265-
end
4266-
end
4267-
42684316
# Pop a domain type, already removing non optional.
4269-
defp map_pop_domain_no_optional(domains = %{}, fields, domain_key) do
4317+
defp map_pop_domain_bdd(domains = %{}, fields, domain_key) do
42704318
case :maps.take(domain_key, domains) do
4271-
{value, domains} -> {value, %{map: map_new(domains, fields)}}
4272-
:error -> {none(), %{map: map_new(domains, fields)}}
4319+
{value, domains} -> {true, value, map_new(domains, fields)}
4320+
:error -> {false, none(), map_new(domains, fields)}
42734321
end
42744322
end
42754323

42764324
# Open/close key
4277-
defp map_pop_domain_no_optional(tag, fields, _domain_key),
4278-
do: {map_domain_tag_to_type(tag), %{map: map_new(tag, fields)}}
4279-
4280-
defp map_split_negative_key(negs, key) do
4281-
Enum.reduce_while(negs, [], fn
4282-
# A negation with an open map means the whole thing is empty.
4283-
{:open, empty}, _acc when is_fields_empty(empty) -> {:halt, :empty}
4284-
{tag, fields}, neg_acc -> {:cont, [map_pop_key(tag, fields, key) | neg_acc]}
4285-
end)
4286-
end
4287-
4288-
defp map_split_negative_domain(negs, domain_key) do
4289-
Enum.reduce_while(negs, [], fn
4290-
{:open, empty}, _acc when is_fields_empty(empty) ->
4291-
{:halt, :empty}
4292-
4293-
{tag, fields}, neg_acc ->
4294-
{:cont, [map_pop_domain_no_optional(tag, fields, domain_key) | neg_acc]}
4295-
end)
4296-
end
4325+
defp map_pop_domain_bdd(tag, fields, _domain_key),
4326+
do: {false, map_domain_tag_to_type(tag), map_new(tag, fields)}
42974327

42984328
# Continue to eliminate negations while length of list of negs decreases
42994329
defp map_eliminate_while_negs_decrease(tag, fields, []), do: {tag, fields, []}
@@ -5767,127 +5797,6 @@ defmodule Module.Types.Descr do
57675797
defp bdd_head({lit, _, _, _}), do: lit
57685798
defp bdd_head(pair), do: pair
57695799

5770-
## Pairs
5771-
5772-
# To simplify disjunctive normal forms of e.g., map types, it is useful to
5773-
# convert them into disjunctive normal forms of pairs of types, and define
5774-
# normalization algorithms on pairs.
5775-
#
5776-
# The algorithms take a line, a list of pairs `{positive, negative}` where
5777-
# `positive` is a list of literals and `negative` is a list of negated literals.
5778-
# Positive pairs can all be intersected component-wise. Negative ones are
5779-
# eliminated iteratively.
5780-
5781-
# Eliminates negations from `{t, s} and not negative` where `negative` is a
5782-
# union of pairs disjoint on their first component.
5783-
#
5784-
# Formula:
5785-
# {t, s} and not (union<i=1..n> {t_i, s_i})
5786-
# = union<i=1..n> {t and t_i, s and not s_i}
5787-
# or {t and not (union{i=1..n} t_i), s}
5788-
#
5789-
# This eliminates all top-level negations and produces a union of pairs that
5790-
# are disjoint on their first component. The function `pair_eliminate_negations_fst`
5791-
# is optimized to only keep the first component out of those pairs.
5792-
defp pair_eliminate_negations_fst(negative, t, s) do
5793-
{pair_union, diff_of_t_i} =
5794-
Enum.reduce(negative, {none(), t}, fn {t_i, s_i}, {accu, diff_of_t_i} ->
5795-
i = intersection(t, t_i)
5796-
5797-
if empty?(i) do
5798-
{accu, diff_of_t_i}
5799-
else
5800-
diff_of_t_i = difference(diff_of_t_i, t_i)
5801-
s_diff = difference(s, s_i)
5802-
5803-
if empty?(s_diff),
5804-
do: {accu, diff_of_t_i},
5805-
else: {union(i, accu), diff_of_t_i}
5806-
end
5807-
end)
5808-
5809-
union(pair_union, diff_of_t_i)
5810-
end
5811-
5812-
# The formula above is symmetric with respect to the first and second components.
5813-
# Hence the following also holds true:
5814-
#
5815-
# {t, s} and not (union<i=1..n> {t_i, s_i})
5816-
# = union<i=1..n> {t and not t_i, s and s_i}
5817-
# or {t, s and not (union{i=1..n} s_i)}
5818-
#
5819-
# which is used to in the following function, optimized to keep the second component.
5820-
defp pair_eliminate_negations_snd(negative, t, s) do
5821-
{pair_union, diff_of_s_i} =
5822-
Enum.reduce(negative, {none(), s}, fn {t_i, s_i}, {accu, diff_of_s_i} ->
5823-
i = intersection(s, s_i)
5824-
5825-
if empty?(i) do
5826-
{accu, diff_of_s_i}
5827-
else
5828-
diff_of_s_i = difference(diff_of_s_i, s_i)
5829-
t_diff = difference(t, t_i)
5830-
5831-
if empty?(t_diff),
5832-
do: {accu, diff_of_s_i},
5833-
else: {union(i, accu), diff_of_s_i}
5834-
end
5835-
end)
5836-
5837-
union(diff_of_s_i, pair_union)
5838-
end
5839-
5840-
# Makes a union of pairs into an equivalent union of disjoint pairs.
5841-
#
5842-
# Inserts a pair of types {fst, snd} into a list of pairs that are disjoint
5843-
# on their first component. The invariant on `acc` is that its elements are
5844-
# two-to-two disjoint with the first argument's `pairs`.
5845-
#
5846-
# To insert {fst, snd} into a disjoint pairs list, we go through the list to find
5847-
# each pair whose first element has a non-empty intersection with `fst`. Then
5848-
# we decompose {fst, snd} over each such pair to produce disjoint ones, and add
5849-
# the decompositions into the accumulator.
5850-
defp pair_make_disjoint(pairs) do
5851-
Enum.reduce(pairs, [], fn {t1, t2}, acc -> add_pair_to_disjoint_list(acc, t1, t2, []) end)
5852-
end
5853-
5854-
defp add_pair_to_disjoint_list([], fst, snd, acc), do: [{fst, snd} | acc]
5855-
5856-
defp add_pair_to_disjoint_list([{s1, s2} | pairs], fst, snd, acc) do
5857-
x = intersection(fst, s1)
5858-
5859-
if empty?(x) do
5860-
add_pair_to_disjoint_list(pairs, fst, snd, [{s1, s2} | acc])
5861-
else
5862-
fst_diff = difference(fst, s1)
5863-
s1_diff = difference(s1, fst)
5864-
empty_fst_diff = empty?(fst_diff)
5865-
empty_s1_diff = empty?(s1_diff)
5866-
5867-
cond do
5868-
# if fst is a subtype of s1, the disjointedness invariant ensures we can
5869-
# add those two pairs and end the recursion
5870-
empty_fst_diff and empty_s1_diff ->
5871-
[{x, union(snd, s2)} | pairs ++ acc]
5872-
5873-
empty_fst_diff ->
5874-
[{s1_diff, s2}, {x, union(snd, s2)} | pairs ++ acc]
5875-
5876-
empty_s1_diff ->
5877-
add_pair_to_disjoint_list(pairs, fst_diff, snd, [{x, union(snd, s2)} | acc])
5878-
5879-
true ->
5880-
# case where, when comparing {fst, snd} and {s1, s2}, both (fst and not s1)
5881-
# and (s1 and not fst) are non empty. that is, there is something in fst
5882-
# that is not in s1, and something in s1 that is not in fst
5883-
add_pair_to_disjoint_list(pairs, fst_diff, snd, [
5884-
{s1_diff, s2},
5885-
{x, union(snd, s2)} | acc
5886-
])
5887-
end
5888-
end
5889-
end
5890-
58915800
## Map helpers
58925801

58935802
# Erlang maps:merge_with/3 has to preserve the order in combiner.

0 commit comments

Comments
 (0)