Skip to content

Commit 6f92d3c

Browse files
committed
Refine dynamic container descriptors
1 parent b5608f5 commit 6f92d3c

3 files changed

Lines changed: 95 additions & 184 deletions

File tree

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

Lines changed: 66 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,31 +2143,27 @@ defmodule Module.Types.Descr do
21432143
#
21442144
# none() types can be given and, while stored, it means the list type is empty.
21452145
defp list_descr(list_type, last_type, empty?) do
2146+
dynamic? = gradual?(list_type) or gradual?(last_type)
21462147
{dynamic_list_type, static_list_type} = pop_dynamic(list_type)
21472148
{dynamic_last_type, static_last_type} = pop_dynamic(last_type)
2148-
dynamic? = dynamic_list_type != static_list_type or dynamic_last_type != static_last_type
2149-
static_possible? = not empty?(static_list_type) and not empty?(static_last_type)
2150-
2151-
dynamic_descr = list_descr_build(dynamic_list_type, dynamic_last_type, empty?)
2149+
dynamic_descr = list_descr_static(dynamic_list_type, dynamic_last_type, empty?)
2150+
# Just a syntactic check, to avoid a recursive empty? call
2151+
static_empty? = static_list_type == @none or static_last_type == @none
21522152

21532153
cond do
2154-
empty?(dynamic_descr) ->
2155-
%{}
2154+
not dynamic? ->
2155+
dynamic_descr
21562156

2157-
dynamic? ->
2158-
if not static_possible? do
2159-
%{dynamic: dynamic_descr}
2160-
else
2161-
static_descr = list_descr_build(static_list_type, static_last_type, empty?)
2162-
Map.put(static_descr, :dynamic, dynamic_descr)
2163-
end
2157+
static_empty? ->
2158+
%{dynamic: dynamic_descr}
21642159

21652160
true ->
2166-
dynamic_descr
2161+
list_descr_static(static_list_type, static_last_type, empty?)
2162+
|> Map.put(:dynamic, dynamic_descr)
21672163
end
21682164
end
21692165

2170-
defp list_descr_build(list_type, last_type, empty?) do
2166+
defp list_descr_static(list_type, last_type, empty?) do
21712167
list_part =
21722168
if last_type == :term do
21732169
list_new(:term, :term)
@@ -2791,29 +2787,19 @@ defmodule Module.Types.Descr do
27912787
defp domain_key_to_descr(:list), do: @list_top
27922788

27932789
defp map_descr(tag, pairs) do
2794-
{fields, domains, dynamic_fields, dynamic_domains, dynamic?, static_possible?} =
2795-
map_descr_pairs(pairs, [], @fields_new, [], @fields_new, false, true)
2790+
{fields, domains, dynamic_fields, dynamic_domains, dynamic?, static_empty?} =
2791+
map_descr_pairs(pairs)
27962792

2797-
dynamic_descr = map_descr_build(tag, dynamic_fields, dynamic_domains)
2793+
dynamic_descr = map_descr_static(tag, dynamic_fields, dynamic_domains)
27982794

27992795
cond do
2800-
empty?(dynamic_descr) ->
2801-
%{}
2802-
2803-
dynamic? ->
2804-
if not static_possible? do
2805-
%{dynamic: dynamic_descr}
2806-
else
2807-
static_descr = map_descr_build(tag, fields, domains)
2808-
Map.put(static_descr, :dynamic, dynamic_descr)
2809-
end
2810-
2811-
true ->
2812-
dynamic_descr
2796+
not dynamic? -> dynamic_descr
2797+
static_empty? -> %{dynamic: dynamic_descr}
2798+
true -> Map.put(map_descr_static(tag, fields, domains), :dynamic, dynamic_descr)
28132799
end
28142800
end
28152801

2816-
defp map_descr_build(tag, fields, domains) do
2802+
defp map_descr_static(tag, fields, domains) do
28172803
map_new =
28182804
if not is_fields_empty(domains) do
28192805
domains =
@@ -2850,91 +2836,34 @@ defmodule Module.Types.Descr do
28502836

28512837
defp map_put_domain(domain, [], _initial, _value), do: domain
28522838

