Skip to content

Commit 382b5b0

Browse files
committed
Convert domains to also use orddict
1 parent 2c54f9a commit 382b5b0

5 files changed

Lines changed: 103 additions & 64 deletions

File tree

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

Lines changed: 99 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ defmodule Module.Types.Descr do
3737

3838
defmacro bdd_leaf(arg1, arg2), do: {arg1, arg2}
3939

40-
# Map fields are stored as orddicts (sorted key-value lists).
40+
# Map fields and domains are stored as orddicts (sorted key-value lists).
4141
@fields_new []
4242
defguardp is_fields_empty(fields) when fields == []
4343
defguardp fields_size(fields) when length(fields)
4444

45-
@domain_key_types [:binary, :integer, :float, :pid, :port, :reference] ++
46-
[:fun, :atom, :tuple, :map, :list]
45+
@domain_key_types :lists.sort(
46+
[:binary, :integer, :float, :pid, :port, :reference] ++
47+
[:fun, :atom, :tuple, :map, :list]
48+
)
4749

4850
# Remark: those are explicit BDD constructors. The functional constructors are `bdd_new/1` and `bdd_new/3`.
4951
@fun_top {:negation, %{}}
@@ -2649,14 +2651,14 @@ defmodule Module.Types.Descr do
26492651
defp domain_key_to_descr(:list), do: @list_top
26502652

26512653
defp map_descr(tag, pairs) do
2652-
{fields, domains, dynamic?} = map_descr_pairs(pairs, [], %{}, false)
2654+
{fields, domains, dynamic?} = map_descr_pairs(pairs, [], @fields_new, false)
26532655

26542656
map_new =
2655-
if domains != %{} do
2657+
if not is_fields_empty(domains) do
26562658
domains =
26572659
if tag == :open do
26582660
value = term_or_optional()
2659-
Enum.reduce(@domain_key_types, domains, &Map.put_new(&2, &1, value))
2661+
fields_put_all_new(domains, @domain_key_types, value)
26602662
else
26612663
domains
26622664
end
@@ -2673,11 +2675,23 @@ defmodule Module.Types.Descr do
26732675
end
26742676

26752677
defp map_put_domain(domain, domain_keys, value) when is_list(domain_keys) do
2676-
Enum.reduce(domain_keys, domain, fn key, acc when is_atom(key) ->
2677-
Map.update(acc, key, if_set(value), &union(&1, value))
2678-
end)
2678+
map_put_domain(domain, :lists.usort(domain_keys), if_set(value), value)
2679+
end
2680+
2681+
defp map_put_domain([{k1, v1} | t1], [k2 | _] = keys, initial, value) when k1 < k2 do
2682+
[{k1, v1} | map_put_domain(t1, keys, initial, value)]
2683+
end
2684+
2685+
defp map_put_domain([{k1, v1} | t1], [k1 | keys], _initial, value) do
2686+
[{k1, union(v1, value)} | map_put_domain(t1, keys, if_set(value), value)]
26792687
end
26802688

2689+
defp map_put_domain(domain, [k2 | keys], initial, value) do
2690+
[{k2, initial} | map_put_domain(domain, keys, initial, value)]
2691+
end
2692+
2693+
defp map_put_domain(domain, [], _initial, _value), do: domain
2694+
26812695
defp map_descr_pairs([{key, :term} | rest], fields, domain, dynamic?) do
26822696
case is_atom(key) do
26832697
true -> map_descr_pairs(rest, [{key, :term} | fields], domain, dynamic?)
@@ -2708,16 +2722,16 @@ defmodule Module.Types.Descr do
27082722
# Gets the default type associated to atom keys in a map.
27092723
defp map_key_tag_to_type(:open), do: term_or_optional()
27102724
defp map_key_tag_to_type(:closed), do: not_set()
2711-
defp map_key_tag_to_type(map = %{}), do: Map.get(map, :atom, not_set())
2725+
defp map_key_tag_to_type(domains), do: fields_get(domains, :atom, not_set())
27122726

27132727
# Gets the domain type association to a map.
27142728
# In this case, we already remove the optional to simplify upstream.
27152729
@compile {:inline, map_domain_tag_to_type: 1}
27162730
defp map_domain_tag_to_type(:open), do: term()
27172731
defp map_domain_tag_to_type(:closed), do: none()
27182732

2719-
defp map_domain_tag_to_type(domain = %{}, key) do
2720-
remove_optional(Map.get(domain, key, none()))
2733+
defp map_domain_tag_to_type(domain, key) when is_list(domain) do
2734+
remove_optional(fields_get(domain, key, none()))
27212735
end
27222736

