diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex index 0db50c7e28..a4fcf190e0 100644 --- a/lib/elixir/lib/module/types.ex +++ b/lib/elixir/lib/module/types.ex @@ -74,9 +74,7 @@ defmodule Module.Types do # List of calls to not warn on as undefined no_warn_undefined: no_warn_undefined, # A list of cached modules received from the parallel compiler - cache: cache, - # If variable refinements is enabled or not - refine: true + cache: cache } end diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index b5d7e4b71a..2c195f074d 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -403,7 +403,9 @@ defmodule Module.Types.Expr do {"rescue #{expr_to_string(expr)} ->", hints} end - {:ok, _type, context} = Of.refine_var(var, expected, expr, formatter, stack, context) + {:ok, _type, context} = + Of.refine_var(var, {expected, expr}, formatter, stack, context) + context end diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex index a263344a76..b97d3ab4f8 100644 --- a/lib/elixir/lib/module/types/of.ex +++ b/lib/elixir/lib/module/types/of.ex @@ -29,7 +29,7 @@ defmodule Module.Types.Of do @doc """ Refines the type of a variable. """ - def refine_var(var, type, expr, formatter \\ :default, stack, context) do + def refine_var(var, {type, expr}, formatter \\ :default, stack, context) do {var_name, meta, var_context} = var version = Keyword.fetch!(meta, :version) @@ -251,7 +251,7 @@ defmodule Module.Types.Of do end with {:ok, _type, context} <- result do - {:ok, specifier_size(kind, right, stack, context)} + {:ok, specifier_size(kind, right, expr, stack, context)} end end @@ -271,11 +271,11 @@ defmodule Module.Types.Of do defp specifier_type(_kind, {:binary, _, _}), do: @binary defp specifier_type(_kind, _specifier), do: @integer - defp specifier_size(kind, {:-, _, [left, right]}, stack, context) do - specifier_size(kind, right, stack, specifier_size(kind, left, stack, context)) + defp specifier_size(kind, {:-, _, [left, right]}, expr, stack, context) do + specifier_size(kind, right, expr, stack, specifier_size(kind, left, expr, stack, context)) end - defp specifier_size(:expr, {:size, _, [arg]} = expr, stack, context) + defp specifier_size(:expr, {:size, _, [arg]}, expr, stack, context) when not is_integer(arg) do case Module.Types.Expr.of_expr(arg, {integer(), expr}, stack, context) do {:ok, _, context} -> context @@ -283,7 +283,7 @@ defmodule Module.Types.Of do end end - defp specifier_size(_pattern_or_guard, {:size, _, [arg]} = expr, stack, context) + defp specifier_size(_pattern_or_guard, {:size, _, [arg]}, expr, stack, context) when not is_integer(arg) do case Module.Types.Pattern.of_guard(arg, {integer(), expr}, stack, context) do {:ok, _, context} -> context @@ -291,7 +291,7 @@ defmodule Module.Types.Of do end end - defp specifier_size(_kind, _expr, _stack, context) do + defp specifier_size(_kind, _specifier, _expr, _stack, context) do context end diff --git a/lib/elixir/lib/module/types/pattern.ex b/lib/elixir/lib/module/types/pattern.ex index fec04321c2..0701a2c107 100644 --- a/lib/elixir/lib/module/types/pattern.ex +++ b/lib/elixir/lib/module/types/pattern.ex @@ -38,11 +38,6 @@ defmodule Module.Types.Pattern do of_pattern(expr, {dynamic(), expr}, stack, context) end - # ^var - def of_pattern({:^, _meta, [var]}, expected_expr, stack, context) do - Of.intersect(Of.var(var, context), expected_expr, stack, context) - end - # left = right # TODO: Track variables and handle nesting def of_pattern({:=, _meta, [left_expr, right_expr]}, {expected, expr}, stack, context) do @@ -73,7 +68,7 @@ defmodule Module.Types.Pattern do ) when not is_atom(struct_var) do with {:ok, struct_type, context} <- - of_pattern(struct_var, {atom(), expr}, %{stack | refine: false}, context), + of_struct_var(struct_var, {atom(), expr}, stack, context), {:ok, map_type, context} <- of_open_map(args, [__struct__: struct_type], expected_expr, stack, context), {_, struct_type} = map_fetch(map_type, :__struct__), @@ -111,23 +106,8 @@ defmodule Module.Types.Pattern do end # var - def of_pattern({name, meta, ctx} = var, {expected, expr}, stack, context) - when is_atom(name) and is_atom(ctx) do - case stack do - %{refine: true} -> - Of.refine_var(var, expected, expr, stack, context) - - %{refine: false} -> - version = Keyword.fetch!(meta, :version) - - case context do - %{vars: %{^version => %{type: type}}} -> - Of.intersect(type, {expected, expr}, stack, context) - - %{} -> - {:ok, expected, context} - end - end + def of_pattern(var, expected_expr, stack, context) when is_var(var) do + Of.refine_var(var, expected_expr, stack, context) end def of_pattern(expr, expected_expr, stack, context) do @@ -198,14 +178,10 @@ defmodule Module.Types.Pattern do end end - # ^var - # Happens from inside size(^...) and map keys - def of_guard({:^, _meta, [var]}, expected_expr, stack, context) do - Of.intersect(Of.var(var, context), expected_expr, stack, context) - end - # var def of_guard(var, expected_expr, stack, context) when is_var(var) do + # TODO: This should be ver refinement once we have inference in guards + # Of.refine_var(var, expected_expr, stack, context) Of.intersect(Of.var(var, context), expected_expr, stack, context) end @@ -213,6 +189,28 @@ defmodule Module.Types.Pattern do of_shared(expr, expected_expr, stack, context, &of_guard/4) end + ## Helpers + + defp of_struct_var({:_, _, _}, {expected, _expr}, _stack, context) do + {:ok, expected, context} + end + + defp of_struct_var({:^, _, [var]}, expected_expr, stack, context) do + Of.intersect(Of.var(var, context), expected_expr, stack, context) + end + + defp of_struct_var({_name, meta, _ctx}, expected_expr, stack, context) do + version = Keyword.fetch!(meta, :version) + + case context do + %{vars: %{^version => %{type: type}}} -> + Of.intersect(type, expected_expr, stack, context) + + %{} -> + {:ok, elem(expected_expr, 0), context} + end + end + ## Shared # :atom @@ -269,6 +267,12 @@ defmodule Module.Types.Pattern do of_shared({:{}, [], [left, right]}, expected_expr, stack, context, fun) end + # ^var + defp of_shared({:^, _meta, [var]}, expected_expr, stack, context, _fun) do + # This is by definition a variable defined outside of this pattern, so we don't track it. + Of.intersect(Of.var(var, context), expected_expr, stack, context) + end + # left | [] defp of_shared({:|, _meta, [left_expr, []]}, _expected_expr, stack, context, fun) do fun.(left_expr, {dynamic(), left_expr}, stack, context) @@ -303,6 +307,7 @@ defmodule Module.Types.Pattern do end # {...} + # TODO: Implement this defp of_shared({:{}, _meta, exprs}, _expected_expr, stack, context, fun) do case map_reduce_ok(exprs, context, &fun.(&1, {dynamic(), &1}, stack, &2)) do {:ok, types, context} -> {:ok, tuple(types), context} diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 2b2896d53d..51dc33701a 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -210,7 +210,7 @@ defmodule Module.Types.ExprTest do ~l""" incompatible types in expression: - size(x) + <> expected type: @@ -225,6 +225,12 @@ defmodule Module.Types.ExprTest do # type: binary() # from: types_test.ex:LINE-2 <> + + where "y" was given the type: + + # type: dynamic() + # from: types_test.ex:208 + y """} end end diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index d4228aaf7b..04dcb5d1db 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -142,7 +142,7 @@ defmodule Module.Types.PatternTest do ~l""" incompatible types in expression: - size(x) + <<..., _::integer-size(x)>> expected type: