Skip to content

Commit 30db5d9

Browse files
committed
More consistent naming in descr
1 parent 5911a98 commit 30db5d9

File tree

1 file changed

+95
-102
lines changed

1 file changed

+95
-102
lines changed

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

Lines changed: 95 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ defmodule Module.Types.Descr do
77
# types require specific data structures.
88

99
# Vocabulary:
10-
# DNF: disjunctive normal form
10+
#
11+
# * DNF - disjunctive normal form which is a pair of unions and negations.
12+
# In the case of maps, we augment each pair with the open/closed tag.
1113

1214
# TODO: When we convert from AST to descr, we need to normalize
1315
# the dynamic type.
@@ -25,8 +27,7 @@ defmodule Module.Types.Descr do
2527
@bit_tuple 1 <<< 8
2628
@bit_fun 1 <<< 9
2729
@bit_top (1 <<< 10) - 1
28-
29-
@bit_unset 1 <<< 10
30+
@bit_optional 1 <<< 10
3031

3132
@atom_top {:negation, :sets.new(version: 2)}
3233
@map_top [{:open, %{}, []}]
@@ -37,11 +38,6 @@ defmodule Module.Types.Descr do
3738
@none %{}
3839
@dynamic %{dynamic: @term}
3940

40-
# Map helpers
41-
42-
@unset %{bitmap: @bit_unset}
43-
@term_or_unset %{bitmap: @bit_top ||| @bit_unset, atom: @atom_top, map: @map_top}
44-
4541
# Type definitions
4642

4743
def dynamic(), do: @dynamic
@@ -68,6 +64,15 @@ defmodule Module.Types.Descr do
6864
@boolset :sets.from_list([true, false], version: 2)
6965
def boolean(), do: %{atom: {:union, @boolset}}
7066

67+
# Map helpers
68+
69+
@not_set %{bitmap: @bit_optional}
70+
@term_or_not_set %{bitmap: @bit_top ||| @bit_optional, atom: @atom_top, map: @map_top}
71+
72+
def not_set(), do: @not_set
73+
def if_set(type), do: Map.update(type, :bitmap, @bit_optional, &(&1 ||| @bit_optional))
74+
defp term_or_not_set(), do: @term_or_not_set
75+
7176
## Set operations
7277

7378
def term?(descr), do: subtype_static(@term, Map.delete(descr, :dynamic))
@@ -183,10 +188,12 @@ defmodule Module.Types.Descr do
183188
def negation(%{} = descr), do: difference(term(), descr)
184189

185190
@doc """
186-
Check if a type is empty. For gradual types, check that the upper bound
187-
(the dynamic part) is empty. Stop as soon as one non-empty component is found.
188-
Simpler components (bitmap, atom) are checked first for speed since, if they are
189-
present, the type is non-empty as we normalize then during construction.
191+
Check if a type is empty.
192+
193+
For gradual types, check that the upper bound (the dynamic part) is empty.
194+
Stop as soon as one non-empty component is found. Simpler components
195+
(bitmap, atom) are checked first for speed since, if they are present,
196+
the type is non-empty as we normalize then during construction.
190197
"""
191198
def empty?(%{} = descr) do
192199
descr = Map.get(descr, :dynamic, descr)
@@ -196,8 +203,6 @@ defmodule Module.Types.Descr do
196203
(not Map.has_key?(descr, :map) or map_empty?(descr.map)))
197204
end
198205

199-
def not_empty?(descr), do: not empty?(descr)
200-
201206
@doc """
202207
Converts a descr to its quoted representation.
203208
"""
@@ -327,7 +332,7 @@ defmodule Module.Types.Descr do
327332
@doc """
328333
Check if two types have a non-empty intersection.
329334
"""
330-
def intersect?(left, right), do: not_empty?(intersection(left, right))
335+
def intersect?(left, right), do: not empty?(intersection(left, right))
331336

