Warning from dialyzer for functions returning struct with a single pattern matched value in argument

Hi Everybody,

Thank you for this amazing community and of course for Elixir.

When learning Elixir I discovered strange warning from dialyzer that doesn’t make sense for the following function:

defmodule TestThis.SomeStruct do
  defstruct present?: nil,
            first: nil,
            second: nil,
            third: nil,
            fifth: nil,
            sixth: nil

  @type t :: %__MODULE__{
          present?: boolean,
          first: String.t(),
          second: atom,
          third: String.t(),
          fifth: String.t(),
          sixth: String.t()
        }

  # THIS will show warning that doesn't make sense:

  @spec for_test(false) :: t()
  def for_test(false) do
    %__MODULE__{
      present?: false,
      first: "",
      second: :just_for_test
    }
  end

  # THIS is OK for some reason:

  @spec for_test(true, String.t(), atom, String.t(), String.t(), String.t()) :: t()
  def for_test(true, first, second, third, fifth, sixth) do
    %__MODULE__{
      present?: true,
      first: first,
      second: second,
      third: third,
      fifth: fifth,
      sixth: sixth
    }
  end
end

I’m thinking about opening a bug report but first I wanted to check with you since I might be doing something wrong.

You can get full project generated by mix from GitHub: https://github.com/rudolfvesely/elixir-lang-static_analysis_warning

Thank you.

Kind regards,

Rudolf Vesely

When I run Dialyzer on that project, this is the error I get:

lib/test_this/some_struct.ex:18:invalid_contract
The @spec for the function does not match the success typing of the function.

Function:
TestThis.SomeStruct.for_test/1

Success typing:
@spec for_test(false) :: %TestThis.SomeStruct{
  :fifth => nil,
  :first => <<>>,
  :present? => false,
  :second => :just_for_test,
  :sixth => nil,
  :third => nil
}

________________________________________________________________________________
done (warnings were emitted)
Halting VM with exit status 2

I don’t see a bug, Dialyzer is correct - the for_test(false) head produces a struct that doesn’t match the definition of t since the unspecified fields default to nil.

Correcting the defaults to:

  defstruct present?: nil,
            first: "",
            second: nil,
            third: "",
            fifth: "",
            sixth: ""

OR correcting the type to:

  @type t :: %__MODULE__{
          present?: boolean,
          first: String.t() | nil,
          second: atom,
          third: String.t() | nil,
          fifth: String.t() | nil,
          sixth: String.t() | nil
        }

will make Dialyzer pass.

3 Likes

Thank you @al2o3cr, makes sense. I focused on the function and warning message instead adding String.t() | nil into struct typespecs as you correctly pointed out.

Thank you.

1 Like