27232737
defp map_domain_tag_to_type(domain, _key) do
@@ -3071,29 +3085,34 @@ defmodule Module.Types.Descr do
30713085
defp map_domain_intersection(:open, tag_or_domains), do: tag_or_domains
30723086
defp map_domain_intersection(tag_or_domains, :open), do: tag_or_domains
30733087

3074-
defp map_domain_intersection(domains1 = %{}, domains2 = %{}) do
3075-
new_domains =
3076-
for {domain_key, type1} <- domains1, reduce: %{} do
3077-
acc_domains ->
3078-
case domains2 do
3079-
%{^domain_key => type2} ->
3080-
inter = intersection(type1, type2)
3088+
defp map_domain_intersection(domains1, domains2) do
3089+
# If the explicit domains are empty, use simple atom tags
3090+
case map_domain_intersection_fields(domains1, domains2) do
3091+
[] -> :closed
3092+
new_domains -> new_domains
3093+
end
3094+
end
3095+
3096+
defp map_domain_intersection_fields([{k1, _} | t1], [{k2, _} | _] = l2) when k1 < k2 do
3097+
map_domain_intersection_fields(t1, l2)
3098+
end
30813099

3082-
if empty_or_optional?(inter) do
3083-
acc_domains
3084-
else
3085-
Map.put(acc_domains, domain_key, inter)
3086-
end
3100+
defp map_domain_intersection_fields([{k1, _} | _] = l1, [{k2, _} | t2]) when k1 > k2 do
3101+
map_domain_intersection_fields(l1, t2)
3102+
end
30873103

3088-
_ ->
3089-
acc_domains
3090-
end
3091-
end
3104+
defp map_domain_intersection_fields([{k, type1} | t1], [{_, type2} | t2]) do
3105+
inter = intersection(type1, type2)
30923106

3093-
# If the explicit domains are empty, use simple atom tags
3094-
if map_size(new_domains) == 0, do: :closed, else: new_domains
3107+
if empty_or_optional?(inter) do
3108+
map_domain_intersection_fields(t1, t2)
3109+
else
3110+
[{k, inter} | map_domain_intersection_fields(t1, t2)]
3111+
end
30953112
end
30963113

3114+
defp map_domain_intersection_fields(_, _), do: []
3115+
30973116
defp map_literal_intersection_open_closed([{k1, v1} | t1], [{k2, _} | _] = l2) when k1 < k2 do
30983117
# If the type in the open map is optional, we continue
30993118
case v1 do
@@ -3423,8 +3442,8 @@ defmodule Module.Types.Descr do
34233442
# which is filtered by `map_bdd_to_dnf_*/1`.
34243443
throw(:open)
34253444

