I have the following code:
defmodule Foo do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{
id: integer(),
foo: String.t(),
baz_id: String.t(),
bar: String.t() | nil
}
embedded_schema do
field :foo, :string
field :bar, :string
end
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def changeset(bae \\ %__MODULE__{}, attrs) do
bae
|> cast(attrs, @fields)
|> unique_constraint(:baz_id)
end
end
foo
and baz_id
should not be nil
, as per the @type
definition.
However, dialyzer
is complaining (with the given @spec
) because the default value %__MODULE__{}
will set them to nil
.
If I replace the @type
definition with:
...
@type t :: %__MODULE__{
id: integer() | nil,
foo: String.t() | nil,
baz_id: String.t() | nil,
bar: String.t() | nil
}
...
then dialyzer
will not complain, but I am no longer capturing the idea that some of the fields are not nullable.
What would be an elegant way to make changeset()
work the way it currently is, and avoid having dialyzer
complaining for this specific use?
Well, you’re explicitly specifying the default argument that violates a dialyzer contract (the schema is a bare struct underneath without default values,) that’s why dialyzer complains.
It is unclear, how you do suppose to handle an empty
%__MODULE__{}
once it’s not allowed, but answering the question stated, the workaround would be to acceptnil
argument as a default.