Skip to content

Commit 31c9c84

Browse files
committed
Improve coverage for map_update
1 parent 284b190 commit 31c9c84

File tree

2 files changed

+241
-152
lines changed

2 files changed

+241
-152
lines changed

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

Lines changed: 80 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,11 @@ defmodule Module.Types.Descr do
303303
end
304304
end
305305

306-
defp remove_optional_static(descr) do
307-
case descr do
308-
%{optional: _} -> Map.delete(descr, :optional)
309-
descr -> descr
310-
end
311-
end
306+
defp optional_static?(%{optional: _}), do: true
307+
defp optional_static?(_), do: false
308+
309+
defp remove_optional_static(%{} = descr), do: Map.delete(descr, :optional)
310+
defp remove_optional_static(descr), do: descr
312311

313312
defp optional_to_term(descr) do
314313
case descr do
@@ -2730,7 +2729,7 @@ defmodule Module.Types.Descr do
27302729
{tag, fields, negs}, acc ->
27312730
{fst, snd} = map_pop_key(tag, fields, key)
27322731

2733-
case map_split_negative(negs, key) do
2732+
case map_split_negative_key(negs, key) do
27342733
:empty ->
27352734
acc
27362735

@@ -2772,59 +2771,53 @@ defmodule Module.Types.Descr do
27722771

27732772
defp map_put_key_static(descr, _key, _type), do: descr
27742773

2775-
@doc """
2776-
Removes a key from a map type and return its type.
2777-
2778-
## Algorithm
2779-
2780-
1. Split the map type based on the presence of the key.
2781-
2. Take the second part of the split, which represents the union of all
2782-
record types where the key has been explicitly removed.
2783-
3. Intersect this with an open record type where the key is explicitly absent.
2784-
This step eliminates the key from open record types where it was implicitly present.
2785-
"""
2786-
# TODO: This should not be exposed
2787-
def map_take_key(descr, key) do
2788-
map_take_key(descr, key, none(), &intersection_static(&1, open_map([{key, not_set()}])))
2789-
end
2790-
2791-
# If initial is nil, note we don't compute the value
2774+
# Removes a key from a map type and return its type.
2775+
#
2776+
# ## Algorithm
2777+
#
2778+
# 1. Split the map type based on the presence of the key.
2779+
# 2. Take the second part of the split, which represents the union of all
2780+
# record types where the key has been explicitly removed.
2781+
# 3. Intersect this with an open record type where the key is explicitly absent.
2782+
# This step eliminates the key from open record types where it was implicitly present.
2783+
#
2784+
# Note: if initial is nil, it means the value is not required.
2785+
# So we don't compute it for performance.
27922786
defp map_take_key(:term, _key, _initial, _updater), do: :badmap
27932787

27942788
defp map_take_key(descr, key, initial, updater) when is_atom(key) do
27952789
case :maps.take(:dynamic, descr) do
27962790
:error ->
27972791
if descr_key?(descr, :map) and map_only?(descr) do
2798-
{optional?, taken, result} =
2799-
map_take_key_static(descr, key, initial)
2792+
{taken, result} = map_take_key_static(descr, key, initial)
28002793

2801-
cond do
2802-
taken == nil -> {nil, updater.(result)}
2803-
optional? or empty?(taken) -> :badkey
2804-
true -> {taken, updater.(result)}
2794+
if taken == nil do
2795+
{nil, updater.(result)}
2796+
else
2797+
{optional?, taken} = pop_optional_static(taken)
2798+
if optional? or empty?(taken), do: :badkey, else: {taken, updater.(result)}
28052799
end
28062800
else
28072801
:badmap
28082802
end
28092803

28102804
{dynamic, static} ->
28112805
if descr_key?(dynamic, :map) and map_only?(static) do
2812-
{_, dynamic_taken, dynamic_result} = map_take_key_static(dynamic, key, initial)
2813-
2814-
{static_optional?, static_taken, static_result} =
2815-
map_take_key_static(static, key, initial)
2816-
2806+
{dynamic_taken, dynamic_result} = map_take_key_static(dynamic, key, initial)
2807+
{static_taken, static_result} = map_take_key_static(static, key, initial)
28172808
result = union(dynamic(updater.(dynamic_result)), updater.(static_result))
28182809

