Skip to content

Ignore defguards in typecheck #12194

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions lib/elixir/lib/kernel/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -348,16 +348,19 @@ defmodule Kernel.Utils do

# Finds every reference to `refs` in `guard` and wraps them in an unquote.
defp unquote_every_ref(guard, refs) do
Macro.postwalk(guard, fn
{ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
case {ref, var_context(meta, context)} in refs do
true -> literal_unquote(var)
false -> var
end
Macro.update_meta(
Macro.postwalk(guard, fn
{ref, meta, context} = var when is_atom(ref) and is_atom(context) ->
case {ref, var_context(meta, context)} in refs do
true -> literal_unquote(var)
false -> var
end

node ->
node
end)
node ->
node
end),
&([generated: true] ++ &1)
)
end

# Prefaces `guard` with unquoted versions of `refs`.
Expand Down
74 changes: 48 additions & 26 deletions lib/elixir/lib/module/types/pattern.ex
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,29 @@ defmodule Module.Types.Pattern do
Refines the type variables in the typing context using type check guards
such as `is_integer/1`.
"""
def of_guard(expr, expected, %{context: stack_context} = stack, context)
when stack_context != :pattern do
def of_guard({_, meta, _} = expr, expected, stack, context) do
if meta[:generated] do
{:ok, :dynamic, context}
else
do_of_guard(expr, expected, stack, context)
end
end

def of_guard(expr, expected, stack, context) do
do_of_guard(expr, expected, stack, context)
end

defp do_of_guard(expr, expected, %{context: stack_context} = stack, context)
when stack_context != :pattern do
of_guard(expr, expected, %{stack | context: :pattern}, context)
end

def of_guard({{:., _, [:erlang, :andalso]}, _, [left, right]} = expr, _expected, stack, context) do
defp do_of_guard(
{{:., _, [:erlang, :andalso]}, _, [left, right]} = expr,
_expected,
stack,
context
) do
stack = push_expr_stack(expr, stack)

with {:ok, left_type, context} <- of_guard(left, @boolean, stack, context),
Expand All @@ -197,7 +214,12 @@ defmodule Module.Types.Pattern do
do: {:ok, to_union([@boolean, right_type], context), context}
end

def of_guard({{:., _, [:erlang, :orelse]}, _, [left, right]} = expr, _expected, stack, context) do
defp do_of_guard(
{{:., _, [:erlang, :orelse]}, _, [left, right]} = expr,
_expected,
stack,
context
) do
stack = push_expr_stack(expr, stack)
left_indexes = collect_var_indexes_from_expr(left, context)
right_indexes = collect_var_indexes_from_expr(right, context)
Expand All @@ -220,41 +242,41 @@ defmodule Module.Types.Pattern do
# The unary operators + and - are special cased to avoid common warnings until
# we add support for intersection types for the guard functions
# -integer / +integer
def of_guard({{:., _, [:erlang, guard]}, _, [integer]}, _expected, _stack, context)
when guard in [:+, :-] and is_integer(integer) do
defp do_of_guard({{:., _, [:erlang, guard]}, _, [integer]}, _expected, _stack, context)
when guard in [:+, :-] and is_integer(integer) do
{:ok, :integer, context}
end

# -float / +float
def of_guard({{:., _, [:erlang, guard]}, _, [float]}, _expected, _stack, context)
when guard in [:+, :-] and is_float(float) do
defp do_of_guard({{:., _, [:erlang, guard]}, _, [float]}, _expected, _stack, context)
when guard in [:+, :-] and is_float(float) do
{:ok, :float, context}
end

# tuple_size(arg) == integer
def of_guard(
{{:., _, [:erlang, :==]}, _, [{{:., _, [:erlang, :tuple_size]}, _, [var]}, size]} = expr,
expected,
stack,
context
)
when is_var(var) and is_integer(size) do
defp do_of_guard(
{{:., _, [:erlang, :==]}, _, [{{:., _, [:erlang, :tuple_size]}, _, [var]}, size]} = expr,
expected,
stack,
context
)
when is_var(var) and is_integer(size) do
of_tuple_size(var, size, expr, expected, stack, context)
end

# integer == tuple_size(arg)
def of_guard(
{{:., _, [:erlang, :==]}, _, [size, {{:., _, [:erlang, :tuple_size]}, _, [var]}]} = expr,
expected,
stack,
context
)
when is_var(var) and is_integer(size) do
defp do_of_guard(
{{:., _, [:erlang, :==]}, _, [size, {{:., _, [:erlang, :tuple_size]}, _, [var]}]} = expr,
expected,
stack,
context
)
when is_var(var) and is_integer(size) do
of_tuple_size(var, size, expr, expected, stack, context)
end

# fun(args)
def of_guard({{:., _, [:erlang, guard]}, _, args} = expr, expected, stack, context) do
defp do_of_guard({{:., _, [:erlang, guard]}, _, args} = expr, expected, stack, context) do
type_guard? = type_guard?(guard)
{consider_type_guards?, keep_guarded?} = stack.type_guards
signature = guard_signature(guard, length(args))
Expand Down Expand Up @@ -296,16 +318,16 @@ defmodule Module.Types.Pattern do
end

# map.field
def of_guard({{:., meta1, [map, field]}, meta2, []}, expected, stack, context) do
defp do_of_guard({{:., meta1, [map, field]}, meta2, []}, expected, stack, context) do
of_guard({{:., meta1, [:erlang, :map_get]}, meta2, [field, map]}, expected, stack, context)
end

# var
def of_guard(var, _expected, _stack, context) when is_var(var) do
defp do_of_guard(var, _expected, _stack, context) when is_var(var) do
{:ok, get_var!(var, context), context}
end

def of_guard(expr, _expected, stack, context) do
defp do_of_guard(expr, _expected, stack, context) do
of_shared(expr, stack, context, &of_guard(&1, :dynamic, &2, &3))
end

Expand Down
15 changes: 15 additions & 0 deletions lib/elixir/test/elixir/module/types/types_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ defmodule Module.Types.TypesTest do
assert string == :none
end

defguard tuple_or_nil(term) when is_tuple(term) or is_nil(term)

test "does not warn on guards defined with defguard" do
string =
warning(
[var],
[is_atom(var)],
case var do
_ when tuple_or_nil(var) -> :ok
end
)

assert string == :none
end

test "check body" do
string = warning([x], [is_integer(x)], :foo = x)

Expand Down