2853-
defp map_descr_pairs(
2854-
[{key, :term} | rest],
2855-
fields,
2856-
domain,
2857-
dynamic_fields,
2858-
dynamic_domain,
2859-
dynamic?,
2860-
static_possible?
2861-
) do
2862-
case is_atom(key) do
2863-
true ->
2864-
map_descr_pairs(
2865-
rest,
2866-
[{key, :term} | fields],
2867-
domain,
2868-
[{key, :term} | dynamic_fields],
2869-
dynamic_domain,
2870-
dynamic?,
2871-
static_possible?
2872-
)
2839+
defp map_descr_pairs(pairs) do
2840+
{fields, domains, dynamic_fields, dynamic_domains, dynamic?, static_possible?} =
2841+
Enum.reduce(pairs, {[], @fields_new, [], @fields_new, false, false}, fn {key, value}, acc ->
2842+
map_descr_pair(key, value, acc)
2843+
end)
28732844

2874-
false ->
2875-
map_descr_pairs(
2876-
rest,
2877-
fields,
2878-
map_put_domain(domain, key, :term),
2879-
dynamic_fields,
2880-
map_put_domain(dynamic_domain, key, :term),
2881-
dynamic?,
2882-
static_possible?
2883-
)
2884-
end
2845+
{fields_from_reverse_list(fields), domains, fields_from_reverse_list(dynamic_fields),
2846+
dynamic_domains, dynamic?, static_possible?}
28852847
end
28862848

