Skip to content

Commit d30f483

Browse files
committed
Add map_take to descr
1 parent 97ee5ec commit d30f483

File tree

2 files changed

+223
-115
lines changed

2 files changed

+223
-115
lines changed

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

Lines changed: 104 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ defmodule Module.Types.Descr do
8383
@boolset :sets.from_list([true, false], version: 2)
8484
def boolean(), do: %{atom: {:union, @boolset}}
8585

86-
# Map helpers
87-
#
86+
## Optional
87+
8888
# `not_set()` is a special base type that represents an not_set field in a map.
8989
# E.g., `%{a: integer(), b: not_set(), ...}` represents a map with an integer
9090
# field `a` and an not_set field `b`, and possibly other fields.
@@ -153,12 +153,15 @@ defmodule Module.Types.Descr do
153153

154154
## Set operations
155155

156-
def term_type?(:term), do: true
157-
def term_type?(descr), do: subtype_static?(unfolded_term(), Map.delete(descr, :dynamic))
158-
156+
@doc """
157+
Returns true if the type has a gradual part.
158+
"""
159159
def gradual?(:term), do: false
160160
def gradual?(descr), do: is_map_key(descr, :dynamic)
161161

162+
@doc """
163+
Returns true if hte type only has a gradual part.
164+
"""
162165
def only_gradual?(%{dynamic: _} = descr), do: map_size(descr) == 1
163166
def only_gradual?(_), do: false
164167

@@ -175,11 +178,17 @@ defmodule Module.Types.Descr do
175178
end
176179
end
177180

181+
@compile {:inline, lazy_union: 2}
182+
defp lazy_union(:term, _fun), do: :term
183+
defp lazy_union(descr, fun), do: union(descr, fun.())
184+
178185
@doc """
179186
Computes the union of two descrs.
180187
"""
181188
def union(:term, other), do: optional_to_term(other)
182189
def union(other, :term), do: optional_to_term(other)
190+
def union(none, other) when none == @none, do: other
191+
def union(other, none) when none == @none, do: other
183192

184193
def union(left, right) do
185194
left = unfold(left)
@@ -201,7 +210,6 @@ defmodule Module.Types.Descr do
201210
end
202211
end
203212

204-
@compile {:inline, union: 3}
205213
defp union(:atom, v1, v2), do: atom_union(v1, v2)
206214
defp union(:bitmap, v1, v2), do: v1 ||| v2
207215
defp union(:dynamic, v1, v2), do: dynamic_union(v1, v2)
@@ -239,7 +247,6 @@ defmodule Module.Types.Descr do
239247
end
240248

241249
# Returning 0 from the callback is taken as none() for that subtype.
242-
@compile {:inline, intersection: 3}
243250
defp intersection(:atom, v1, v2), do: atom_intersection(v1, v2)
244251
defp intersection(:bitmap, v1, v2), do: v1 &&& v2
245252
defp intersection(:dynamic, v1, v2), do: dynamic_intersection(v1, v2)
@@ -299,7 +306,6 @@ defmodule Module.Types.Descr do
299306
defp iterator_difference_static(:none, map), do: map
300307

301308
# Returning 0 from the callback is taken as none() for that subtype.
302-
@compile {:inline, difference: 3}
303309
defp difference(:atom, v1, v2), do: atom_difference(v1, v2)
304310
defp difference(:bitmap, v1, v2), do: v1 - (v1 &&& v2)
305311
defp difference(:dynamic, v1, v2), do: dynamic_difference(v1, v2)
@@ -378,7 +384,6 @@ defmodule Module.Types.Descr do
378384
end
379385
end
380386

381-
@compile {:inline, to_quoted: 2}
382387
defp to_quoted(:atom, val), do: atom_to_quoted(val)
383388
defp to_quoted(:bitmap, val), do: bitmap_to_quoted(val)
384389
defp to_quoted(:dynamic, descr), do: dynamic_to_quoted(descr)
@@ -513,6 +518,12 @@ defmodule Module.Types.Descr do
513518
end
514519
end
515520

521+
@doc """
522+
Optimized version of `not empty?(term(), type)`.
523+
"""
524+
def term_type?(:term), do: true
525+
def term_type?(descr), do: subtype_static?(unfolded_term(), Map.delete(descr, :dynamic))
526+
516527
@doc """
517528
Optimized version of `not empty?(intersection(empty_list(), type))`.
518529
"""
@@ -1061,7 +1072,7 @@ defmodule Module.Types.Descr do
10611072
end
10621073
end
10631074

