Skip to content

Commit 9c00059

Browse files
committed
Further optimize map differences and fix bug in map_update with negated keys
1 parent 630d5ca commit 9c00059

2 files changed

Lines changed: 71 additions & 56 deletions

File tree

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

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,9 @@ defmodule Module.Types.Descr do
839839
defp print_as_negated_bdd(bdd_leaf(_, _), _top), do: 0
840840
defp print_as_negated_bdd(bdd, top), do: if(negated_bdd?(bdd, top), do: 1, else: -100)
841841

842+
defp negated_bdd?({top, bdd, :bdd_bot, :bdd_bot}, top),
843+
do: negated_bdd?(bdd, top)
844+
842845
defp negated_bdd?({_, :bdd_bot, :bdd_bot, bdd}, top),
843846
do: bdd in [:bdd_top, top] or negated_bdd?(bdd, top)
844847

@@ -3090,17 +3093,24 @@ defmodule Module.Types.Descr do
30903093
#
30913094
# Outside of this particular scenario, the `a_int` optimization has been useful,
30923095
# but we haven't measured benefits for `a_union`.
3093-
defp map_leaf_difference(bdd_leaf(tag, fields), bdd_leaf(:open, [{key, v2}]), :none) do
3096+
defp map_leaf_difference(bdd_leaf(tag, fields), bdd_leaf(:open, [{key, v2}]), type) do
30943097
{found?, v1} =
30953098
case fields_find(key, fields) do
30963099
{:ok, value} -> {true, value}
30973100
:error -> {false, map_key_tag_to_type(tag)}
30983101
end
30993102

3100-
if tag == :closed and not found? and not is_optional_static(v2) do
3101-
:disjoint
3102-
else
3103-
map_leaf_one_key_difference(tag, fields, key, v1, v2, :none)
3103+
cond do
3104+
tag == :closed and not found? and not is_optional_static(v2) ->
3105+
:disjoint
3106+
3107+
tag == :open and not found? ->
3108+
# In case the left-side is open, we will only be adding new keys
3109+
# to the open map, which makes future eliminations harder.
3110+
:none
3111+
3112+
true ->
3113+
map_leaf_one_key_difference(tag, fields, key, v1, v2, type)
31043114
end
31053115
end
31063116

@@ -3709,8 +3719,11 @@ defmodule Module.Types.Descr do
37093719
{required_keys, optional_keys, maybe_negated_set, required_domains, optional_domains} =
37103720
split_keys
37113721

3722+
optional_keys =
3723+
((map_keys_from_negated_set(maybe_negated_set, bdd) -- optional_keys) -- required_keys) ++
3724+
optional_keys
3725+
37123726
dnf = map_bdd_to_dnf_with_empty(bdd)
3713-
bdd = map_update_put_negated(bdd, maybe_negated_set, type_fun)
37143727

37153728
{found?, value, domains, errors} =
37163729
if force? and not return_type? do
@@ -3874,29 +3887,6 @@ defmodule Module.Types.Descr do
38743887
end
38753888
end
38763889

