diff options
author | José Valim <jose.valim@dashbit.co> | 2020-09-29 13:06:11 +0200 |
---|---|---|
committer | José Valim <jose.valim@dashbit.co> | 2020-09-29 13:11:03 +0200 |
commit | 5d212030bcb7458bb72f338430efdbf2146ac531 (patch) | |
tree | 9ba4feaa65a980b690424f16ae19c4ff410526e4 | |
parent | f01d47ceb62eabd518c75d75862567f82a0c3ce5 (diff) | |
download | elixir-5d212030bcb7458bb72f338430efdbf2146ac531.tar.gz |
Fix inference of open maps with non-singleton keys
Closes #10371.
-rw-r--r-- | lib/elixir/lib/module/types/of.ex | 18 | ||||
-rw-r--r-- | lib/elixir/lib/module/types/unify.ex | 21 | ||||
-rw-r--r-- | lib/elixir/test/elixir/module/types/pattern_test.exs | 9 |
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} ]}} |