summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@dashbit.co>2020-09-29 14:54:03 +0200
committerJosé Valim <jose.valim@dashbit.co>2020-09-29 15:06:17 +0200
commitbe3f3f205300127f6820507b5f53c0ea6607017a (patch)
treec5ad877b2cadd037cd2c6f5d2490aef35ccfc5df
parent5d212030bcb7458bb72f338430efdbf2146ac531 (diff)
downloadelixir-jv-left-union.tar.gz
Handle var | left_type < right_typejv-left-union
-rw-r--r--lib/elixir/lib/module/types/unify.ex20
-rw-r--r--lib/elixir/test/elixir/module/types/pattern_test.exs11
-rw-r--r--lib/elixir/test/elixir/module/types/unify_test.exs31
3 files changed, 44 insertions, 18 deletions
diff --git a/lib/elixir/lib/module/types/unify.ex b/lib/elixir/lib/module/types/unify.ex
index df1774530..9dcd301aa 100644
--- a/lib/elixir/lib/module/types/unify.ex
+++ b/lib/elixir/lib/module/types/unify.ex
@@ -98,14 +98,22 @@ defmodule Module.Types.Unify do
{:ok, target, context}
end
+ defp do_unify({:union, types}, target, stack, context) do
+ unify_result =
+ map_reduce_ok(types, context, fn type, context ->
+ unify(type, target, stack, context)
+ end)
+
+ case unify_result do
+ {:ok, types, context} -> {:ok, to_union(types, context), context}
+ {:error, context} -> {:error, context}
+ end
+ end
+
defp do_unify(source, target, stack, context) do
cond do
- # This condition exists to handle unions with unbound vars.
- # TODO: handle unions properly. Note we can easily unify
- # "union < type" even if union has vars as the vars must be
- # type
- (match?({:union, _}, source) and has_unbound_var?(source, context)) or
- (match?({:union, _}, target) and has_unbound_var?(target, context)) ->
+ # TODO: This condition exists to handle unions with unbound vars.
+ match?({:union, _}, target) and has_unbound_var?(target, context) ->
{:ok, source, context}
subtype?(source, target, context) ->
diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs
index 458d0645e..8bfdfde39 100644
--- a/lib/elixir/test/elixir/module/types/pattern_test.exs
+++ b/lib/elixir/test/elixir/module/types/pattern_test.exs
@@ -339,7 +339,7 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {:tuple, :atom, _}}} =
quoted_head([x], [is_tuple(x) and is_atom(x)])
- assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
+ assert {:error, {:unable_unify, {{:atom, true}, :tuple, _}}} =
quoted_head([x], [is_tuple(is_atom(x))])
end
@@ -354,16 +354,13 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {{:atom, :foo}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(:foo)])
- assert {:error,
- {:unable_unify, {{:union, [atom: true, atom: false]}, {:list, :dynamic}, _}}} =
+ assert {:error, {:unable_unify, {{:atom, true}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(is_tuple(x))])
- assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
+ assert {:error, {:unable_unify, {{:atom, true}, :tuple, _}}} =
quoted_head([x], [elem(is_tuple(x), 0)])
- assert {:error,
- {:unable_unify,
- {{:union, [atom: true, atom: false]}, {:union, [:integer, :float]}, _}}} =
+ assert {:error, {:unable_unify, {{:atom, true}, {:union, [:integer, :float]}, _}}} =
quoted_head([x], [elem({}, is_tuple(x))])
assert quoted_head([x], [elem({}, 1)]) == {:ok, [var: 0]}
diff --git a/lib/elixir/test/elixir/module/types/unify_test.exs b/lib/elixir/test/elixir/module/types/unify_test.exs
index 6ab44c358..eb9a73e6d 100644
--- a/lib/elixir/test/elixir/module/types/unify_test.exs
+++ b/lib/elixir/test/elixir/module/types/unify_test.exs
@@ -10,10 +10,10 @@ defmodule Module.Types.UnifyTest do
|> lift_result()
end
- defp unify_directed_lift(left, right) do
+ defp unify_directed_lift(left, right, context \\ new_context()) do
stack = %{new_stack() | context: :expr}
- unify(left, right, stack, new_context())
+ unify(left, right, stack, context)
|> lift_result()
end
@@ -268,9 +268,9 @@ defmodule Module.Types.UnifyTest do
{:ok, {:union, [:integer, :atom]}}
assert unify_lift({:union, [:atom]}, {:union, [{:atom, :bar}]}) ==
- {:ok, {:union, [{:atom, :bar}]}}
+ {:ok, {:atom, :bar}}
- assert {:error, {:unable_unify, {{:union, [:integer]}, {:union, [:atom]}, _}}} =
+ assert {:error, {:unable_unify, {:integer, {:union, [:atom]}, _}}} =
unify_lift({:union, [:integer]}, {:union, [:atom]})
end
@@ -342,7 +342,28 @@ defmodule Module.Types.UnifyTest do
unify_lift({:tuple, 1, [{:var, 0}]}, {:tuple, 1, [{:var, 1}]}, context)
end
- # TODO: Vars inside unions
+ # TODO: Vars inside right unions
+
+ test "vars inside left unions" do
+ assert {{:var, 0}, var_context} = new_var({:foo, [version: 0], nil}, new_context())
+
+ assert {:ok, {:var, 0}, context} =
+ unify({:union, [{:var, 0}, :integer]}, :integer, var_context)
+
+ assert Types.lift_type({:var, 0}, context) == :integer
+
+ assert {:ok, {:var, 0}, context} =
+ unify({:union, [{:var, 0}, :integer]}, {:union, [:integer, :atom]}, var_context)
+
+ assert Types.lift_type({:var, 0}, context) == {:union, [:integer, :atom]}
+
+ assert {:error, {:unable_unify, {:integer, {:union, [:binary, :atom]}, _}}} =
+ unify_directed_lift(
+ {:union, [{:var, 0}, :integer]},
+ {:union, [:binary, :atom]},
+ var_context
+ )
+ end
test "recursive type" do
assert {{:var, 0}, var_context} = new_var({:foo, [version: 0], nil}, new_context())