3877-
# For keys with `not :foo`, we generate an approximation
3878-
# by adding the type to all keys, except `:foo`.
3879-
defp map_update_put_negated(bdd, nil, _type_fun), do: bdd
3880-
3881-
defp map_update_put_negated(bdd, negated, type_fun) do
3882-
bdd_map(bdd, fn {tag, fields} ->
3883-
fields =
3884-
fields_map(
3885-
fn key, value ->
3886-
if :sets.is_element(key, negated) do
3887-
value
3888-
else
3889-
{optional?, call_value} = pop_optional_static(value)
3890-
union(value, type_fun.(optional?, call_value))
3891-
end
3892-
end,
3893-
fields
3894-
)
3895-
3896-
{tag, fields}
3897-
end)
3898-
end
3899-
39003890
defp map_update_merge_atom_key(bdd, dnf) do
39013891
{_seen, acc} =
39023892
bdd_reduce(bdd, {%{}, none()}, fn {_tag, fields}, seen_acc ->
@@ -4079,8 +4069,11 @@ defmodule Module.Types.Descr do
40794069
{required_keys, optional_keys, maybe_negated_set, required_domains, optional_domains} =
40804070
split_keys
40814071

4072+
optional_keys =
4073+
((map_keys_from_negated_set(maybe_negated_set, bdd) -- optional_keys) -- required_keys) ++
4074+
optional_keys
4075+
40824076
type_fun = fn _, _ -> type end
4083-
bdd = map_update_put_negated(bdd, maybe_negated_set, type_fun)
40844077

40854078
descr =
40864079
case required_domains ++ optional_domains do
@@ -4171,7 +4164,7 @@ defmodule Module.Types.Descr do
41714164
acc = none()
41724165
acc = map_get_keys(dnf, required_keys, acc)
41734166
acc = map_get_keys(dnf, optional_keys, acc)
4174-
acc = map_get_keys(dnf, map_materialize_negated_set(maybe_negated_set, bdd), acc)
4167+
acc = map_get_keys(dnf, map_keys_from_negated_set(maybe_negated_set, bdd), acc)
41754168
acc = Enum.reduce(required_domains, acc, &map_get_domain_no_optional(dnf, &1, &2))
41764169
acc = Enum.reduce(optional_domains, acc, &map_get_domain_no_optional(dnf, &1, &2))
41774170
remove_optional(acc)
@@ -4206,9 +4199,9 @@ defmodule Module.Types.Descr do
42064199
end)
42074200
end
42084201

4209-
defp map_materialize_negated_set(nil, _bdd), do: []
4202+
defp map_keys_from_negated_set(nil, _bdd), do: []
42104203

4211-
defp map_materialize_negated_set(set, bdd) do
4204+
defp map_keys_from_negated_set(set, bdd) do
42124205
bdd
42134206
|> bdd_reduce(%{}, fn {_, fields}, acc ->
42144207
fields_fold(fields, acc, fn atom, _, acc ->
@@ -5901,7 +5894,7 @@ defmodule Module.Types.Descr do
59015894
# and the union of said keys, returning a_union and a_diff:
59025895
#
59035896
# ((a1 and C1) or U1 or (not a1 and D1)) and not a2
5904-
# (a_diff and C1) or (U1 and not a2) or (not a1 and not a2 and D1)
5897+
# (a_diff and C1) or (U1 and not a2) or (not a_union and D1)
59055898
#
59065899
defp bdd_difference({a1, c1, u1, d1} = bdd1, bdd_leaf(_, _) = bdd2, leaf_compare)
59075900
when is_tuple(bdd2) do

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

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,21 +2401,27 @@ defmodule Module.Types.DescrTest do
24012401
# Putting dynamic atom over record keys
24022402
assert map_update(closed_map(key1: binary(), key2: pid()), atom(), integer()) ==
24032403
{union(binary(), pid()),
2404-
closed_map(key1: union(integer(), binary()), key2: union(integer(), pid())),
2405-
[baddomain: atom()]}
2404+
union(
2405+
closed_map(key1: binary(), key2: integer()),
2406+
closed_map(key1: union(integer(), binary()), key2: pid())
2407+
), [baddomain: atom()]}
24062408

24072409
# ... unless forcing
24082410
assert map_update(closed_map(key1: binary(), key2: pid()), atom(), integer(), true, true) ==
24092411
{union(binary(), pid()),
2410-
closed_map([
2411-
{:key1, union(integer(), binary())},
2412-
{:key2, union(integer(), pid())},
2413-
{domain_key(:atom), integer()}
2414-
]), [baddomain: atom()]}
2412+
[
2413+
closed_map([{domain_key(:atom), integer()}, key1: binary(), key2: pid()]),
2414+
closed_map(key1: integer(), key2: pid()),
2415+
closed_map(key1: binary(), key2: integer())
2416+
]
2417+
|> Enum.reduce(&union/2), [baddomain: atom()]}
24152418

