Skip to content

Commit 50d628c

Browse files
committed
Fixes to descr
1 parent aec6d68 commit 50d628c

File tree

2 files changed

+89
-43
lines changed

2 files changed

+89
-43
lines changed

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

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,16 @@ defmodule Module.Types.Descr do
4141
list: @non_empty_list_top
4242
}
4343

44+
@empty_intersection [0, @none]
45+
@empty_difference [0, []]
46+
4447
# Type definitions
4548

4649
defguard is_descr(descr) when is_map(descr) or descr == :term
4750

51+
defp descr_key?(:term, _key), do: true
52+
defp descr_key?(descr, key), do: is_map_key(descr, key)
53+
4854
def dynamic(), do: %{dynamic: :term}
4955
def none(), do: @none
5056
def term(), do: :term
@@ -91,21 +97,33 @@ defmodule Module.Types.Descr do
9197

9298
@not_set %{optional: 1}
9399
@term_or_optional Map.put(@term, :optional, 1)
100+
@term_or_dynamic_optional Map.put(@term, :dynamic, %{optional: 1})
94101

95102
def not_set(), do: @not_set
96-
defp term_or_optional(), do: @term_or_optional
97-
98103
def if_set(:term), do: term_or_optional()
99104
def if_set(type), do: Map.put(type, :optional, 1)
105+
defp term_or_optional(), do: @term_or_optional
100106

101-
defp descr_key?(:term, _key), do: true
102-
defp descr_key?(descr, key), do: is_map_key(descr, key)
107+
@compile {:inline,
108+
keep_optional: 1, remove_optional: 1, remove_optional_static: 1, optional_to_term: 1}
109+
defp keep_optional(descr) do
110+
case descr do
111+
%{dynamic: %{optional: 1}} -> %{dynamic: %{optional: 1}}
112+
%{optional: 1} -> %{optional: 1}
113+
_ -> %{}
114+
end
115+
end
103116

104-
@compile {:inline, remove_optional: 1, remove_optional_static: 1, optional_to_term: 1}
105117
defp remove_optional(descr) do
106118
case descr do
107-
%{dynamic: %{optional: _} = dynamic} -> %{descr | dynamic: Map.delete(dynamic, :optional)}
108-
_ -> remove_optional_static(descr)
119+
%{dynamic: %{optional: _} = dynamic} when map_size(dynamic) == 1 ->
120+
Map.delete(descr, :dynamic)
121+
122+
%{dynamic: %{optional: _} = dynamic} ->
123+
%{descr | dynamic: Map.delete(dynamic, :optional)}
124+
125+
_ ->
126+
remove_optional_static(descr)
109127
end
110128
end
111129

@@ -118,7 +136,7 @@ defmodule Module.Types.Descr do
118136

119137
defp optional_to_term(descr) do
120138
case descr do
121-
%{dynamic: %{optional: _}} -> dynamic(term_or_optional())
139+
%{dynamic: %{optional: _}} -> @term_or_dynamic_optional
122140
%{optional: _} -> term_or_optional()
123141
_ -> :term
124142
end
@@ -230,7 +248,7 @@ defmodule Module.Types.Descr do
230248
@doc """
231249
Computes the difference between two types.
232250
"""
233-
def difference(left, :term), do: Map.take(unfold(left), [:optional])
251+
def difference(left, :term), do: keep_optional(left)
234252

235253
def difference(left, right) do
236254
left = unfold(left)
@@ -250,7 +268,7 @@ defmodule Module.Types.Descr do
250268
end
251269

252270
# For static types, the difference is component-wise.
253-
defp difference_static(left, :term), do: Map.take(unfold(left), [:optional])
271+
defp difference_static(left, :term), do: keep_optional(left)
254272