2819-
cond do
2820-
static_taken == nil and dynamic_taken == nil ->
2821-
{nil, result}
2810+
if static_taken == nil and dynamic_taken == nil do
2811+
{nil, result}
2812+
else
2813+
{static_optional?, static_taken} = pop_optional_static(static_taken)
2814+
{_dynamic_optional?, dynamic_taken} = pop_optional_static(dynamic_taken)
28222815

2823-
static_optional? or empty?(dynamic_taken) ->
2816+
if static_optional? or empty?(dynamic_taken) do
28242817
:badkey
2825-
2826-
true ->
2818+
else
28272819
{union(dynamic(dynamic_taken), static_taken), result}
2820+
end
28282821
end
28292822
else
28302823
:badmap
@@ -2840,11 +2833,11 @@ defmodule Module.Types.Descr do
28402833

28412834
# If there is no map part to this static type, there is nothing to delete.
28422835
defp map_take_key_static(%{}, _key, initial) do
2843-
{false, initial, none()}
2836+
{initial, none()}
28442837
end
28452838

28462839
defp map_take_key_static(:term, _key, initial) do
2847-
{true, maybe_union(initial, fn -> term() end), open_map()}
2840+
{maybe_union(initial, fn -> term() end), open_map()}
28482841
end
28492842

28502843
defp map_dnf_take_key_static(dnf, key, initial) do
@@ -2858,7 +2851,7 @@ defmodule Module.Types.Descr do
28582851
{tag, fields, negs}, {value, map} ->
28592852
{fst, snd} = map_pop_key(tag, fields, key)
28602853

2861-
case map_split_negative(negs, key) do
2854+
case map_split_negative_key(negs, key) do
28622855
:empty ->
28632856
{value, map}
28642857

@@ -2870,13 +2863,7 @@ defmodule Module.Types.Descr do
28702863
end
28712864
end)
28722865

2873-
if value == nil do
2874-
# The boolean is unused when value is nil
2875-
{true, value, descr}
2876-
else
2877-
{optional?, value} = pop_optional_static(value)
2878-
{optional?, value, descr}
2879-
end
2866+
{value, descr}
28802867
end
28812868