1064-
# TODO: Eliminate empty lists from the union
1075+
# TODO: Eliminate empty lists from the union.
10651076
defp list_normalize(dnf), do: dnf
10661077
# Enum.filter(dnf, fn {list_type, last_type, negs} ->
10671078
# not Enum.any?(negs, fn neg -> subtype?(list_type, neg) end)
@@ -1280,7 +1291,7 @@ defmodule Module.Types.Descr do
12801291
defp map_put_static_value(descr, key, type) do
12811292
case :maps.take(:dynamic, descr) do
12821293
:error ->
1283-
if map_only?(descr) do
1294+
if descr_key?(descr, :map) and map_only?(descr) do
12841295
map_put_static_shared(descr, key, type)
12851296
else
12861297
:badmap
@@ -1306,8 +1317,8 @@ defmodule Module.Types.Descr do
13061317

13071318
# Directly inserts a key of a given type into every positive and negative map
13081319
defp map_put_static_shared(descr, key, type) do
1309-
case map_delete_static(descr, key) do
1310-
%{map: dnf} = descr ->
1320+
case map_take_static(descr, key, :term) do
1321+
{_, %{map: dnf} = descr} ->
13111322
dnf =
13121323
Enum.map(dnf, fn {tag, fields, negs} ->
13131324
{tag, Map.put(fields, key, type),
@@ -1318,7 +1329,7 @@ defmodule Module.Types.Descr do
13181329

13191330
%{descr | map: dnf}
13201331

1321-
%{} ->
1332+
{_, %{}} ->
13221333
descr
13231334
end
13241335
end
@@ -1439,6 +1450,17 @@ defmodule Module.Types.Descr do
14391450

14401451
@doc """
14411452
Removes a key from a map type.
1453+
"""
1454+
def map_delete(descr, key) do
1455+
# We pass :term as the initial value so we can avoid computing the unions.
1456+
case map_take(descr, key, :term) do
1457+
{_, descr} -> {:ok, descr}
1458+
error -> error
1459+
end
1460+
end
1461+
1462+
@doc """
1463+
Removes a key from a map type and return its type.
14421464
14431465
## Algorithm
14441466
@@ -1448,62 +1470,76 @@ defmodule Module.Types.Descr do
14481470
3. Intersect this with an open record type where the key is explicitly absent.
14491471
This step eliminates the key from open record types where it was implicitly present.
14501472
"""
1451-
def map_delete(:term, _key), do: :badmap
1473+
def map_take(descr, key) do
1474+
map_take(descr, key, none())
1475+
end
1476+
1477+
defp map_take(:term, _key, _initial), do: :badmap
14521478

1453-
def map_delete(descr, key) when is_atom(key) do
1479+
defp map_take(descr, key, initial) when is_atom(key) do
14541480
case :maps.take(:dynamic, descr) do
14551481
:error ->
1456-
# Note: the empty typ is not a valid input
1482+
# Note: the empty type is not a valid input
14571483
if descr_key?(descr, :map) and map_only?(descr) do
1458-
map_delete_static(descr, key)
1459-
|> intersection(open_map([{key, not_set()}]))
1484+
{taken, descr} = map_take_static(descr, key, initial)
1485+
{taken, intersection(descr, open_map([{key, not_set()}]))}
14601486
else
14611487
:badmap
14621488
end
14631489

14641490
{dynamic, static} ->
14651491
if descr_key?(dynamic, :map) and map_only?(static) do
1466-
dynamic_result = map_delete_static(dynamic, key)
1467-
static_result = map_delete_static(static, key)
1492+
{dynamic_taken, dynamic_result} = map_take_static(dynamic, key, initial)
1493+
{static_taken, static_result} = map_take_static(static, key, initial)
14681494

1469-
union(dynamic(dynamic_result), static_result)
1470-
|> intersection(open_map([{key, not_set()}]))
1495+
taken =
1496+
if dynamic_taken == :term,
1497+
do: dynamic(),
1498+
else: union(dynamic(dynamic_taken), static_taken)
1499+
1500+
result =
1501+
union(dynamic(dynamic_result), static_result)
1502+
|> intersection(open_map([{key, not_set()}]))
1503+
1504+
{taken, result}
14711505
else
14721506
:badmap
14731507
end
14741508
end
14751509
end
14761510

14771511
# Takes a static map type and removes a key from it.
1478-
defp map_delete_static(%{map: dnf}, key) do
1479-
Enum.reduce(dnf, none(), fn
1480-
# Optimization: if there are no negatives, we can directly remove the key.
1481-
{tag, fields, []}, acc ->
1482-
union(acc, %{map: map_new(tag, :maps.remove(key, fields))})
1512+
defp map_take_static(%{map: dnf}, key, initial) do
1513+
{value, map} =
1514+
Enum.reduce(dnf, {initial, none()}, fn
1515+
# Optimization: if there are no negatives, we can directly remove the key.
1516+
{tag, fields, []}, {taken, map} ->
1517+
{fst, snd} = map_pop_key(tag, fields, key)
1518+
{lazy_union(taken, fn -> fst end), union(map, snd)}
14831519

1484-
{tag, fields, negs}, acc ->
1485-
{fst, snd} = map_pop_key(tag, fields, key)
1520+
{tag, fields, negs}, {taken, map} ->
1521+
{fst, snd} = map_pop_key(tag, fields, key)
14861522

1487-
union(
1488-
acc,
14891523
case map_split_negative(negs, key) do
14901524
:empty ->
1491-
none()
1525+
{taken, map}
14921526

14931527
negative ->
1494-
negative |> pair_make_disjoint() |> pair_eliminate_negations_snd(fst, snd)
1528+
disjoint = pair_make_disjoint(negative)
1529+
1530+
{lazy_union(taken, fn -> pair_eliminate_negations_fst(disjoint, fst, snd) end),
1531+
disjoint |> pair_eliminate_negations_snd(fst, snd) |> union(map)}
14951532
end
1496-
)
1497-
end)
1533+
end)
1534+
1535+
{remove_optional_static(value), map}
14981536
end
14991537

1500-
defp map_delete_static(:term, key), do: open_map([{key, not_set()}])
1538+
defp map_take_static(:term, key, _initial), do: {:term, open_map([{key, not_set()}])}
15011539

15021540
# If there is no map part to this static type, there is nothing to delete.
1503-
defp map_delete_static(_type, _key), do: none()
1541+
defp map_take_static(_type, _key, initial), do: {initial, none()}
15041542

1505-
# Emptiness checking for maps.
1506-
#
15071543
# Short-circuits if it finds a non-empty map literal in the union.
15081544
# Since the algorithm is recursive, we implement the short-circuiting
15091545
# as throw/catch.
@@ -1574,10 +1610,15 @@ defmodule Module.Types.Descr do
15741610
{fst, snd} = map_pop_key(tag, fields, key)
15751611

15761612
case map_split_negative(negs, key) do
1577-
:empty -> none()
1578-
negative -> negative |> pair_make_disjoint() |> pair_eliminate_negations_fst(fst, snd)
1613+
:empty ->
1614+
acc
1615+
1616+
negative ->
1617+
negative
1618+
|> pair_make_disjoint()
1619+
|> pair_eliminate_negations_fst(fst, snd)
1620+
|> union(acc)
15791621
end
1580-
|> union(acc)
15811622
end)
15821623
end
15831624

@@ -1597,7 +1638,7 @@ defmodule Module.Types.Descr do
15971638
end
15981639

15991640
# Use heuristics to normalize a map dnf for pretty printing.
1600-
# TODO: eliminate some simple negations, those which have only zero or one key in common.
1641+
# TODO: Eliminate some simple negations, those which have only zero or one key in common.
16011642
defp map_normalize(dnf) do
16021643
dnf
16031644
|> Enum.reject(&map_empty?([&1]))
@@ -1989,10 +2030,15 @@ defmodule Module.Types.Descr do
19892030
{fst, snd} = tuple_pop_index(tag, elements, index)
19902031

19912032
case tuple_split_negative(negs, index) do
1992-
:empty -> none()
1993-
negative -> negative |> pair_make_disjoint() |> pair_eliminate_negations_fst(fst, snd)
2033+
:empty ->
2034+
acc
2035+
2036+
negative ->
2037+
negative
2038+
|> pair_make_disjoint()
2039+
|> pair_eliminate_negations_fst(fst, snd)
2040+
|> union(acc)
19942041
end
1995-
|> union(acc)
19962042
end)
19972043
end
19982044

@@ -2077,13 +2123,16 @@ defmodule Module.Types.Descr do
20772123
{tag, elements, negs}, acc ->
20782124
{fst, snd} = tuple_pop_index(tag, elements, index)
20792125

2080-
union(
2081-
acc,
2082-
case tuple_split_negative(negs, index) do
2083-
:empty -> none()
2084-
negative -> negative |> pair_make_disjoint() |> pair_eliminate_negations_snd(fst, snd)
2085-
end
2086-
)
2126+
case tuple_split_negative(negs, index) do
2127+
:empty ->
2128+
acc
2129+
2130+
negative ->
2131+
negative
2132+
|> pair_make_disjoint()
2133+
|> pair_eliminate_negations_snd(fst, snd)
2134+
|> union(acc)
2135+
end
20872136
end)
20882137
end
20892138

@@ -2181,7 +2230,7 @@ defmodule Module.Types.Descr do
21812230
end
21822231

21832232
## Pairs
2184-
#
2233+
21852234
# To simplify disjunctive normal forms of e.g., map types, it is useful to
21862235
# convert them into disjunctive normal forms of pairs of types, and define
21872236
# normalization algorithms on pairs.

0 commit comments

Comments
 (0)