Skip to content

defguard compilation expands guard expression in :guard context even if caller context is nil #15284

@lukaszsamson

Description

@lukaszsamson

Elixir and Erlang/OTP versions

Erlang/OTP 28 [erts-16.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Elixir 1.20.0-rc.4 (116f8f4) (compiled with Erlang/OTP 28)

Operating system

any

Current behavior

Kernel.Utils.defguard always expands the guard expression with context: :guard even when the __CALLER__ context is not :guard

def defguard(args, expr, env) do
{^args, vars} = extract_refs_from_args(args)
env = :elixir_env.with_vars(%{env | context: :guard}, vars)
{expr, _, _} = :elixir_expand.expand(expr, :elixir_env.env_to_ex(env), env)
quote do
case Macro.Env.in_guard?(__CALLER__) do
true ->
unquote(literal_quote(unquote_every_ref(expr, vars), []))
false ->
unquote(literal_quote(unquote_refs_once(expr, vars, env.module), generated: true))
end
end
end

This means that context aware macros like Kernel.and and Kernel.or do not expand to build_boolean_check when guard is used as a function. They always expand to :erlang.andalso and :erlang.orelse and miss optimize_boolean path.

The bug is affecting runtime behavior

defmodule G do
    defguard g(x) when x and true
end
import G
g(nil)
** (ArgumentError) argument error: nil
    iex:3: (file)

ArgumentError comes from :badarg throw by :erlang.andalso

:erlang.andalso(nil, true)
** (ArgumentError) argument error: nil
    iex:3: (file)

Compare that to usual elixir BadBooleanError

nil and true
** (BadBooleanError) expected a boolean on left-side of "and", got:

    nil

    iex:3: (file)

Expected behavior

I think the guard expression should be expanded twice, once in :guard, once in nil context and the approperiate version used depending on Macro.Env.in_guard?(__CALLER__). It should be safe, state and env is discarded on expansion anyway. Compiler tracers firing twice may be an issue though.
Alternative is documenting the current behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions