The pattern can never match the type

Hi folks, I got this warning…but everything just work…maybe It will give bug to my app next time.

def create_group(attrs \\ %{}) do
    %Group{}
    |> Group.create_changeset(attrs)
    |> maybe_put_products(attrs)
    |> Repo.insert()
  end
...
defp maybe_put_products(changeset, []), do: changeset

  defp maybe_put_products(changeset, attrs) do
    products = Products.get_products(attrs["products"])

    Ecto.Changeset.put_assoc(changeset, :products, products)
  end

maybe_put_products(changeset, []) have the warning:

The pattern can never match the type.

Pattern:
_changeset, []

Type:

%Ecto.Changeset{
:action => atom(),
:changes => %{atom() => _},
:constraints => [map()],
:data => nil | map(),
:empty_values => ,
:errors => [{
, _}],
:filters => %{atom() => },
:params => nil | %{binary() => },
:prepare => [(
-> any())],
:repo => atom(),
:repo_opts => [{
, }],
:required => [atom()],
:types => nil | %{atom() => atom() | {
, } | {, _, }},
:valid? => boolean(),
:validations => [{
, _}]
},
:invalid | %{:struct => none(), (atom() | binary()) => _}

ElixirLS Dialyzer

ElixirLS Dialyzer says that default value in create_group/1 (i.e. empty map %{}) does not match pattern match in maybe_put_products/2 (i.e. empty list []), so if nothing else is calling maybe_put-products/2 then such clause will never be called.

1 Like

how do I catch empty “products” like:

defp maybe_put_products(changeset, []), do: changeset

i tried change the list to %{} but got other warning.

this is my Group Schema:

...
  schema "groups" do
    field :name, :string
    field :description, :string


    many_to_many :users, MyApp.Accounts.User, join_through: "groups_users"
    many_to_many :products, MyApp.Products.Product, join_through: "groups_products", on_replace: :delete
    timestamps()
  end

  def create_changeset(group, attrs) do
    group
    |> cast(attrs, [:name, :description])
    |> validate_required([:name])
  end

  @doc false
  def changeset(group, attrs) do
    group
    |> Repo.preload(:products)
    |> cast(attrs, [:name, :description])
    |> validate_required([:name])
  end

Here you are trying to catch attrs - not products.

  def create_group(attrs \\ %{}) do
    %Group{}
    |> Group.create_changeset(attrs)
    # returns nil if key "products" does not exists in map
    |> maybe_put_products(attrs["products"])
    |> Repo.insert()
  end

  # matches when products are not set or when empty list
  defp maybe_put_products(changeset, products) when products in [nil, []], do: changeset

  # otherwise
  defp maybe_put_products(changeset, products) do
    products = Products.get_products(products)
    Ecto.Changeset.put_assoc(changeset, :products, products)
  end
2 Likes

Thanks for explaining.

I would rather go with an explicit pattern match on products:

  # matches when products are the expected non-empty list
  defp maybe_put_products(changeset, %{"products" => products}) 
      when length(products) > 0 do
    products = Products.get_products(products)
    Ecto.Changeset.put_assoc(changeset, :products, products)
  end

  # otherwise
  defp maybe_put_products(changeset, _), do: changeset

and call it as |> maybe_put_products(attrs) from change_group/1. This is surely a matter of preference, but the latter reads cleaner IMHO.

3 Likes