summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@dashbit.co>2020-09-29 13:06:11 +0200
committerJosé Valim <jose.valim@dashbit.co>2020-09-29 13:11:03 +0200
commit5d212030bcb7458bb72f338430efdbf2146ac531 (patch)
tree9ba4feaa65a980b690424f16ae19c4ff410526e4
parentf01d47ceb62eabd518c75d75862567f82a0c3ce5 (diff)
downloadelixir-5d212030bcb7458bb72f338430efdbf2146ac531.tar.gz
Fix inference of open maps with non-singleton keys
Closes #10371.
-rw-r--r--lib/elixir/lib/module/types/of.ex18
-rw-r--r--lib/elixir/lib/module/types/unify.ex21
-rw-r--r--lib/elixir/test/elixir/module/types/pattern_test.exs9
3 files changed, 41 insertions, 7 deletions
diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex
index 5c5b0bd40..ee147a137 100644
--- a/lib/elixir/lib/module/types/of.ex
+++ b/lib/elixir/lib/module/types/of.ex
@@ -16,7 +16,17 @@ defmodule Module.Types.Of do
"""
def open_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
- {:ok, {:map, pairs_to_unions(pairs, context) ++ [{:optional, :dynamic, :dynamic}]}, context}
+ pairs =
+ for {key, value} <- pairs, not has_unbound_var?(key, context) do
+ if singleton?(key, context) do
+ {key, value}
+ else
+ {key, to_union([value, :dynamic], context)}
+ end
+ end
+
+ triplets = pairs_to_unions(pairs, [], context) ++ [{:optional, :dynamic, :dynamic}]
+ {:ok, {:map, triplets}, context}
end
end
@@ -25,7 +35,7 @@ defmodule Module.Types.Of do
"""
def closed_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
- {:ok, {:map, pairs_to_unions(pairs, context)}, context}
+ {:ok, {:map, closed_to_unions(pairs, context)}, context}
end
end
@@ -37,9 +47,9 @@ defmodule Module.Types.Of do
end)
end
- defp pairs_to_unions([{key, value}], _context), do: [{:required, key, value}]
+ defp closed_to_unions([{key, value}], _context), do: [{:required, key, value}]
- defp pairs_to_unions(pairs, context) do
+ defp closed_to_unions(pairs, context) do
case Enum.split_with(pairs, fn {key, _value} -> has_unbound_var?(key, context) end) do
{[], pairs} -> pairs_to_unions(pairs, [], context)
{[_ | _], pairs} -> pairs_to_unions([{:dynamic, :dynamic} | pairs], [], context)
diff --git a/lib/elixir/lib/module/types/unify.ex b/lib/elixir/lib/module/types/unify.ex
index bc839199a..df1774530 100644
--- a/lib/elixir/lib/module/types/unify.ex
+++ b/lib/elixir/lib/module/types/unify.ex
@@ -560,13 +560,32 @@ defmodule Module.Types.Unify do
def has_unbound_var?(_type, _context), do: false
@doc """
+ Returns true if it is a singleton type.
+
+ Only atoms are singleton types. Unbound vars are not
+ considered singleton types.
+ """
+ def singleton?({:var, var}, context) do
+ case context.types do
+ %{^var => :unbound} -> false
+ %{^var => type} -> singleton?(type, context)
+ end
+ end
+
+ def singleton?({:atom, _}, _context), do: true
+ def singleton?(_type, _context), do: false
+
+ @doc """
Checks if the first argument is a subtype of the second argument.
This function assumes that:
- * dynamic is not considered a subtype of all other types but the top type
* unbound variables are not subtype of anything
+ * dynamic is not considered a subtype of all other types but the top type.
+ This allows this function can be used for ordering, in other cases, you
+ may need to check for both sides
+
"""
def subtype?(type, type, _context), do: true
diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs
index 5eb336b1b..458d0645e 100644
--- a/lib/elixir/test/elixir/module/types/pattern_test.exs
+++ b/lib/elixir/test/elixir/module/types/pattern_test.exs
@@ -118,13 +118,18 @@ defmodule Module.Types.PatternTest do
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]}}
assert quoted_pattern(%{123 => a}) ==
- {:ok, {:map, [{:required, :integer, {:var, 0}}, {:optional, :dynamic, :dynamic}]}}
+ {:ok,
+ {:map,
+ [
+ {:required, :integer, {:union, [{:var, 0}, :dynamic]}},
+ {:optional, :dynamic, :dynamic}
+ ]}}
assert quoted_pattern(%{123 => :foo, 456 => :bar}) ==
{:ok,
{:map,
[
- {:required, :integer, {:union, [{:atom, :foo}, {:atom, :bar}]}},
+ {:required, :integer, :dynamic},
{:optional, :dynamic, :dynamic}
]}}