diff options
author | Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com> | 2020-10-28 20:21:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-28 20:21:26 +0100 |
commit | f351ea72d88ec9191a2cfaeebf171a35805c2200 (patch) | |
tree | 577b4e38a8a42b3fff263c925604701b7c529703 | |
parent | 29f14b92ee2c9155a70500ec20861ce1a2f6d2b6 (diff) | |
download | elixir-f351ea72d88ec9191a2cfaeebf171a35805c2200.tar.gz |
Add expected type (#10459)
-rw-r--r-- | lib/elixir/lib/module/types.ex | 2 | ||||
-rw-r--r-- | lib/elixir/lib/module/types/expr.ex | 186 | ||||
-rw-r--r-- | lib/elixir/lib/module/types/of.ex | 40 | ||||
-rw-r--r-- | lib/elixir/lib/module/types/pattern.ex | 18 | ||||
-rw-r--r-- | lib/elixir/test/elixir/module/types/type_helper.exs | 2 | ||||
-rw-r--r-- | lib/elixir/test/elixir/module/types/types_test.exs | 2 |
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())}") |