summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>2020-10-28 20:21:26 +0100
committerGitHub <noreply@github.com>2020-10-28 20:21:26 +0100
commitf351ea72d88ec9191a2cfaeebf171a35805c2200 (patch)
tree577b4e38a8a42b3fff263c925604701b7c529703
parent29f14b92ee2c9155a70500ec20861ce1a2f6d2b6 (diff)
downloadelixir-f351ea72d88ec9191a2cfaeebf171a35805c2200.tar.gz
Add expected type (#10459)
-rw-r--r--lib/elixir/lib/module/types.ex2
-rw-r--r--lib/elixir/lib/module/types/expr.ex186
-rw-r--r--lib/elixir/lib/module/types/of.ex40
-rw-r--r--lib/elixir/lib/module/types/pattern.ex18
-rw-r--r--lib/elixir/test/elixir/module/types/type_helper.exs2
-rw-r--r--lib/elixir/test/elixir/module/types/types_test.exs2
6 files changed, 143 insertions, 107 deletions
diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex
index b47d55ca6..d215e46ba 100644
--- a/lib/elixir/lib/module/types.ex
+++ b/lib/elixir/lib/module/types.ex
@@ -60,7 +60,7 @@ defmodule Module.Types do
head_stack = Unify.push_expr_stack(def_expr, stack)
with {:ok, _types, context} <- Pattern.of_head(args, guards, head_stack, context),
- {:ok, _type, context} <- Expr.of_expr(body, stack, context) do
+ {:ok, _type, context} <- Expr.of_expr(body, :dynamic, stack, context) do
context.warnings
else
{:error, {type, error, context}} ->
diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex
index 579bb5772..20e707cb0 100644
--- a/lib/elixir/lib/module/types/expr.ex
+++ b/lib/elixir/lib/module/types/expr.ex
@@ -4,38 +4,39 @@ defmodule Module.Types.Expr do
alias Module.Types.{Of, Pattern}
import Module.Types.{Helpers, Unify}
- def of_expr(expr, %{context: stack_context} = stack, context) when stack_context != :expr do
- of_expr(expr, %{stack | context: :expr}, context)
+ def of_expr(expr, expected, %{context: stack_context} = stack, context)
+ when stack_context != :expr do
+ of_expr(expr, expected, %{stack | context: :expr}, context)
end
# :atom
- def of_expr(atom, _stack, context) when is_atom(atom) do
+ def of_expr(atom, _expected, _stack, context) when is_atom(atom) do
{:ok, {:atom, atom}, context}
end
# 12
- def of_expr(literal, _stack, context) when is_integer(literal) do
+ def of_expr(literal, _expected, _stack, context) when is_integer(literal) do
{:ok, :integer, context}
end
# 1.2
- def of_expr(literal, _stack, context) when is_float(literal) do
+ def of_expr(literal, _expected, _stack, context) when is_float(literal) do
{:ok, :float, context}
end
# "..."
- def of_expr(literal, _stack, context) when is_binary(literal) do
+ def of_expr(literal, _expected, _stack, context) when is_binary(literal) do
{:ok, :binary, context}
end
# #PID<...>
- def of_expr(literal, _stack, context) when is_pid(literal) do
+ def of_expr(literal, _expected, _stack, context) when is_pid(literal) do
{:ok, :dynamic, context}
end
# <<...>>>
- def of_expr({:<<>>, _meta, args}, stack, context) do
- result = Of.binary(args, stack, context, &of_expr/3)
+ def of_expr({:<<>>, _meta, args}, _expected, stack, context) do
+ result = Of.binary(args, stack, context, &of_expr/4)
case result do
{:ok, context} -> {:ok, :binary, context}
@@ -44,18 +45,18 @@ defmodule Module.Types.Expr do
end
# left | []
- def of_expr({:|, _meta, [left_expr, []]} = expr, stack, context) do
+ def of_expr({:|, _meta, [left_expr, []]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
- of_expr(left_expr, stack, context)
+ of_expr(left_expr, :dynamic, stack, context)
end
# left | right
- def of_expr({:|, _meta, [left_expr, right_expr]} = expr, stack, context) do
+ def of_expr({:|, _meta, [left_expr, right_expr]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
- case of_expr(left_expr, stack, context) do
+ case of_expr(left_expr, :dynamic, stack, context) do
{:ok, left, context} ->
- case of_expr(right_expr, stack, context) do
+ case of_expr(right_expr, :dynamic, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, to_union([left, right], context), context}
@@ -72,22 +73,23 @@ defmodule Module.Types.Expr do
end
# []
- def of_expr([], _stack, context) do
+ def of_expr([], _expected, _stack, context) do
{:ok, {:list, :dynamic}, context}
end
# [expr, ...]
- def of_expr(exprs, stack, context) when is_list(exprs) do
+ def of_expr(exprs, _expected, stack, context) when is_list(exprs) do
stack = push_expr_stack(exprs, stack)
- case map_reduce_ok(exprs, context, &of_expr(&1, stack, &2)) do
+ case map_reduce_ok(exprs, context, &of_expr(&1, :dynamic, stack, &2)) do
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
{:error, reason} -> {:error, reason}
end
end
# __CALLER__
- def of_expr({:__CALLER__, _meta, var_context}, _stack, context) when is_atom(var_context) do
+ def of_expr({:__CALLER__, _meta, var_context}, _expected, _stack, context)
+ when is_atom(var_context) do
struct_pair = {:required, {:atom, :__struct__}, {:atom, Macro.Env}}
pairs =
@@ -99,7 +101,8 @@ defmodule Module.Types.Expr do
end
# __STACKTRACE__
- def of_expr({:__STACKTRACE__, _meta, var_context}, _stack, context) when is_atom(var_context) do
+ def of_expr({:__STACKTRACE__, _meta, var_context}, _expected, _stack, context)
+ when is_atom(var_context) do
file = {:tuple, 2, [{:atom, :file}, {:list, :integer}]}
line = {:tuple, 2, [{:atom, :line}, :integer]}
file_line = {:list, {:union, [file, line]}}
@@ -108,41 +111,45 @@ defmodule Module.Types.Expr do
end
# var
- def of_expr(var, _stack, context) when is_var(var) do
+ def of_expr(var, _expected, _stack, context) when is_var(var) do
{:ok, get_var!(var, context), context}
end
# {left, right}
- def of_expr({left, right}, stack, context) do
- of_expr({:{}, [], [left, right]}, stack, context)
+ def of_expr({left, right}, expected, stack, context) do
+ of_expr({:{}, [], [left, right]}, expected, stack, context)
end
# {...}
- def of_expr({:{}, _meta, exprs} = expr, stack, context) do
+ def of_expr({:{}, _meta, exprs} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
- case map_reduce_ok(exprs, context, &of_expr(&1, stack, &2)) do
+ case map_reduce_ok(exprs, context, &of_expr(&1, :dynamic, stack, &2)) do
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
{:error, reason} -> {:error, reason}
end
end
# left = right
- def of_expr({:=, _meta, [left_expr, right_expr]} = expr, stack, context) do
+ def of_expr({:=, _meta, [left_expr, right_expr]} = expr, _expected, stack, context) do
+ # TODO: We might want to bring the expected type forward in case the type of this
+ # pattern is not useful. For example: 1 = _ = expr
+
stack = push_expr_stack(expr, stack)
with {:ok, left_type, context} <-
Pattern.of_pattern(left_expr, stack, context),
- {:ok, right_type, context} <- of_expr(right_expr, stack, context),
+ {:ok, right_type, context} <- of_expr(right_expr, left_type, stack, context),
do: unify(right_type, left_type, %{stack | context: :pattern}, context)
end
# %{map | ...}
- def of_expr({:%{}, _, [{:|, _, [map, args]}]} = expr, stack, context) do
+ def of_expr({:%{}, _, [{:|, _, [map, args]}]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
+ map_type = {:map, [{:optional, :dynamic, :dynamic}]}
- with {:ok, map_type, context} <- of_expr(map, stack, context),
- {:ok, {:map, arg_pairs}, context} <- Of.closed_map(args, stack, context, &of_expr/3),
+ with {:ok, map_type, context} <- of_expr(map, map_type, stack, context),
+ {:ok, {:map, arg_pairs}, context} <- Of.closed_map(args, stack, context, &of_expr/4),
dynamic_value_pairs =
Enum.map(arg_pairs, fn {:required, key, _value} -> {:required, key, :dynamic} end),
args_type = {:map, dynamic_value_pairs ++ [{:optional, :dynamic, :dynamic}]},
@@ -160,55 +167,68 @@ defmodule Module.Types.Expr do
end
# %Struct{map | ...}
- def of_expr({:%, meta, [module, {:%{}, _, [{:|, _, [_, _]}]} = update]} = expr, stack, context) do
+ def of_expr(
+ {:%, meta, [module, {:%{}, _, [{:|, _, [_, _]}]} = update]} = expr,
+ _expected,
+ stack,
+ context
+ ) do
stack = push_expr_stack(expr, stack)
+ map_type = {:map, [{:optional, :dynamic, :dynamic}]}
with {:ok, struct, context} <- Of.struct(module, meta, context),
- {:ok, update, context} <- of_expr(update, stack, context) do
+ {:ok, update, context} <- of_expr(update, map_type, stack, context) do
unify(update, struct, stack, context)
end
end
# %{...}
- def of_expr({:%{}, _meta, args} = expr, stack, context) do
+ def of_expr({:%{}, _meta, args} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
- Of.closed_map(args, stack, context, &of_expr/3)
+ Of.closed_map(args, stack, context, &of_expr/4)
end
# %Struct{...}
- def of_expr({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context) do
+ def of_expr({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
- {:ok, map, context} <- Of.open_map(args, stack, context, &of_expr/3) do
+ {:ok, map, context} <- Of.open_map(args, stack, context, &of_expr/4) do
unify(map, struct, stack, context)
end
end
# ()
- def of_expr({:__block__, _meta, []}, _stack, context) do
+ def of_expr({:__block__, _meta, []}, _expected, _stack, context) do
{:ok, {:atom, nil}, context}
end
# (expr; expr)
- def of_expr({:__block__, _meta, exprs}, stack, context) do
- case map_reduce_ok(exprs, context, &of_expr(&1, stack, &2)) do
+ def of_expr({:__block__, _meta, exprs}, expected, stack, context) do
+ expected_types = List.duplicate(:dynamic, length(exprs) - 1) ++ [expected]
+
+ result =
+ map_reduce_ok(Enum.zip(exprs, expected_types), context, fn {expr, expected}, context ->
+ of_expr(expr, expected, stack, context)
+ end)
+
+ case result do
{:ok, expr_types, context} -> {:ok, Enum.at(expr_types, -1), context}
{:error, reason} -> {:error, reason}
end
end
# case expr do pat -> expr end
- def of_expr({:case, _meta, [case_expr, [{:do, clauses}]]} = expr, stack, context) do
+ def of_expr({:case, _meta, [case_expr, [{:do, clauses}]]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
- with {:ok, _expr_type, context} <- of_expr(case_expr, stack, context),
+ with {:ok, _expr_type, context} <- of_expr(case_expr, :dynamic, stack, context),
{:ok, context} <- of_clauses(clauses, stack, context),
do: {:ok, :dynamic, context}
end
# fn pat -> expr end
- def of_expr({:fn, _meta, clauses} = expr, stack, context) do
+ def of_expr({:fn, _meta, clauses} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
case of_clauses(clauses, stack, context) do
@@ -221,7 +241,7 @@ defmodule Module.Types.Expr do
@try_clause_blocks [:catch, :else, :after]
# try do expr end
- def of_expr({:try, _meta, [blocks]} = expr, stack, context) do
+ def of_expr({:try, _meta, [blocks]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
{result, context} =
@@ -231,20 +251,20 @@ defmodule Module.Types.Expr do
{:->, _, [[{:in, _, [var, _exceptions]}], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
- with {:ok, context} <- of_expr_context(body, stack, context) do
+ with {:ok, context} <- of_expr_context(body, :dynamic, stack, context) do
{:ok, keep_warnings(acc, context)}
end
{:->, _, [[var], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
- with {:ok, context} <- of_expr_context(body, stack, context) do
+ with {:ok, context} <- of_expr_context(body, :dynamic, stack, context) do
{:ok, keep_warnings(acc, context)}
end
end)
{block, body}, context = acc when block in @try_blocks ->
- with {:ok, context} <- of_expr_context(body, stack, context) do
+ with {:ok, context} <- of_expr_context(body, :dynamic, stack, context) do
{:ok, keep_warnings(acc, context)}
end
@@ -259,7 +279,7 @@ defmodule Module.Types.Expr do
end
# receive do pat -> expr end
- def of_expr({:receive, _meta, [blocks]} = expr, stack, context) do
+ def of_expr({:receive, _meta, [blocks]} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
{result, context} =
@@ -271,8 +291,8 @@ defmodule Module.Types.Expr do
of_clauses(clauses, stack, context)
{:after, [{:->, _meta, [head, body]}]}, context = acc ->
- with {:ok, _type, context} <- of_expr(head, stack, context),
- {:ok, _type, context} <- of_expr(body, stack, context),
+ with {:ok, _type, context} <- of_expr(head, :dynamic, stack, context),
+ {:ok, _type, context} <- of_expr(body, :dynamic, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
@@ -283,7 +303,7 @@ defmodule Module.Types.Expr do
end
# for pat <- expr do expr end
- def of_expr({:for, _meta, args} = expr, stack, context) do
+ def of_expr({:for, _meta, args} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
{clauses, [[{:do, block} | opts]]} = Enum.split(args, -1)
@@ -294,7 +314,7 @@ defmodule Module.Types.Expr do
{:ok, :dynamic, context}
end
else
- with {:ok, _type, context} <- of_expr(block, stack, context) do
+ with {:ok, _type, context} <- of_expr(block, :dynamic, stack, context) do
{:ok, :dynamic, context}
end
end
@@ -302,7 +322,7 @@ defmodule Module.Types.Expr do
end
# with pat <- expr do expr end
- def of_expr({:with, _meta, clauses} = expr, stack, context) do
+ def of_expr({:with, _meta, clauses} = expr, _expected, stack, context) do
stack = push_expr_stack(expr, stack)
case reduce_ok(clauses, context, &with_clause(&1, stack, &2)) do
@@ -311,13 +331,14 @@ defmodule Module.Types.Expr do
end
end
- # fun.(arg)
- def of_expr({{:., _meta1, [fun]}, _meta2, args} = expr, stack, context) do
+ # fun.(args)
+ def of_expr({{:., _meta1, [fun]}, _meta2, args} = expr, _expected, stack, context) do
+ # TODO: Use expected type to infer intersection return type
stack = push_expr_stack(expr, stack)
- case of_expr(fun, stack, context) do
+ case of_expr(fun, :dynamic, stack, context) do
{:ok, _fun_type, context} ->
- case map_reduce_ok(args, context, &of_expr(&1, stack, &2)) do
+ case map_reduce_ok(args, context, &of_expr(&1, :dynamic, stack, &2)) do
{:ok, _arg_types, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
@@ -328,12 +349,12 @@ defmodule Module.Types.Expr do
end
# expr.key_or_fun
- def of_expr({{:., _meta1, [expr1, key_or_fun]}, meta2, []} = expr2, stack, context)
+ def of_expr({{:., _meta1, [expr1, key_or_fun]}, meta2, []} = expr2, _expected, stack, context)
when not is_atom(expr1) do
stack = push_expr_stack(expr2, stack)
if Keyword.get(meta2, :no_parens, false) do
- with {:ok, expr_type, context} <- of_expr(expr1, stack, context),
+ with {:ok, expr_type, context} <- of_expr(expr1, :dynamic, stack, context),
{value_var, context} = add_var(context),
pair_type = {:required, {:atom, key_or_fun}, value_var},
optional_type = {:optional, :dynamic, :dynamic},
@@ -341,20 +362,23 @@ defmodule Module.Types.Expr do
{:ok, _map_type, context} <- unify(map_field_type, expr_type, stack, context),
do: {:ok, value_var, context}
else
- with {:ok, expr_type, context} <- of_expr(expr1, stack, context),
+ # TODO: Use expected type to infer intersection return type
+ with {:ok, expr_type, context} <- of_expr(expr1, :dynamic, stack, context),
{:ok, _map_type, context} <- unify(expr_type, :atom, stack, context),
do: {:ok, :dynamic, context}
end
end
# expr.fun(arg)
- def of_expr({{:., meta1, [expr1, fun]}, _meta2, args} = expr2, stack, context) do
+ def of_expr({{:., meta1, [expr1, fun]}, _meta2, args} = expr2, _expected, stack, context) do
+ # TODO: Use expected type to infer intersection return type
+
context = Of.remote(expr1, fun, length(args), meta1, context)
stack = push_expr_stack(expr2, stack)
- with {:ok, _expr_type, context} <- of_expr(expr1, stack, context),
- {:ok, _fun_type, context} <- of_expr(fun, stack, context) do
- case map_reduce_ok(args, context, &of_expr(&1, stack, &2)) do
+ with {:ok, _expr_type, context} <- of_expr(expr1, :dynamic, stack, context),
+ {:ok, _fun_type, context} <- of_expr(fun, :dynamic, stack, context) do
+ case map_reduce_ok(args, context, &of_expr(&1, :dynamic, stack, &2)) do
{:ok, _arg_types, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
@@ -362,7 +386,12 @@ defmodule Module.Types.Expr do
end
# &Foo.bar/1
- def of_expr({:&, meta, [{:/, _, [{{:., _, [module, fun]}, _, []}, arity]}]}, _stack, context)
+ def of_expr(
+ {:&, meta, [{:/, _, [{{:., _, [module, fun]}, _, []}, arity]}]},
+ _expected,
+ _stack,
+ context
+ )
when is_atom(module) and is_atom(fun) do
context = Of.remote(module, fun, arity, meta, context)
{:ok, :dynamic, context}
@@ -370,16 +399,19 @@ defmodule Module.Types.Expr do
# &foo/1
# & &1
- def of_expr({:&, _meta, _arg}, _stack, context) do
+ def of_expr({:&, _meta, _arg}, _expected, _stack, context) do
# TODO: Function type
{:ok, :dynamic, context}
end
# fun(arg)
- def of_expr({fun, _meta, args} = expr, stack, context) when is_atom(fun) and is_list(args) do
+ def of_expr({fun, _meta, args} = expr, _expected, stack, context)
+ when is_atom(fun) and is_list(args) do
+ # TODO: Use expected type to infer intersection return type
+
stack = push_expr_stack(expr, stack)
- case map_reduce_ok(args, context, &of_expr(&1, stack, &2)) do
+ case map_reduce_ok(args, context, &of_expr(&1, :dynamic, stack, &2)) do
{:ok, _arg_types, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
@@ -389,14 +421,14 @@ defmodule Module.Types.Expr do
{pattern, guards} = extract_head([left])
with {:ok, _pattern_type, context} <- Pattern.of_head([pattern], guards, stack, context),
- {:ok, _expr_type, context} <- of_expr(expr, stack, context),
+ {:ok, _expr_type, context} <- of_expr(expr, :dynamic, stack, context),
do: {:ok, context}
end
defp for_clause({:<<>>, _, [{:<-, _, [pattern, expr]}]}, stack, context) do
# TODO: the compiler guarantees pattern is a binary but we need to check expr is a binary
with {:ok, _pattern_type, context} <- Pattern.of_pattern(pattern, stack, context),
- {:ok, _expr_type, context} <- of_expr(expr, stack, context),
+ {:ok, _expr_type, context} <- of_expr(expr, :dynamic, stack, context),
do: {:ok, context}
end
@@ -405,15 +437,15 @@ defmodule Module.Types.Expr do
end
defp for_clause(expr, stack, context) do
- of_expr_context(expr, stack, context)
+ of_expr_context(expr, :dynamic, stack, context)
end
defp for_option({:into, expr}, stack, context) do
- of_expr_context(expr, stack, context)
+ of_expr_context(expr, :dynamic, stack, context)
end
defp for_option({:reduce, expr}, stack, context) do
- of_expr_context(expr, stack, context)
+ of_expr_context(expr, :dynamic, stack, context)
end
defp for_option({:uniq, _}, _stack, context) do
@@ -424,7 +456,7 @@ defmodule Module.Types.Expr do
{pattern, guards} = extract_head([left])
with {:ok, _pattern_type, context} <- Pattern.of_head([pattern], guards, stack, context),
- {:ok, _expr_type, context} <- of_expr(expr, stack, context),
+ {:ok, _expr_type, context} <- of_expr(expr, :dynamic, stack, context),
do: {:ok, context}
end
@@ -433,11 +465,11 @@ defmodule Module.Types.Expr do
end
defp with_clause(expr, stack, context) do
- of_expr_context(expr, stack, context)
+ of_expr_context(expr, :dynamic, stack, context)
end
defp with_option({:do, body}, stack, context) do
- of_expr_context(body, stack, context)
+ of_expr_context(body, :dynamic, stack, context)
end
defp with_option({:else, clauses}, stack, context) do
@@ -449,7 +481,7 @@ defmodule Module.Types.Expr do
{patterns, guards} = extract_head(head)
with {:ok, _, context} <- Pattern.of_head(patterns, guards, stack, context),
- {:ok, _expr_type, context} <- of_expr(body, stack, context),
+ {:ok, _expr_type, context} <- of_expr(body, :dynamic, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
end
@@ -477,8 +509,8 @@ defmodule Module.Types.Expr do
[other]
end
- defp of_expr_context(expr, stack, context) do
- case of_expr(expr, stack, context) do
+ defp of_expr_context(expr, expected, stack, context) do
+ case of_expr(expr, expected, stack, context) do
{:ok, _type, context} -> {:ok, context}
{:error, reason} -> {:error, reason}
end
diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex
index 6157ecab7..809238af5 100644
--- a/lib/elixir/lib/module/types/of.ex
+++ b/lib/elixir/lib/module/types/of.ex
@@ -14,8 +14,8 @@ defmodule Module.Types.Of do
@doc """
Handles open maps (with dynamic => dynamic).
"""
- def open_map(args, stack, context, fun) do
- with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
+ def open_map(args, stack, context, of_fun) do
+ with {:ok, pairs, context} <- map_pairs(args, stack, context, of_fun) do
# If we match on a map such as %{"foo" => "bar"}, we cannot
# assert that %{binary() => binary()}, since we are matching
# only a single binary of infinite possible values. Therefore,
@@ -48,16 +48,16 @@ defmodule Module.Types.Of do
@doc """
Handles closed maps (without dynamic => dynamic).
"""
- def closed_map(args, stack, context, fun) do
- with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
+ def closed_map(args, stack, context, of_fun) do
+ with {:ok, pairs, context} <- map_pairs(args, stack, context, of_fun) do
{:ok, {:map, closed_to_unions(pairs, context)}, context}
end
end
- defp map_pairs(pairs, stack, context, fun) do
+ defp map_pairs(pairs, stack, context, of_fun) do
map_reduce_ok(pairs, context, fn {key, value}, context ->
- with {:ok, key_type, context} <- fun.(key, stack, context),
- {:ok, value_type, context} <- fun.(value, stack, context),
+ with {:ok, key_type, context} <- of_fun.(key, :dynamic, stack, context),
+ {:ok, value_type, context} <- of_fun.(value, :dynamic, stack, context),
do: {:ok, {key_type, value_type}, context}
end)
end
@@ -128,39 +128,39 @@ defmodule Module.Types.Of do
In the stack, we add nodes such as <<expr>>, <<..., expr>>, etc,
based on the position of the expression within the binary.
"""
- def binary([], _stack, context, _fun) do
+ def binary([], _stack, context, _of_fun) do
{:ok, context}
end
- def binary([head], stack, context, fun) do
+ def binary([head], stack, context, of_fun) do
head_stack = push_expr_stack({:<<>>, get_meta(head), [head]}, stack)
- binary_segment(head, head_stack, context, fun)
+ binary_segment(head, head_stack, context, of_fun)
end
- def binary([head | tail], stack, context, fun) do
+ def binary([head | tail], stack, context, of_fun) do
head_stack = push_expr_stack({:<<>>, get_meta(head), [head, @suffix]}, stack)
- case binary_segment(head, head_stack, context, fun) do
- {:ok, context} -> binary_many(tail, stack, context, fun)
+ case binary_segment(head, head_stack, context, of_fun) do
+ {:ok, context} -> binary_many(tail, stack, context, of_fun)
{:error, reason} -> {:error, reason}
end
end
- defp binary_many([last], stack, context, fun) do
+ defp binary_many([last], stack, context, of_fun) do
last_stack = push_expr_stack({:<<>>, get_meta(last), [@prefix, last]}, stack)
- binary_segment(last, last_stack, context, fun)
+ binary_segment(last, last_stack, context, of_fun)
end
- defp binary_many([head | tail], stack, context, fun) do
+ defp binary_many([head | tail], stack, context, of_fun) do
head_stack = push_expr_stack({:<<>>, get_meta(head), [@prefix, head, @suffix]}, stack)
- case binary_segment(head, head_stack, context, fun) do
- {:ok, context} -> binary_many(tail, stack, context, fun)
+ case binary_segment(head, head_stack, context, of_fun) do
+ {:ok, context} -> binary_many(tail, stack, context, of_fun)
{:error, reason} -> {:error, reason}
end
end
- defp binary_segment({:"::", _meta, [expr, specifiers]}, stack, context, fun) do
+ defp binary_segment({:"::", _meta, [expr, specifiers]}, stack, context, of_fun) do
expected_type =
collect_binary_specifier(specifiers, &binary_type(stack.context, &1)) || :integer
@@ -177,7 +177,7 @@ defmodule Module.Types.Of do
{:ok, context}
true ->
- with {:ok, type, context} <- fun.(expr, stack, context),
+ with {:ok, type, context} <- of_fun.(expr, expected_type, stack, context),
{:ok, _type, context} <- unify(type, expected_type, stack, context),
do: {:ok, context}
end
diff --git a/lib/elixir/lib/module/types/pattern.ex b/lib/elixir/lib/module/types/pattern.ex
index 184081f3e..870bcfed3 100644
--- a/lib/elixir/lib/module/types/pattern.ex
+++ b/lib/elixir/lib/module/types/pattern.ex
@@ -46,9 +46,7 @@ defmodule Module.Types.Pattern do
# <<...>>>
def of_pattern({:<<>>, _meta, args}, stack, context) do
- result = Of.binary(args, stack, context, &of_pattern/3)
-
- case result do
+ case Of.binary(args, stack, context, &of_pattern_expected/4) do
{:ok, context} -> {:ok, :binary, context}
{:error, reason} -> {:error, reason}
end
@@ -166,7 +164,7 @@ defmodule Module.Types.Pattern do
# %{...}
def of_pattern({:%{}, _meta, args} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
- Of.open_map(args, stack, context, &of_pattern/3)
+ Of.open_map(args, stack, context, &of_pattern_expected/4)
end
# %Struct{...}
@@ -175,7 +173,7 @@ defmodule Module.Types.Pattern do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
- {:ok, map, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
+ {:ok, map, context} <- Of.open_map(args, stack, context, &of_pattern_expected/4) do
unify(map, struct, stack, context)
end
end
@@ -189,7 +187,8 @@ defmodule Module.Types.Pattern do
when is_atom(var_context) do
stack = push_expr_stack(expr, stack)
- with {:ok, {:map, pairs}, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
+ with {:ok, {:map, pairs}, context} <-
+ Of.open_map(args, stack, context, &of_pattern_expected/4) do
{:ok, {:map, [{:required, {:atom, :__struct__}, :atom} | pairs]}, context}
end
end
@@ -200,7 +199,8 @@ defmodule Module.Types.Pattern do
with {:ok, var_type, context} = of_pattern(var, stack, context),
{:ok, _, context} <- unify(var_type, :atom, stack, context),
- {:ok, {:map, pairs}, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
+ {:ok, {:map, pairs}, context} <-
+ Of.open_map(args, stack, context, &of_pattern_expected/4) do
{:ok, {:map, [{:required, {:atom, :__struct__}, var_type} | pairs]}, context}
end
end
@@ -209,6 +209,10 @@ defmodule Module.Types.Pattern do
def unify_kinds(_, :required), do: :required
def unify_kinds(:optional, :optional), do: :optional
+ defp of_pattern_expected(expr, _expected, stack, context) do
+ of_pattern(expr, stack, context)
+ end
+
## GUARDS
# TODO: Some guards can be changed to intersection types or higher order types
diff --git a/lib/elixir/test/elixir/module/types/type_helper.exs b/lib/elixir/test/elixir/module/types/type_helper.exs
index 55c649f44..89f1279ad 100644
--- a/lib/elixir/test/elixir/module/types/type_helper.exs
+++ b/lib/elixir/test/elixir/module/types/type_helper.exs
@@ -15,7 +15,7 @@ defmodule TypeHelper do
def __expr__({patterns, guards, body}) do
with {:ok, _types, context} <-
Pattern.of_head(patterns, guards, new_stack(), new_context()),
- {:ok, type, context} <- Expr.of_expr(body, new_stack(), context) do
+ {:ok, type, context} <- Expr.of_expr(body, :dynamic, new_stack(), context) do
{:ok, [type] |> Unify.lift_types(context) |> hd()}
else
{:error, {type, reason, _context}} ->
diff --git a/lib/elixir/test/elixir/module/types/types_test.exs b/lib/elixir/test/elixir/module/types/types_test.exs
index d17fceeb8..69c84508d 100644
--- a/lib/elixir/test/elixir/module/types/types_test.exs
+++ b/lib/elixir/test/elixir/module/types/types_test.exs
@@ -22,7 +22,7 @@ defmodule Module.Types.TypesTest do
def __expr__({patterns, guards, body}) do
with {:ok, _types, context} <-
Pattern.of_head(patterns, guards, TypeHelper.new_stack(), TypeHelper.new_context()),
- {:ok, type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
+ {:ok, type, context} <- Expr.of_expr(body, :dynamic, TypeHelper.new_stack(), context) do
case context.warnings do
[warning] -> {:warning, warning}
_ -> flunk("expexted error, got: #{inspect([type] |> Unify.lift_types(context) |> hd())}")