Skip to content

Commit 1ea2dfa

Browse files
committed
Fix unmatched branch in map emptiness check
1 parent 30db5d9 commit 1ea2dfa

File tree

2 files changed

+27
-11
lines changed

2 files changed

+27
-11
lines changed

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

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,8 @@ defmodule Module.Types.Descr do
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_not_set(), else: not_set()
655-
default2 = if tag2 == :open, do: term_or_not_set(), else: not_set()
654+
default1 = map_tag_to_type(tag1)
655+
default2 = map_tag_to_type(tag2)
656656

657657
# if any intersection of values is empty, the whole intersection is empty
658658
new_fields =
@@ -690,7 +690,11 @@ defmodule Module.Types.Descr do
690690
end)
691691
end
692692

693-
# Emptiness checking for maps. Short-circuits if it finds a non-empty map literal in the union.
693+
# Emptiness checking for maps.
694+
#
695+
# Short-circuits if it finds a non-empty map literal in the union.
696+
# Since the algorithm is recursive, we implement the short-circuiting
697+
# as throw/catch.
694698
defp map_empty?(dnf) do
695699
try do
696700
for {tag, pos, negs} <- dnf do
@@ -709,20 +713,27 @@ defmodule Module.Types.Descr do
709713

710714
defp map_empty?(tag, fields, [{neg_tag, neg_fields} | negs]) do
711715
try do
712-
# keys that are present in the negative map, but not in the positive one
716+
# Keys that are present in the negative map, but not in the positive one
713717
for {neg_key, neg_type} <- neg_fields, not is_map_key(fields, neg_key) do
714718
cond do
715-
# key is required, and the positive map is closed: empty intersection
719+
# The key is not shared between positive and negative maps,
720+
# and because the negative type is required, there is no value in common
716721
tag == :closed and not optional?(neg_type) ->
717-
throw(:no_intersection)
722+
throw(:discard_negative)
723+
724+
# The key is not shared between positive and negative maps,
725+
# but because the negative type is not required, there may be a value in common
726+
tag == :closed ->
727+
true
718728

719-
# if the positive map is open
729+
# There may be value in common
720730
tag == :open ->
721731
diff = difference(term_or_not_set(), neg_type)
722732
empty?(diff) or map_empty?(tag, Map.put(fields, neg_key, diff), negs)
723733
end
724734
end
725735

736+
# Keys from the positive map that may be present in the negative one
726737
for {key, type} <- fields do
727738
case neg_fields do
728739
%{^key => neg_type} ->
@@ -731,20 +742,24 @@ defmodule Module.Types.Descr do
731742

732743
%{} ->
733744
if neg_tag == :closed and not optional?(type) do
734-
throw(:no_intersection)
745+
throw(:discard_negative)
735746
else
736747
# an absent key in a open negative map can be ignored
737-
default2 = if neg_tag == :open, do: @term_or_not_set, else: @not_set
738-
diff = difference(type, default2)
748+
diff = difference(type, map_tag_to_type(neg_tag))
739749
empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs)
740750
end
741751
end
742752
end
753+
754+
true
743755
catch
744-
:no_intersection -> map_empty?(tag, fields, negs)
756+
:discard_negative -> map_empty?(tag, fields, negs)
745757
end
746758
end
747759

760+
defp map_tag_to_type(:open), do: term_or_not_set()
761+
defp map_tag_to_type(:closed), do: not_set()
762+
748763
# Takes a map bdd and a key, and returns an equivalent dnf of pairs, in which
749764
# the type of the key in the map can be found in the first element of the pair.
750765
# See `split_line_on_key/5`.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ defmodule Module.Types.DescrTest do
153153
assert empty?(difference(map(a: integer()), map()))
154154
assert empty?(difference(map(a: integer()), map(a: integer())))
155155
assert empty?(difference(map(a: integer()), map([a: integer()], :open)))
156+
assert empty?(difference(map(a: integer()), map([b: if_set(integer())], :open)))
156157

157158
assert difference(map(a: integer(), b: if_set(atom())), map(a: integer()))
158159
|> difference(map(a: integer(), b: atom()))

0 commit comments

Comments
 (0)