28822869
@doc """
@@ -2902,14 +2889,15 @@ defmodule Module.Types.Descr do
29022889
case :maps.take(:dynamic, descr) do
29032890
:error ->
29042891
if descr_key?(descr, :map) and map_only?(descr) do
2905-
with {present?, _maybe_optional_value, descr} <-
2906-
map_update_static(descr, split_keys, type, fn optional?, value ->
2907-
optional? or empty?(value)
2892+
with {:ok, {_type, descr}} <-
2893+
map_update_static(descr, split_keys, type, fn type ->
2894+
{optional?, type} = pop_optional_static(type)
2895+
optional? or empty?(type)
29082896
end) do
2909-
if present? do
2910-
{:ok, descr}
2911-
else
2897+
if descr == none() do
29122898
{:baddomain, key_descr}
2899+
else
2900+
{:ok, descr}
29132901
end
29142902
end
29152903
else
@@ -2918,14 +2906,14 @@ defmodule Module.Types.Descr do
29182906

29192907
{dynamic, static} ->
29202908
if descr_key?(dynamic, :map) and map_only?(static) do
2921-
with {static_present?, _maybe_optional_static_value, static_descr} <-
2922-
map_update_static(static, split_keys, type, fn optional?, _ -> optional? end),
2923-
{dynamic_present?, _maybe_optional_dynamic_value, dynamic_descr} <-
2924-
map_update_static(dynamic, split_keys, type, fn _, value -> empty?(value) end) do
2925-
if static_present? or dynamic_present? do
2926-
{:ok, union(static_descr, dynamic(dynamic_descr))}
2927-
else
2909+
with {:ok, {_static_value, static_descr}} <-
2910+
map_update_static(static, split_keys, type, &optional_static?/1),
2911+
{:ok, {_dynamic_value, dynamic_descr}} <-
2912+
map_update_static(dynamic, split_keys, type, &empty_or_optional?/1) do
2913+
if dynamic_descr == none() do
29282914
{:baddomain, key_descr}
2915+
else
2916+
{:ok, union(static_descr, dynamic(dynamic_descr))}
29292917
end
29302918
end
29312919
else
@@ -2962,9 +2950,9 @@ defmodule Module.Types.Descr do
29622950
# initial return type. `map_update_static_keys` will then union into the
29632951
# computed type below, using the original bdd/dnf, not the one with updated domains.
29642952
descr = map_update_put_domains(bdd, required_domains ++ optional_domains, type)
2965-
{true, value, descr}
2953+
{value, descr}
29662954
else
2967-
{false, value, none()}
2955+
{value, none()}
29682956
end
29692957

29702958
map_update_static_keys(dnf, required_keys, optional_keys, type, missing_fun, acc)
@@ -2975,7 +2963,7 @@ defmodule Module.Types.Descr do
29752963
end
29762964

29772965
defp map_update_static(%{}, _split_keys, _type, _missing_fun) do
2978-
{false, none(), none()}
2966+
{:ok, {none(), none()}}
29792967
end
29802968

29812969
defp map_update_static(:term, split_keys, type, missing_fun) do
@@ -2985,28 +2973,39 @@ defmodule Module.Types.Descr do
29852973
{required_keys, optional_keys, _maybe_negated_set, required_domains, optional_domains} =
29862974
split_keys
29872975

2988-
dnf = map_bdd_to_dnf(@map_top)
2989-
acc = {required_domains != [] or optional_domains != [], term(), open_map()}
2990-
map_update_static_keys(dnf, required_keys, optional_keys, type, missing_fun, acc)
2976+
if required_domains != [] or optional_domains != [] do
2977+
{:ok, {term(), open_map()}}
2978+
else
2979+
acc = {none(), none()}
2980+
dnf = map_bdd_to_dnf(@map_top)
2981+
map_update_static_keys(dnf, required_keys, optional_keys, type, missing_fun, acc)
2982+
end
29912983
end
29922984

29932985
defp map_update_static_keys(dnf, required, optional, type, missing_fun, acc) do
29942986
acc = map_update_keys(dnf, required, type, :required, missing_fun, acc)
29952987
acc = map_update_keys(dnf, optional, type, :optional, missing_fun, acc)
2996-
acc
2988+
{:ok, acc}
29972989
catch
29982990
{:badkey, key} -> {:badkey, key}
29992991
end
30002992

30012993
defp map_update_keys(dnf, keys, type, required_or_optional, missing_fun, acc) do
3002-
Enum.reduce(keys, acc, fn key, {present?, acc_value, acc_descr} ->
3003-
{optional?, value, descr} = map_dnf_take_key_static(dnf, key, none())
3004-
missing? = missing_fun.(optional?, value)
3005-
3006-
required_or_optional == :required and missing? and throw({:badkey, key})
3007-
acc_value = union(value, acc_value)
3008-
acc_descr = union(map_put_key_static(descr, key, type), acc_descr)
3009-
{present? or not missing?, acc_value, acc_descr}
2994+
Enum.reduce(keys, acc, fn key, {acc_value, acc_descr} ->
2995+
{value, descr} = map_dnf_take_key_static(dnf, key, none())
2996+
2997+
cond do
2998+
not missing_fun.(value) ->
2999+
acc_value = union(value, acc_value)
3000+
acc_descr = union(map_put_key_static(descr, key, type), acc_descr)
3001+
{acc_value, acc_descr}
3002+
3003+
required_or_optional == :required ->
3004+
throw({:badkey, key})
3005+
3006+
true ->
3007+
{acc_value, acc_descr}
3008+
end
30103009
end)
30113010
end
30123011

@@ -3454,11 +3453,11 @@ defmodule Module.Types.Descr do
34543453
end
34553454
end
34563455

3457-
# Atom case
3456+
# Open/close key
34583457
defp map_pop_domain(tag, fields, _domain_key),
34593458
do: {map_key_tag_to_type(tag), %{map: map_new(tag, fields)}}
34603459

3461-
defp map_split_negative(negs, key) do
3460+
defp map_split_negative_key(negs, key) do
34623461
Enum.reduce_while(negs, [], fn
34633462
# A negation with an open map means the whole thing is empty.
34643463
{:open, fields}, _acc when map_size(fields) == 0 -> {:halt, :empty}

0 commit comments

Comments
 (0)