24162419
assert map_update(closed_map(key1: binary(), key2: pid()), dynamic(atom()), integer()) ==
24172420
{union(binary(), pid()),
2418-
closed_map(key1: union(integer(), binary()), key2: union(integer(), pid())), []}
2421+
union(
2422+
closed_map(key1: binary(), key2: integer()),
2423+
closed_map(key1: union(integer(), binary()), key2: pid())
2424+
), []}
24192425

24202426
# A "none()" map
24212427
assert open_map()
@@ -2464,9 +2470,13 @@ defmodule Module.Types.DescrTest do
24642470
integer()
24652471
) ==
24662472
{union(binary(), pid()),
2467-
closed_map(
2468-
[key1: binary(), key2: union(integer(), binary())] ++
2469-
[{domain_key(:atom), union(integer(), pid())}]
2473+
union(
2474+
closed_map([{domain_key(:atom), pid()}, key1: binary(), key2: integer()]),
2475+
closed_map([
2476+
{domain_key(:atom), union(pid(), integer())},
2477+
key1: binary(),
2478+
key2: binary()
2479+
])
24702480
), []}
24712481

24722482
# Missing keys
@@ -2497,6 +2507,12 @@ defmodule Module.Types.DescrTest do
24972507
{domain_key(:integer), binary()}
24982508
])
24992509
), []}
2510+
2511+
# Popping dynamic keys
2512+
non_struct_map = difference(open_map(), open_map(__struct__: atom()))
2513+
{type, descr, []} = map_update(non_struct_map, dynamic(), not_set(), true, true)
2514+
assert type == term()
2515+
assert equal?(descr, open_map(__struct__: if_set(negation(atom()))))
25002516
end
25012517
end
25022518

@@ -2639,10 +2655,12 @@ defmodule Module.Types.DescrTest do
26392655
# Putting dynamic atom over record keys
26402656
assert map_put(closed_map(key1: binary(), key2: binary()), atom(), integer()) ==
26412657
{:ok,
2642-
closed_map(
2643-
[key1: union(integer(), binary()), key2: union(integer(), binary())] ++
2644-
[{domain_key(:atom), integer()}]
2645-
)}
2658+
[
2659+
closed_map(key1: binary(), key2: integer()),
2660+
closed_map(key1: integer(), key2: binary()),
2661+
closed_map([{domain_key(:atom), integer()}, key1: binary(), key2: binary()])
2662+
]
2663+
|> Enum.reduce(&union/2)}
26462664
end
26472665

26482666
test "with mixed keys" do
@@ -2687,9 +2705,9 @@ defmodule Module.Types.DescrTest do
26872705
integer()
26882706
) ==
26892707
{:ok,
2690-
closed_map(
2691-
[key1: binary(), key2: union(binary(), integer())] ++
2692-
[{domain_key(:atom), integer()}]
2708+
union(
2709+
closed_map(key1: binary(), key2: integer()),
2710+
closed_map([{domain_key(:atom), integer()}, key1: binary(), key2: binary()])
26932711
)}
26942712

26952713
assert map_put(
@@ -2698,9 +2716,13 @@ defmodule Module.Types.DescrTest do
26982716
integer()
26992717
) ==
27002718
{:ok,
2701-
closed_map(
2702-
[key1: binary(), key2: union(integer(), binary())] ++
2703-
[{domain_key(:atom), union(integer(), pid())}]
2719+
union(
2720+
closed_map([{domain_key(:atom), pid()}, key1: binary(), key2: integer()]),
2721+
closed_map([
2722+
{domain_key(:atom), union(integer(), pid())},
2723+
key1: binary(),
2724+
key2: binary()
2725+
])
27042726
)}
27052727
end
27062728
end
@@ -3090,7 +3112,7 @@ defmodule Module.Types.DescrTest do
30903112
"%{..., Foo.Bar => float()}"
30913113

30923114
assert difference(open_map(), open_map(a: term())) |> to_quoted_string() ==
3093-
"%{..., a: not_set()}"
3115+
"map() and not %{..., a: term()}"
30943116

30953117
assert closed_map(a: integer(), b: atom()) |> to_quoted_string() ==
30963118
"%{a: integer(), b: atom()}"

0 commit comments

Comments
 (0)