We have a function for validating a certain struct, but our doctest seems to generate a warning. The warning itself is obvious, but the test is also to show it's wrong.
defmodule Foo do
defstruct [:bar]
@doc """
Returns if the argument is a valid Foo struct or not.
iex> Foo.is_valid?(%{})
false
iex> Foo.is_valid?(%Foo{})
false
iex> Foo.is_valid?(%Foo{bar: "baz"})
true
"""
@spec is_valid?(any()) :: boolean()
defguard is_valid?(term)
when is_struct(term, __MODULE__) and is_map_key(term, :bar) and not is_nil(term.bar)
end
I think that the code and tests are valid, but the doctest generates this warning:
warning: undefined field "bar" in expression:
# (for doctest at) lib/foo.ex:7
arg0.bar
expected one of the following fields:
where "arg0" was given the type map() in:
# (for doctest at) lib/foo.ex:7
{arg0} = {%{}}
Conflict found at
(for doctest at) lib/foo.ex:7: FooTest."doctest Foo.is_valid?/1 (1)"/1
It will show the error for any type other than %Foo{}.
No warnings when running the doctest.
This is a common warning in guards because guards are expanded and the compiler is smart enough to "see inside". For example, we can't really test is_map(true) because the compiler will warn since true is not a map.
One possible fix for now is for you to replace %{} by Map.new() although in the future the compiler may even be smart enough to see across Map.new. :)
Yeah I was suspecting something like that, but the test still looks valid to me. Is it maybe an option to not emit these warnings for (doc)tests?
For now I don't want to allow to skip warnings because that's a tricky slippery slope. First we start skip warnings for tests then folks will start skip warnings in actual valid code.
The good news though is that: every time you get a warning because of your example, the user will also get the same warning, which means they will know it will fail before they even invoke it.
I think the warnings is incorrect. The type warning should only be emitted for expressions that will fail at runtime - this is not the case, the expression discussed here will return false because of the earlier checks that the field exists before accessing it.
In other words, is_map_key(x, :foo) and x.foo should never produce a warning, regardless if x is inferred to have a :foo key or not - the expression will work correctly in both cases.
Ah, yes, good point about the conditionals.
Most helpful comment
I think the warnings is incorrect. The type warning should only be emitted for expressions that will fail at runtime - this is not the case, the expression discussed here will return
falsebecause of the earlier checks that the field exists before accessing it.In other words,
is_map_key(x, :foo) and x.fooshould never produce a warning, regardless ifxis inferred to have a:fookey or not - the expression will work correctly in both cases.