Skip to content

Commit f7cb5ae

Browse files
committed
Fix inference of open maps with non-singleton keys
Closes #10371.
1 parent b4845e6 commit f7cb5ae

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

lib/elixir/lib/module/types/of.ex

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ defmodule Module.Types.Of do
1616
"""
1717
def open_map(args, stack, context, fun) do
1818
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
19-
{:ok, {:map, pairs_to_unions(pairs, context) ++ [{:optional, :dynamic, :dynamic}]}, context}
19+
pairs =
20+
for {key, value} <- pairs, not has_unbound_var?(key, context) do
21+
if singleton?(key, context) do
22+
{key, value}
23+
else
24+
{key, to_union([value, :dynamic], context)}
25+
end
26+
end
27+
28+
triplets = pairs_to_unions(pairs, [], context) ++ [{:optional, :dynamic, :dynamic}]
29+
{:ok, {:map, triplets}, context}
2030
end
2131
end
2232

@@ -25,7 +35,7 @@ defmodule Module.Types.Of do
2535
"""
2636
def closed_map(args, stack, context, fun) do
2737
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
28-
{:ok, {:map, pairs_to_unions(pairs, context)}, context}
38+
{:ok, {:map, closed_to_unions(pairs, context)}, context}
2939
end
3040
end
3141

@@ -37,9 +47,9 @@ defmodule Module.Types.Of do
3747
end)
3848
end
3949

40-
defp pairs_to_unions([{key, value}], _context), do: [{:required, key, value}]
50+
defp closed_to_unions([{key, value}], _context), do: [{:required, key, value}]
4151

42-
defp pairs_to_unions(pairs, context) do
52+
defp closed_to_unions(pairs, context) do
4353
case Enum.split_with(pairs, fn {key, _value} -> has_unbound_var?(key, context) end) do
4454
{[], pairs} -> pairs_to_unions(pairs, [], context)
4555
{[_ | _], pairs} -> pairs_to_unions([{:dynamic, :dynamic} | pairs], [], context)

lib/elixir/lib/module/types/unify.ex

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,33 @@ defmodule Module.Types.Unify do
499499

500500
def has_unbound_var?(_type, _context), do: false
501501

502+
@doc """
503+
Returns true if it is a singleton type.
504+
505+
Only atoms are singleton types. Unbound vars are not
506+
considered singleton types.
507+
"""
508+
def singleton?({:var, var}, context) do
509+
case context.types do
510+
%{^var => :unbound} -> false
511+
%{^var => type} -> singleton?(type, context)
512+
end
513+
end
514+
515+
def singleton?({:atom, _}, _context), do: true
516+
def singleton?(_type, _context), do: false
517+
502518
@doc """
503519
Checks if the first argument is a subtype of the second argument.
504520
505521
This function assumes that:
506522
507-
* dynamic is not considered a subtype of all other types but the top type
508523
* unbound variables are not subtype of anything
509524
525+
* dynamic is not considered a subtype of all other types but the top type.
526+
This allows this function can be used for ordering, in other cases, you
527+
may need to check for both sides
528+
510529
"""
511530
def subtype?(type, type, _context), do: true
512531

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,18 @@ defmodule Module.Types.PatternTest do
118118
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]}}
119119

120120
assert quoted_pattern(%{123 => a}) ==
121-
{:ok, {:map, [{:required, :integer, {:var, 0}}, {:optional, :dynamic, :dynamic}]}}
121+
{:ok,
122+
{:map,
123+
[
124+
{:required, :integer, {:union, [{:var, 0}, :dynamic]}},
125+
{:optional, :dynamic, :dynamic}
126+
]}}
122127

123128
assert quoted_pattern(%{123 => :foo, 456 => :bar}) ==
124129
{:ok,
125130
{:map,
126131
[
127-
{:required, :integer, {:union, [{:atom, :foo}, {:atom, :bar}]}},
132+
{:required, :integer, :dynamic},
128133
{:optional, :dynamic, :dynamic}
129134
]}}
130135

0 commit comments

Comments
 (0)