332337
@doc """
333338
Checks if a type is a compatible subtype of another.
@@ -349,10 +354,10 @@ defmodule Module.Types.Descr do
349354
{input_dynamic, input_static} = Map.pop(input_type, :dynamic, input_type)
350355
expected_dynamic = Map.get(expected_type, :dynamic, expected_type)
351356

352-
if not empty?(input_static) do
353-
subtype_static(input_static, expected_dynamic)
354-
else
357+
if empty?(input_static) do
355358
intersect?(input_dynamic, expected_dynamic)
359+
else
360+
subtype_static(input_static, expected_dynamic)
356361
end
357362
end
358363

@@ -496,7 +501,7 @@ defmodule Module.Types.Descr do
496501
# on top of the static type. Though, the latter may be used for printing purposes.
497502
#
498503
# There are two ways for a descr to represent a static type: either the
499-
# `:dynamic` field is unset, or it contains a type equal to the static component
504+
# `:dynamic` field is not_set, or it contains a type equal to the static component
500505
# (that is, there are no extra dynamic values).
501506

502507
defp dynamic_intersection(left, right) do
@@ -528,38 +533,12 @@ defmodule Module.Types.Descr do
528533
end
529534
end
530535

531-
## Not_set
532-
533-
# `not_set()` is a special base type that represents an unset field in a map.
534-
# E.g., `%{a: integer(), b: not_set(), ...}` represents a map with an integer
535-
# field `a` and an unset field `b`, and possibly other fields.
536-
537-
# The `if_set()` modifier is syntactic sugar for specifying the key as a union
538-
# of the key type and `not_set()`. For example, `%{:foo => if_set(integer())}`
539-
# is equivalent to `%{:foo => integer() or not_set()}`.
540-
541-
# `not_set()` has no meaning outside of map types.
542-
543-
def not_set(), do: @unset
544-
def if_set(type), do: Map.update(type, :bitmap, @bit_unset, &(&1 ||| @bit_unset))
545-
546-
defp is_optional?(type), do: (Map.get(type, :bitmap, 0) &&& @bit_unset) != 0
547-
548-
defp remove_not_set(type) do
549-
case type do
550-
%{:bitmap => @bit_unset} -> Map.delete(type, :bitmap)
551-
%{:bitmap => bitmap} -> Map.put(type, :bitmap, bitmap &&& ~~~@bit_unset)
552-
_ -> type
553-
end
554-
end
555-
556536
## Map
557537
#
558-
# Map operations.
559-
#
560538
# Maps are in disjunctive normal form (DNF), that is, a list (union) of pairs
561539
# `{map_literal, negated_literals}` where `map_literal` is a map type literal
562540
# and `negated_literals` is a list of map type literals that are negated from it.
541+
# Each pair is augmented with the :open or :closed tag.
563542
#
564543
# A map literal is a pair `{:open | :closed, %{keys => value_types}}`.
565544
#
@@ -579,6 +558,29 @@ defmodule Module.Types.Descr do
579558
# Set-theoretic operations take two DNFs (lists) and return a DNF (list).
580559
# Simplifications can be done to prune the latter.
581560

561+
## Not_set
562+
563+
# `not_set()` is a special base type that represents an not_set field in a map.
564+
# E.g., `%{a: integer(), b: not_set(), ...}` represents a map with an integer
565+
# field `a` and an not_set field `b`, and possibly other fields.
566+
567+
# The `if_set()` modifier is syntactic sugar for specifying the key as a union
568+
# of the key type and `not_set()`. For example, `%{:foo => if_set(integer())}`
569+
# is equivalent to `%{:foo => integer() or not_set()}`.
570+
571+
# `not_set()` has no meaning outside of map types.
572+
573+
defp optional?(%{bitmap: bitmap}) when (bitmap &&& @bit_optional) != 0, do: true
574+
defp optional?(_), do: false
575+
576+
defp remove_not_set(type) do
577+
case type do
578+
%{bitmap: @bit_optional} -> Map.delete(type, :bitmap)
579+
%{bitmap: bitmap} -> %{type | bitmap: bitmap &&& ~~~@bit_optional}
580+
_ -> type
581+
end
582+
end
583+
582584
# Create a DNF from a specification of a map type.
583585
defp map_new(open_or_closed, pairs), do: [{open_or_closed, Map.new(pairs), []}]
584586

@@ -634,36 +636,34 @@ defmodule Module.Types.Descr do
634636

635637
# Given two unions of maps, intersects each pair of maps.
636638
defp map_intersection(dnf1, dnf2) do
637-
Enum.flat_map(dnf1, &map_intersection_aux(&1, dnf2))
638-
end
639-
640-
# Intersects a map with a union of maps.
641-
defp map_intersection_aux({tag1, pos1, negs1}, dnf2) do
642-
Enum.reduce(dnf2, [], fn {tag2, pos2, negs2}, acc ->
643-
try do
644-
{tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
645-
[{tag, fields, negs1 ++ negs2} | acc]
646-
catch
647-
:empty_intersection -> acc
648-
end
649-
end)
639+
for {tag1, pos1, negs1} <- dnf1,
640+
{tag2, pos2, negs2} <- dnf2,
641+
reduce: [] do
642+
acc ->
643+
try do
644+
{tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
645+
[{tag, fields, negs1 ++ negs2} | acc]
646+
catch
647+
:empty -> acc
648+
end
649+
end
650650
end
651651

652652
# Intersects two map literals; throws if their intersection is empty.
653653
defp map_literal_intersection(tag1, map1, tag2, map2) do
654-
default1 = if tag1 == :open, do: @term_or_unset, else: @unset
655-
default2 = if tag2 == :open, do: @term_or_unset, else: @unset
654+
default1 = if tag1 == :open, do: term_or_not_set(), else: not_set()
655+
default2 = if tag2 == :open, do: term_or_not_set(), else: not_set()
656656

657657
# if any intersection of values is empty, the whole intersection is empty
658658
new_fields =
659659
(for {key, value_type} <- map1 do
660660
value_type2 = Map.get(map2, key, default2)
661661
t = intersection(value_type, value_type2)
662-
if empty?(t), do: throw(:empty_intersection), else: {key, t}
662+
if empty?(t), do: throw(:empty), else: {key, t}
663663
end ++
664664
for {key, value_type} <- map2, not is_map_key(map1, key) do
665665
t = intersection(default1, value_type)
666-
if empty?(t), do: throw(:empty_intersection), else: {key, t}
666+
if empty?(t), do: throw(:empty), else: {key, t}
667667
end)
668668
|> Map.new()
669669

@@ -674,26 +674,19 @@ defmodule Module.Types.Descr do
674674
end
675675

676676
defp map_difference(dnf1, dnf2) do
677-
case dnf2 do
678-
[] ->
679-
dnf1
680-
681-
[lit2 | dnf2] ->
682-
Enum.flat_map(dnf1, fn lit1 -> map_single_difference(lit1, lit2) end)
683-
|> map_difference(dnf2)
684-
end
685-
end
686-
687-
# Computes the difference between two maps union.
688-
defp map_single_difference({tag1, fields1, negs1}, {tag2, fields2, negs2}) do
689-
Enum.reduce(negs2, [{tag1, fields1, [{tag2, fields2} | negs1]}], fn
690-
{tag2, fields2}, acc ->
691-
try do
692-
{tag, fields} = map_literal_intersection(tag1, fields1, tag2, fields2)
693-
[{tag, fields, negs1} | acc]
694-
catch
695-
:empty_intersection -> acc
696-
end
677+
Enum.reduce(dnf2, dnf1, fn {tag2, fields2, negs2}, dnf1 ->
678+
Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc ->
679+
acc = [{tag1, fields1, [{tag2, fields2} | negs1]} | acc]
680+
681+
Enum.reduce(negs2, acc, fn {neg_tag2, neg_fields2}, acc ->
682+
try do
683+
{tag, fields} = map_literal_intersection(tag1, fields1, neg_tag2, neg_fields2)
684+
[{tag, fields, negs1} | acc]
685+
catch
686+
:empty -> acc
687+
end
688+
end)
689+
end)
697690
end)
698691
end
699692

@@ -720,12 +713,12 @@ defmodule Module.Types.Descr do
720713
for {neg_key, neg_type} <- neg_fields, not is_map_key(fields, neg_key) do
721714
cond do
722715
# key is required, and the positive map is closed: empty intersection
723-
tag == :closed and not is_optional?(neg_type) ->
716+
tag == :closed and not optional?(neg_type) ->
724717
throw(:no_intersection)
725718

726719
# if the positive map is open
727720
tag == :open ->
728-
diff = difference(@term_or_unset, neg_type)
721+
diff = difference(term_or_not_set(), neg_type)
729722
empty?(diff) or map_empty?(tag, Map.put(fields, neg_key, diff), negs)
730723
end
731724
end
@@ -737,11 +730,11 @@ defmodule Module.Types.Descr do
737730
empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs)
738731

739732
%{} ->
740-
if neg_tag == :closed and not is_optional?(type) do
733+
if neg_tag == :closed and not optional?(type) do
741734
throw(:no_intersection)
742735
else
743736
# an absent key in a open negative map can be ignored
744-
default2 = if neg_tag == :open, do: @term_or_unset, else: @unset
737+
default2 = if neg_tag == :open, do: @term_or_not_set, else: @not_set
745738
diff = difference(type, default2)
746739
empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs)
747740
end
@@ -760,7 +753,7 @@ defmodule Module.Types.Descr do
760753
{fst, snd} =
761754
case single_split({tag, fields}, key) do
762755
# { .. } the open map in a positive intersection can be ignored
763-
:no_split -> {@term_or_unset, @term_or_unset}
756+
:no_split -> {term_or_not_set(), term_or_not_set()}
764757
# {typeof l, rest} is added to the positive accumulator
765758
{value_type, rest_of_map} -> {value_type, rest_of_map}
766759
end
@@ -780,10 +773,10 @@ defmodule Module.Types.Descr do
780773

781774
cond do
782775
value_type != nil -> {value_type, map_descr(tag, rest_of_map)}
783-
tag == :closed -> {@unset, map_descr(tag, rest_of_map)}
776+
tag == :closed -> {not_set(), map_descr(tag, rest_of_map)}
784777
# case where there is an open map with no keys { .. }
785778
map_size(fields) == 0 -> :no_split
786-
true -> {@term_or_unset, map_descr(tag, rest_of_map)}
779+
true -> {term_or_not_set(), map_descr(tag, rest_of_map)}
787780
end
788781
end
789782

@@ -829,12 +822,12 @@ defmodule Module.Types.Descr do
829822
try do
830823
for {neg_key, neg_type} when not is_map_key(fields, neg_key) <- neg_fields do
831824
# key is required, and the positive map is closed: empty intersection
832-
if tag == :closed and not is_optional?(neg_type), do: throw(:no_intersection)
825+
if tag == :closed and not optional?(neg_type), do: throw(:no_intersection)
833826
end
834827

835828
for {key, type} when not is_map_key(neg_fields, key) <- fields,
836829
# key is required, and the negative map is closed: empty intersection
837-
not is_optional?(type) and neg_tag == :closed do
830+
not optional?(type) and neg_tag == :closed do
838831
throw(:no_intersection)
839832
end
840833

@@ -868,10 +861,10 @@ defmodule Module.Types.Descr do
868861

869862
defp fields_to_quoted(tag, map) do
870863
for {key, type} <- map,
871-
not (tag == :open and is_optional?(type) and term?(type)) do
864+
not (tag == :open and optional?(type) and term?(type)) do
872865
cond do
873-
is_optional?(type) and empty?(type) -> {literal(key), {:not_set, [], []}}
874-
is_optional?(type) -> {literal(key), {:if_set, [], [to_quoted(type)]}}
866+
optional?(type) and empty?(type) -> {literal(key), {:not_set, [], []}}
867+
optional?(type) -> {literal(key), {:if_set, [], [to_quoted(type)]}}
875868
true -> {literal(key), to_quoted(type)}
876869
end
877870
end
@@ -884,7 +877,7 @@ defmodule Module.Types.Descr do
884877
# normalization algorithms on pairs.
885878

886879
# Takes a DNF of pairs and simplifies it into a equivalent single list (union)
887-
# of type pairs. The `term` argument should be either `@term_or_unset` (for
880+
# of type pairs. The `term` argument should be either `@term_or_not_set` (for
888881
# map value types) or `@term` in general.
889882
# Remark: all lines of a pair dnf are naturally disjoint, because choosing a
890883
# different edge means intersection with a literal or its negation.
@@ -909,15 +902,15 @@ defmodule Module.Types.Descr do
909902
fn {t_i, s_i}, {accu, diff_of_t_i} ->
910903
i = intersection(t, t_i)
911904

912-
if not_empty?(i) do
905+
if empty?(i) do
906+
{accu, diff_of_t_i}
907+
else
913908
diff_of_t_i = difference(diff_of_t_i, t_i)
914909
s_diff = difference(s, s_i)
915910

916-
if not_empty?(s_diff),
917-
do: {[i | accu], diff_of_t_i},
918-
else: {accu, diff_of_t_i}
919-
else
920-
{accu, diff_of_t_i}
911+
if empty?(s_diff),
912+
do: {accu, diff_of_t_i},
913+
else: {[i | accu], diff_of_t_i}
921914
end
922915
end
923916
)

0 commit comments

Comments
 (0)