255273
defp difference_static(left, right) do
256274
iterator_difference_static(:maps.next(:maps.iterator(unfold(right))), unfold(left))
@@ -260,10 +278,12 @@ defmodule Module.Types.Descr do
260278
acc =
261279
case map do
262280
%{^key => v1} ->
263-
case difference(key, v1, v2) do
264-
0 -> Map.delete(map, key)
265-
[] -> Map.delete(map, key)
266-
value -> %{map | key => value}
281+
value = difference(key, v1, v2)
282+
283+
if value in @empty_difference do
284+
Map.delete(map, key)
285+
else
286+
%{map | key => value}
267287
end
268288

269289
%{} ->
@@ -319,6 +339,13 @@ defmodule Module.Types.Descr do
319339
end
320340
end
321341

342+
# For atom, bitmap, and optional, if the key is present,
343+
# then they are not empty,
344+
defp empty_key?(:map, value), do: map_empty?(value)
345+
defp empty_key?(:list, value), do: list_empty?(value)
346+
defp empty_key?(:tuple, value), do: tuple_empty?(value)
347+
defp empty_key?(_, _value), do: false
348+
322349
@doc """
323350
Converts a descr to its quoted representation.
324351
"""
@@ -370,8 +397,6 @@ defmodule Module.Types.Descr do
370397
Because of the dynamic/static invariant in the `descr`, subtyping can be
371398
simplified in several cases according to which type is gradual or not.
372399
"""
373-
def subtype?(left, :term), do: left != @not_set
374-
375400
def subtype?(left, right) do
376401
left = unfold(left)
377402
right = unfold(right)
@@ -450,20 +475,21 @@ defmodule Module.Types.Descr do
450475
include `dynamic()`, `integer()`, but also `dynamic() and (integer() or atom())`.
451476
Incompatible subtypes include `integer() or list()`, `dynamic() and atom()`.
452477
"""
453-
def compatible?(left, :term), do: not empty?(remove_optional(left))
454-
455478
def compatible?(left, right) do
456-
left = unfold(left)
457-
right = unfold(right)
458-
{left_dynamic, left_static} = Map.pop(left, :dynamic, left)
459-
right_dynamic = Map.get(right, :dynamic, right)
479+
{left_dynamic, left_static} =
480+
case left do
481+
:term -> {:term, :term}
482+
_ -> Map.pop(left, :dynamic, left)
483+
end
460484

461-
if empty?(left_static) do
462-
cond do
463-
left_dynamic == :term -> not empty?(right_dynamic)
464-
right_dynamic == :term -> not empty?(left_dynamic)
465-
true -> non_disjoint_intersection?(left_dynamic, right_dynamic)
485+
right_dynamic =
486+
case right do
487+
%{dynamic: dynamic} -> dynamic
488+
_ -> right
466489
end
490+
491+
if empty?(left_static) do
492+
not disjoint?(left_dynamic, right_dynamic)
467493
else
468494
subtype_static?(left_static, right_dynamic)
469495
end
@@ -1035,7 +1061,12 @@ defmodule Module.Types.Descr do
10351061
defp dynamic_intersection(left, right),
10361062
do: symmetrical_intersection(unfold(left), unfold(right), &intersection/3)
10371063

1038-
defp dynamic_difference(left, right), do: difference_static(left, right)
1064+
defp dynamic_difference(left, right) do
1065+
case difference_static(left, right) do
1066+
value when value == @none -> 0
1067+
value -> value
1068+
end
1069+
end
10391070

10401071
defp dynamic_to_quoted(descr) do
10411072
cond do
@@ -2248,10 +2279,12 @@ defmodule Module.Types.Descr do
22482279
acc =
22492280
case map do
22502281
%{^key => v2} ->
2251-
case fun.(key, v1, v2) do
2252-
0 -> acc
2253-
value when value == @none -> acc
2254-
value -> [{key, value} | acc]
2282+
value = fun.(key, v1, v2)
2283+
2284+
if value in @empty_intersection do
2285+
acc
2286+
else
2287+
[{key, value} | acc]
22552288
end
22562289

22572290
%{} ->
@@ -2275,7 +2308,8 @@ defmodule Module.Types.Descr do
22752308

22762309
defp iterator_non_disjoint_intersection?({key, v1, iterator}, map) do
22772310
with %{^key => v2} <- map,
2278-
value when value != 0 and value != @none <- intersection(key, v1, v2) do
2311+
value when value not in @empty_intersection <- intersection(key, v1, v2),
2312+
false <- empty_key?(key, value) do
22792313
true
22802314
else
22812315
_ -> iterator_non_disjoint_intersection?(:maps.next(iterator), map)

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ defmodule Module.Types.DescrTest do
5353
assert equal?(union(dynamic(), dynamic()), dynamic())
5454
assert equal?(union(dynamic(), term()), term())
5555
assert equal?(union(term(), dynamic()), term())
56-
assert equal?(union(intersection(dynamic(), atom()), atom()), atom())
56+
57+
assert equal?(union(dynamic(atom()), atom()), atom())
58+
refute equal?(union(dynamic(atom()), atom()), dynamic(atom()))
59+
60+
assert equal?(union(term(), dynamic(if_set(integer()))), union(term(), dynamic(not_set())))
61+
refute equal?(union(term(), dynamic(if_set(integer()))), dynamic(union(term(), not_set())))
5762
end
5863

5964
test "tuple" do
@@ -128,6 +133,12 @@ defmodule Module.Types.DescrTest do
128133
assert equal?(intersection(term(), dynamic()), dynamic())
129134
assert empty?(intersection(dynamic(), none()))
130135
assert empty?(intersection(intersection(dynamic(), atom()), integer()))
136+
137+
assert empty?(intersection(dynamic(not_set()), term()))
138+
refute empty?(intersection(dynamic(if_set(integer())), term()))
139+
140+
# Check for structural equivalence
141+
assert intersection(dynamic(not_set()), term()) == none()
131142
end
132143

133144
test "tuple" do
@@ -266,6 +277,7 @@ defmodule Module.Types.DescrTest do
266277
assert equal?(dynamic(), difference(term(), dynamic()))
267278
assert empty?(difference(dynamic(), term()))
268279
assert empty?(difference(none(), dynamic()))
280+
assert empty?(difference(dynamic(integer()), integer()))
269281
end
270282

271283
defp empty_tuple(), do: tuple([])
@@ -530,24 +542,24 @@ defmodule Module.Types.DescrTest do
530542
end
531543
end
532544

533-
describe "empty" do
545+
describe "empty?" do
534546
test "tuple" do
535-
assert intersection(tuple([integer(), atom()]), open_tuple([atom()])) |> empty?
536-
refute open_tuple([integer(), integer()]) |> difference(empty_tuple()) |> empty?
537-
refute open_tuple([integer(), integer()]) |> difference(open_tuple([atom()])) |> empty?
538-
refute open_tuple([term()]) |> difference(tuple([term()])) |> empty?
539-
assert difference(tuple(), empty_tuple()) |> difference(open_tuple([term()])) |> empty?
540-
assert difference(tuple(), open_tuple([term()])) |> difference(empty_tuple()) |> empty?
547+
assert intersection(tuple([integer(), atom()]), open_tuple([atom()])) |> empty?()
548+
refute open_tuple([integer(), integer()]) |> difference(empty_tuple()) |> empty?()
549+
refute open_tuple([integer(), integer()]) |> difference(open_tuple([atom()])) |> empty?()
550+
refute open_tuple([term()]) |> difference(tuple([term()])) |> empty?()
551+
assert difference(tuple(), empty_tuple()) |> difference(open_tuple([term()])) |> empty?()
552+
assert difference(tuple(), open_tuple([term()])) |> difference(empty_tuple()) |> empty?()
541553

542554
refute open_tuple([term()])
543555
|> difference(tuple([term()]))
544556
|> difference(tuple([term()]))
545-
|> empty?
557+
|> empty?()
546558

547559
assert tuple([integer(), union(integer(), atom())])
548560
|> difference(tuple([integer(), integer()]))
549561
|> difference(tuple([integer(), atom()]))
550-
|> empty?
562+
|> empty?()
551563
end
552564

553565
test "map" do

0 commit comments

Comments
 (0)