3426-
domains = %{} ->
3427-
Enum.reduce(domains, acc, fn {domain_key, value}, acc ->
3445+
domains when is_list(domains) ->
3446+
fields_fold(domains, acc, fn domain_key, value, acc ->
34283447
value = remove_optional(value)
34293448

34303449
if empty?(value) do
@@ -3862,16 +3881,18 @@ defmodule Module.Types.Descr do
38623881
:open
38633882

38643883
:closed ->
3865-
Map.from_keys(domain_keys, if_set(type_fun.(true, none())))
3884+
fields_from_keys(domain_keys, if_set(type_fun.(true, none())))
38663885

3867-
domains = %{} ->
3886+
# Note: domain_keys may contain duplicates, so we cannot
3887+
# do a side-by-side traversal here.
3888+
domains when is_list(domains) ->
38683889
Enum.reduce(domain_keys, domains, fn domain_key, acc ->
3869-
case acc do
3870-
%{^domain_key => value} ->
3871-
%{acc | domain_key => union(value, type_fun.(true, remove_optional(value)))}
3890+
case fields_find(domain_key, acc) do
3891+
{:ok, value} ->
3892+
fields_store(domain_key, union(value, type_fun.(true, remove_optional(value))), acc)
38723893

3873-
%{} ->
3874-
Map.put(acc, domain_key, if_set(type_fun.(true, none())))
3894+
:error ->
3895+
fields_store(domain_key, if_set(type_fun.(true, none())), acc)
38753896
end
38763897
end)
38773898
end
@@ -4303,25 +4324,27 @@ defmodule Module.Types.Descr do
43034324

43044325
# An open map is a subtype iff the negative domains are all present as term_or_optional()
43054326
defp map_check_domain_keys?(:open, neg_domains) do
4306-
map_size(neg_domains) == length(@domain_key_types) and
4307-
Enum.all?(neg_domains, fn {_domain_key, type} -> subtype?(term_or_optional(), type) end)
4327+
fields_size(neg_domains) == length(@domain_key_types) and
4328+
Enum.all?(fields_to_list(neg_domains), fn {_domain_key, type} ->
4329+
subtype?(term_or_optional(), type)
4330+
end)
43084331
end
43094332

43104333
# A positive domains is smaller than a closed map iff all its keys are empty or optional
43114334
defp map_check_domain_keys?(pos_domains, :closed) do
4312-
Enum.all?(pos_domains, fn {_domain_key, type} -> empty_or_optional?(type) end)
4335+
Enum.all?(fields_to_list(pos_domains), fn {_domain_key, type} -> empty_or_optional?(type) end)
43134336
end
43144337

43154338
# Component-wise comparison of domains
43164339
defp map_check_domain_keys?(pos_domains, neg_domains) do
4317-
Enum.all?(pos_domains, fn {domain_key, type} ->
4318-
subtype?(type, Map.get(neg_domains, domain_key, not_set()))
4340+
Enum.all?(fields_to_list(pos_domains), fn {domain_key, type} ->
4341+
subtype?(type, fields_get(neg_domains, domain_key, not_set()))
43194342
end)
43204343
end
43214344

43224345
# Pop a domain type, already removing non optional.
4323-
defp map_pop_domain_bdd(domains = %{}, fields, domain_key) do
4324-
case :maps.take(domain_key, domains) do
4346+
defp map_pop_domain_bdd(domains, fields, domain_key) when is_list(domains) do
4347+
case fields_take(domain_key, domains) do
43254348
{value, domains} -> {true, value, map_new(domains, fields)}
43264349
:error -> {false, none(), map_new(domains, fields)}
43274350
end
@@ -4551,26 +4574,26 @@ defmodule Module.Types.Descr do
45514574
end
45524575
end
45534576

4554-
def map_literal_to_quoted({:closed, empty}, _opts) when is_fields_empty(empty) do
4577+
defp map_literal_to_quoted({:closed, empty}, _opts) when is_fields_empty(empty) do
45554578
{:empty_map, [], []}
45564579
end
45574580

4558-
def map_literal_to_quoted({:open, empty}, _opts) when is_fields_empty(empty) do
4581+
defp map_literal_to_quoted({:open, empty}, _opts) when is_fields_empty(empty) do
45594582
{:map, [], []}
45604583
end
45614584

4562-
def map_literal_to_quoted({domains = %{}, empty}, _opts)
4563-
when map_size(domains) == 0 and is_fields_empty(empty) do
4585+
defp map_literal_to_quoted({domains, empty}, _opts)
4586+
when is_fields_empty(domains) and is_fields_empty(empty) do
45644587
{:empty_map, [], []}
45654588
end
45664589

4567-
def map_literal_to_quoted({:open, [{:__struct__, @not_atom_or_optional}]}, _opts) do
4590+
defp map_literal_to_quoted({:open, [{:__struct__, @not_atom_or_optional}]}, _opts) do
45684591
{:non_struct_map, [], []}
45694592
end
45704593

4571-
def map_literal_to_quoted({domains = %{}, fields}, opts) do
4594+
defp map_literal_to_quoted({domains, fields}, opts) when is_list(domains) do
45724595
domain_fields =
4573-
for {domain_key, value_type} <- domains do
4596+
for {domain_key, value_type} <- fields_to_list(domains) do
45744597
non_optional = remove_optional_static(value_type)
45754598

45764599
value_quoted =
@@ -4587,7 +4610,7 @@ defmodule Module.Types.Descr do
45874610
{:%{}, [], domain_fields ++ regular_fields_quoted}
45884611
end
45894612

4590-
def map_literal_to_quoted({tag, fields}, opts) do
4613+
defp map_literal_to_quoted({tag, fields}, opts) do
45914614
case tag do
45924615
:closed ->
45934616
with {:ok, struct_descr} <- fields_find(:__struct__, fields),
@@ -4655,12 +4678,13 @@ defmodule Module.Types.Descr do
46554678

46564679
## Map fields helpers
46574680
#
4658-
# Map fields are stored as orddicts (sorted key-value lists).
4681+
# Map fields and domains are stored as orddicts (sorted key-value lists).
46594682
# These helpers wrap :orddict operations so the representation
46604683
# can be changed without modifying every call site.
46614684

46624685
@compile {:inline,
46634686
fields_from_reverse_list: 1,
4687+
fields_from_keys: 2,
46644688
fields_to_list: 1,
46654689
fields_fold: 3,
46664690
fields_keys: 1,
@@ -4674,13 +4698,18 @@ defmodule Module.Types.Descr do
46744698
fields_map: 2}
46754699

46764700
defp fields_from_reverse_list(list), do: :lists.ukeysort(1, list)
4701+
defp fields_from_keys(keys, value), do: Enum.map(:lists.usort(keys), &{&1, value})
46774702
defp fields_to_list(fields), do: fields
46784703
defp fields_fold(fields, acc, fun), do: :orddict.fold(fun, acc, fields)
4679-
46804704
defp fields_keys(fields), do: :orddict.fetch_keys(fields)
46814705
defp fields_store(key, value, fields), do: :orddict.store(key, value, fields)
46824706
defp fields_find(key, fields), do: :orddict.find(key, fields)
46834707
defp fields_take(key, fields), do: :orddict.take(key, fields)
4708+
defp fields_fetch!(key, fields), do: :orddict.fetch(key, fields)
4709+
defp fields_is_key(key, fields), do: :orddict.is_key(key, fields)
4710+
4711+
defp fields_merge(fun, fields1, fields2), do: :orddict.merge(fun, fields1, fields2)
4712+
defp fields_map(fun, fields), do: :orddict.map(fun, fields)
46844713

46854714
defp fields_get(fields, key, default) do
46864715
case :orddict.find(key, fields) do
@@ -4689,10 +4718,20 @@ defmodule Module.Types.Descr do
46894718
end
46904719
end
46914720

4692-
defp fields_fetch!(key, fields), do: :orddict.fetch(key, fields)
4693-
defp fields_is_key(key, fields), do: :orddict.is_key(key, fields)
4694-
defp fields_merge(fun, fields1, fields2), do: :orddict.merge(fun, fields1, fields2)
4695-
defp fields_map(fun, fields), do: :orddict.map(fun, fields)
4721+
defp fields_put_all_new(fields, [], _value), do: fields
4722+
defp fields_put_all_new([], keys, value), do: Enum.map(keys, &{&1, value})
4723+
4724+
defp fields_put_all_new([{k1, _} = h | t1], [k2 | _] = keys, value) when k1 < k2 do
4725+
[h | fields_put_all_new(t1, keys, value)]
4726+
end
4727+
4728+
defp fields_put_all_new([{k1, _} | _] = fields, [k2 | keys], value) when k1 > k2 do
4729+
[{k2, value} | fields_put_all_new(fields, keys, value)]
4730+
end
4731+
4732+
defp fields_put_all_new([h | t1], [_ | keys], value) do
4733+
[h | fields_put_all_new(t1, keys, value)]
4734+
end
46964735

46974736
defp fields_merge_with_defaults([{k1, v1} | rest1] = f1, d1, [{k2, v2} | rest2] = f2, d2, fun) do
46984737
cond do

lib/elixir/src/elixir_erl.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
-define(typespecs, 'Elixir.Kernel.Typespec').
1212

1313
checker_version() ->
14-
elixir_checker_v6.
14+
elixir_checker_v7.
1515

1616
%% debug_info callback
1717

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1793,7 +1793,7 @@ defmodule Module.Types.IntegrationTest do
17931793

17941794
defp read_chunk(binary) do
17951795
assert {:ok, {_module, [{~c"ExCk", chunk}]}} = :beam_lib.chunks(binary, [~c"ExCk"])
1796-
assert {:elixir_checker_v6, map} = :erlang.binary_to_term(chunk)
1796+
assert {:elixir_checker_v7, map} = :erlang.binary_to_term(chunk)
17971797
map
17981798
end
17991799

lib/elixir/test/elixir/protocol/consolidation_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ defmodule Protocol.ConsolidationTest do
165165

166166
defp exports(binary) do
167167
{:ok, {_, [{~c"ExCk", check_bin}]}} = :beam_lib.chunks(binary, [~c"ExCk"])
168-
assert {:elixir_checker_v6, contents} = :erlang.binary_to_term(check_bin)
168+
assert {:elixir_checker_v7, contents} = :erlang.binary_to_term(check_bin)
169169
Map.new(contents.exports)
170170
end
171171

lib/mix/lib/mix/compilers/elixir.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
defmodule Mix.Compilers.Elixir do
66
@moduledoc false
77

8-
@manifest_vsn 32
8+
@manifest_vsn 33
99
@checkpoint_vsn 4
1010

1111
import Record

0 commit comments

Comments
 (0)