Compilation warning about usage of struct in guards

Hello,

This is the code in question:

defguardp is_noretry(err)
  when (is_atom(err) and err in @noretry_errors) or
       (is_map(err) and is_map_key(err, :__struct__) and
          :erlang.map_get(:__struct__, err) in @noretry_errors)

defguard is_retryable(err) when not is_noretry(err)

@noretry_errors is a list of modules.

I call it like this some_atom when is_retryable(some_atom) -> ...

This is the dialyzer error This is the compilation warning, dialyzer is fine :

warning: expected Kernel.is_map_key/2 to have signature:

    :__struct__, atom() -> dynamic()

but it has signature:

    dynamic(), %{optional(dynamic()) => dynamic()} -> dynamic()

in expression:

    # .../backend_base.ex:47
    is_map_key(some_atom, :__struct__)

It looks like it complains about is_map_key being called on an atom, although it does not happen because of the is_map(err) call in the guards.

Am I missing something?

Thank you.

What if it’s an atom that is not in @noretry_errors?

EDIT: misread, sorry

Then neither the first clause (is_atom(err) and err in @noretry_errors) nor the second (is_map(err) ...) matches and the guard is false.

(np :slight_smile: )

Here is some reproductible example

defmodule SomeGood do
  defstruct dummy: nil
end

defmodule SomeBad do
  defstruct dummy: nil
end

defmodule Demo do
  @good_modules [Somegood]

  defguard is_good(mod_or_struct)
           when (is_atom(mod_or_struct) and mod_or_struct in @good_modules) or
                  (is_map(mod_or_struct) and is_map_key(mod_or_struct, :__struct__) and
                     :erlang.map_get(:__struct__, mod_or_struct) in @good_modules)
end

defmodule Worker do
  import Demo

  def work(%contract{} = data) when is_good(contract) do # <------------- WARN
    :alright
  end

  def work(_) do
    :nope
  end

  def a_case(data) do
    case data do
      %contract{} when is_good(contract) -> :good_contract # <----------- WARN
      data when is_good(data) -> :good_data
      _other -> :bad_data
    end
  end
end

defmodule Client do
  def run do
    good_data = %SomeGood{}
    bad_data = %SomeBad{}
    {Worker.work(good_data), Worker.work(bad_data)}
  end
end

This sounds like a bug. Yes those types are incompatible, but it’s only one branch of two.

warning: incompatible types:

    atom() !~ map()

in expression:

    # lib/test.ex:34
    is_map(contract)

where "contract" was given the type atom() in:

    # lib/test.ex:34
    %contract{}

where "contract" was given the type map() in:

    # lib/test.ex:34
    is_map(contract)

Conflict found at
  lib/test.ex:34: Worker.a_case/1
1 Like

This is what I think too. I will create an issue in the elixir repository.

Edit: Possible bug in compiler emitted warnings about incompatibles types in guard · Issue #11744 · elixir-lang/elixir · GitHub