2887-
defp map_descr_pairs(
2888-
[{key, value} | rest],
2889-
fields,
2890-
domain,
2891-
dynamic_fields,
2892-
dynamic_domain,
2893-
dynamic?,
2894-
static_possible?
2849+
defp map_descr_pair(
2850+
key,
2851+
value,
2852+
{fields, domains, dynamic_fields, dynamic_domains, dynamic?, static_empty?}
28952853
) do
28962854
{dynamic_value, static_value} = pop_dynamic(value)
2897-
dynamic? = dynamic? or dynamic_value != static_value
2898-
static_possible? = static_possible? and not empty?(static_value)
2855+
dynamic? = dynamic? or gradual?(value)
2856+
static_empty? = static_empty? or static_value == @none
28992857

2900-
case is_atom(key) do
2901-
true ->
2902-
map_descr_pairs(
2903-
rest,
2904-
[{key, static_value} | fields],
2905-
domain,
2906-
[{key, dynamic_value} | dynamic_fields],
2907-
dynamic_domain,
2908-
dynamic?,
2909-
static_possible?
2910-
)
2911-
2912-
false ->
2913-
map_descr_pairs(
2914-
rest,
2915-
fields,
2916-
map_put_domain(domain, key, static_value),
2917-
dynamic_fields,
2918-
map_put_domain(dynamic_domain, key, dynamic_value),
2919-
dynamic?,
2920-
static_possible?
2921-
)
2858+
if is_atom(key) do
2859+
{[{key, static_value} | fields], domains, [{key, dynamic_value} | dynamic_fields],
2860+
dynamic_domains, dynamic?, static_empty?}
2861+
else
2862+
{fields, map_put_domain(domains, key, static_value), dynamic_fields,
2863+
map_put_domain(dynamic_domains, key, dynamic_value), dynamic?, static_empty?}
29222864
end
29232865
end
29242866

2925-
defp map_descr_pairs(
2926-
[],
2927-
fields,
2928-
domain,
2929-
dynamic_fields,
2930-
dynamic_domain,
2931-
dynamic?,
2932-
static_possible?
2933-
) do
2934-
{fields_from_reverse_list(fields), domain, fields_from_reverse_list(dynamic_fields),
2935-
dynamic_domain, dynamic?, static_possible?}
2936-
end
2937-
29382867
# Gets the default type associated to atom keys in a map.
29392868
defp map_key_tag_to_type(:open), do: term_or_optional()
29402869
defp map_key_tag_to_type(:closed), do: not_set()
@@ -4981,47 +4910,34 @@ defmodule Module.Types.Descr do
49814910
# - {atom(), boolean(), ...} is encoded as {:open, [atom(), boolean()]}
49824911

49834912
defp tuple_descr(tag, fields) do
4984-
case tuple_descr(fields, [], [], false, true) do
4985-
:empty ->
4986-
%{}
4913+
{static_fields, dynamic_fields, dynamic?, static_empty?} =
4914+
tuple_descr_fields(fields, [], [], false, false)
49874915

4988-
{static_fields, dynamic_fields, true, static_possible?} ->
4989-
dynamic_descr = %{tuple: tuple_new(tag, :lists.reverse(dynamic_fields))}
4916+
dynamic_descr = tuple_descr_static(tag, dynamic_fields)
49904917

4991-
if not static_possible? do
4992-
%{dynamic: dynamic_descr}
4993-
else
4994-
static_descr = %{tuple: tuple_new(tag, :lists.reverse(static_fields))}
4995-
Map.put(static_descr, :dynamic, dynamic_descr)
4996-
end
4997-
4998-
{fields, _dynamic_fields, false, _static_possible?} ->
4999-
%{tuple: tuple_new(tag, :lists.reverse(fields))}
4918+
cond do
4919+
not dynamic? -> dynamic_descr
4920+
static_empty? -> %{dynamic: dynamic_descr}
4921+
true -> Map.put(tuple_descr_static(tag, static_fields), :dynamic, dynamic_descr)
50004922
end
50014923
end
50024924

5003-
defp tuple_descr([:term | rest], acc, dynamic_acc, dynamic?, static_possible?) do
5004-
tuple_descr(rest, [:term | acc], [:term | dynamic_acc], dynamic?, static_possible?)
5005-
end
4925+
defp tuple_descr_static(tag, fields), do: %{tuple: tuple_new(tag, :lists.reverse(fields))}
50064926

5007-
defp tuple_descr([value | rest], acc, dynamic_acc, dynamic?, static_possible?) do
4927+
defp tuple_descr_fields([value | rest], acc, dynamic_acc, dynamic?, static_empty?) do
50084928
{dynamic_value, static_value} = pop_dynamic(value)
50094929

5010-
if empty?(dynamic_value) do
5011-
:empty
5012-
else
5013-
tuple_descr(
5014-
rest,
5015-
[static_value | acc],
5016-
[dynamic_value | dynamic_acc],
5017-
dynamic? or dynamic_value != static_value,
5018-
static_possible? and not empty?(static_value)
5019-
)
5020-
end
4930+
tuple_descr_fields(
4931+
rest,
4932+
[static_value | acc],
4933+
[dynamic_value | dynamic_acc],
4934+
dynamic? or gradual?(value),
4935+
static_empty? or static_value == @none
4936+
)
50214937
end
50224938

5023-
defp tuple_descr([], acc, dynamic_acc, dynamic?, static_possible?) do
5024-
{acc, dynamic_acc, dynamic?, static_possible?}
4939+
defp tuple_descr_fields([], acc, dynamic_acc, dynamic?, static_empty?) do
4940+
{acc, dynamic_acc, dynamic?, static_empty?}
50254941
end
50264942

50274943
defp tuple_new(tag, elements), do: bdd_leaf_new(tag, elements)
@@ -5040,7 +4956,14 @@ defmodule Module.Types.Descr do
50404956
end
50414957
end
50424958

5043-
defp tuple_literal_intersection(:open, [], tag, elements), do: {tag, elements}
4959+
# Detecting tuples built with none() fields
4960+
defp tuple_literal_intersection(:open, [], tag, elements) do
4961+
if Enum.any?(elements, &empty?/1) do
4962+
:empty
4963+
else
4964+
{tag, elements}
4965+
end
4966+
end
50444967

50454968
defp tuple_literal_intersection(tag1, elements1, tag2, elements2) do
50464969
case tuple_sizes_strategy(tag1, length(elements1), tag2, length(elements2)) do
@@ -5679,7 +5602,7 @@ defmodule Module.Types.Descr do
56795602
def tuple_values(descr) do
56805603
case :maps.take(:dynamic, descr) do
56815604
:error ->
5682-
if tuple_only?(descr) do
5605+
if non_empty_tuple_only?(descr) do
56835606
process_tuples_values(Map.get(descr, :tuple, :bdd_bot))
56845607
else
56855608
:badtuple
@@ -5725,7 +5648,7 @@ defmodule Module.Types.Descr do
57255648
case :maps.take(:dynamic, descr) do
57265649
:error ->
57275650
# Note: the empty type is not a valid input
5728-
is_proper_tuple? = descr_key?(descr, :tuple) and tuple_only?(descr)
5651+
is_proper_tuple? = descr_key?(descr, :tuple) and non_empty_tuple_only?(descr)
57295652
is_proper_size? = tuple_of_size_at_least_static?(descr, index + 1)
57305653

57315654
cond do
@@ -5794,7 +5717,7 @@ defmodule Module.Types.Descr do
57945717
case :maps.take(:dynamic, descr) do
57955718
:error ->
57965719
# Note: the empty type is not a valid input
5797-
is_proper_tuple? = descr_key?(descr, :tuple) and tuple_only?(descr)
5720+
is_proper_tuple? = descr_key?(descr, :tuple) and non_empty_tuple_only?(descr)
57985721
is_proper_size? = index == 0 or tuple_of_size_at_least_static?(descr, index)
57995722

58005723
cond do

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

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -824,37 +824,28 @@ defmodule Module.Types.DescrTest do
824824
assert union(open_map(a: not_set()), t1) == t2
825825
end
826826

827-
test "tuple preserves static part of gradual elements" do
828-
x = union(atom([:ok]), dynamic(integer()))
829-
830-
assert tuple([x, atom([:x])]) == %{
831-
tuple: {:closed, [atom([:ok]), atom([:x])]},
832-
dynamic: %{tuple: {:closed, [upper_bound(x), atom([:x])]}}
833-
}
834-
835-
assert subtype?(tuple([atom([:ok]), atom([:x])]), tuple([x, atom([:x])]))
836-
end
837-
838-
test "closed_map preserves static part of gradual values" do
839-
x = union(atom([:ok]), dynamic(integer()))
840-
841-
assert closed_map(a: x) == %{
842-
map: {:closed, [a: atom([:ok])]},
843-
dynamic: %{map: {:closed, [a: upper_bound(x)]}}
844-
}
845-
846-
assert subtype?(closed_map(a: atom([:ok])), closed_map(a: x))
847-
end
848-
849-
test "non_empty_list preserves static part of gradual head types" do
850-
x = union(atom([:ok]), dynamic(integer()))
851-
852-
assert non_empty_list(x) == %{
853-
list: {atom([:ok]), empty_list()},
854-
dynamic: %{list: {upper_bound(x), empty_list()}}
855-
}
856-
857-
assert subtype?(non_empty_list(atom([:ok])), non_empty_list(x))
827+
test "structural types preserve static part of gradual elements" do
828+
static = atom([:ok])
829+
gradual = union(static, dynamic(integer()))
830+
upper_bound = upper_bound(gradual)
831+
x = atom([:x])
832+
head = atom([:head])
833+
834+
for {descr, static_descr, dynamic_descr} <- [
835+
{tuple([gradual, x]), tuple([static, x]), tuple([upper_bound, x])},
836+
{open_tuple([gradual]), open_tuple([static]), open_tuple([upper_bound])},
837+
{closed_map(a: gradual), closed_map(a: static), closed_map(a: upper_bound)},
838+
{open_map(a: gradual), open_map(a: static), open_map(a: upper_bound)},
839+
{closed_map([{domain_key(:integer), gradual}]),
840+
closed_map([{domain_key(:integer), static}]),
841+
closed_map([{domain_key(:integer), upper_bound}])},
842+
{non_empty_list(gradual), non_empty_list(static), non_empty_list(upper_bound)},
843+
{non_empty_list(head, gradual), non_empty_list(head, static),
844+
non_empty_list(head, upper_bound)}
845+
] do
846+
assert descr == Map.put(static_descr, :dynamic, dynamic_descr)
847+
assert subtype?(static_descr, descr)
848+
end
858849
end
859850
end
860851

@@ -1708,6 +1699,7 @@ defmodule Module.Types.DescrTest do
17081699
assert tuple_fetch(dynamic(empty_tuple()), 0) == :badindex
17091700
assert tuple_fetch(dynamic(tuple([integer(), atom()])), 2) == :badindex
17101701
assert tuple_fetch(union(dynamic(), integer()), 0) == :badtuple
1702+
assert tuple_fetch(tuple([none()]), 0) == :badtuple
17111703

17121704
assert tuple_fetch(dynamic(tuple()), 0)
17131705
|> Kernel.then(fn {opt, type} -> opt and equal?(type, dynamic()) end)
@@ -1773,6 +1765,7 @@ defmodule Module.Types.DescrTest do
17731765
assert tuple_insert_at(tuple([integer(), atom()]), -1, boolean()) == :badindex
17741766
assert tuple_insert_at(integer(), 0, boolean()) == :badtuple
17751767
assert tuple_insert_at(term(), 0, boolean()) == :badtuple
1768+
assert tuple_insert_at(tuple([none()]), 0, boolean()) == :badtuple
17761769

17771770
# Out-of-bounds in a union
17781771
assert union(tuple([integer(), atom()]), tuple([float()]))
@@ -2944,6 +2937,7 @@ defmodule Module.Types.DescrTest do
29442937
end
29452938

29462939
test "list" do
2940+
assert non_empty_list(none()) |> to_quoted_string() == "none()"
29472941
assert list(term()) |> to_quoted_string() == "list(term())"
29482942
assert list(integer()) |> to_quoted_string() == "list(integer())"
29492943

0 commit comments

Comments
 (0)