Warning about missing pattern match, despite pattern matching

I’m getting the following warning when compiling my project and I’m not sure how to resolve it, because from what I see there is a proper pattern match

Warning
    warning: a struct for Plug.Upload is expected on struct update:

        %Plug.Upload{image | filename: random_filename(name)}

    but got type:

        dynamic()

    where "image" was given the type:

        # type: dynamic()
        # from: my_file.ex:95:79
        %{"image" => %Plug.Upload{filename: name} = image} = params

    when defining the variable "image", you must also pattern match on "%Plug.Upload{}".

    hint: given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of:

        user = some_function()
        %User{user | name: "John Doe"}

    it is enough to write:

        %User{} = user = some_function()
        %{user | name: "John Doe"}

    typing violation found at:
    │
 96 │     image = %Plug.Upload{image | filename: random_filename(name)}
    │             ~
    │
    └─ my_file.ex:96:13: MyModule.put_random_filename/1

Code
  defp put_random_filename(%{"image" => %Plug.Upload{filename: name} = image} = params) do
    image = %Plug.Upload{image | filename: random_filename(name)}
    %{params | "image" => image}
  end

  defp put_random_filename(params), do: params
Elixir -v

Erlang/OTP 28 [erts-16.1.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Elixir 1.19.2 (compiled with Erlang/OTP 28)

Different variations that trigger the same warning

No match against file name
  defp put_random_filename(%{"image" => %Plug.Upload{} = image} = params) do
    image = %Plug.Upload{image | filename: random_filename(image.filename)}
    %{params | "image" => image}
  end
Flipping the assignment
  defp put_random_filename(%{"image" => image = %Plug.Upload{filename: name}} = params) do
    image = %Plug.Upload{image | filename: random_filename(name)}
    %{params | "image" => image}
  end
2 Likes
image = %Plug.Upload{image | filename: random_filename(name)}

What about rewiting this to:

image = %{image | filename: random_filename(name)}
2 Likes

Thanks, that does fix the warning. I would rather call this a workaround than a solution though :thinking:

1 Like

Surely this is a bug? The struct is clearly typed. Maybe someone with more knowledge will reply, but I would say file an issue.

Does this infer properly?

def foo(%{"image" => image}) do
  %Plug.Upload{} = image
  %Plug.Upload{image | filename: "foo"}
end
2 Likes

I’ve tried these 2 variations. Both trigger the warning

  defp put_random_filename(%{"image" => image} = params) do
    %Plug.Upload{filename: name} = image
    image = %Plug.Upload{image | filename: random_filename(name)}
    %{params | "image" => image}
  end

  defp put_random_filename(%{"image" => image} = params) do
    %Plug.Upload{} = image
    image = %Plug.Upload{image | filename: random_filename(image.filename)}
    %{params | "image" => image}
  end
2 Likes

Maybe I’m missing something but that doesn’t seem right to me. There was a lot of back-and-forth on what to do with the “struct update” syntax and I’m not sure what the final decision was there, but I feel like those examples should work, right?

2 Likes

It’s not a bug. The compoler does accept it (except if you turn warnings to errors)

There is no benefited value to specify the struct in the update. Therefore the recommendation to drop it.

I think we just need to get used to the new style

Is there an official statement on that somewhere? I haven’t tried out 1.19 yet but this doesn’t match my understanding of either of the possibilities that were being considered. If it is the struct update syntax itself being deprecated shouldn’t the warning specifically say that rather than missing pattern match? Or what could be the point of warning about it if it’s not deprecated?

2 Likes

The release note for 1.9 states:

[Kernel] The struct update syntax, such as %URI{uri | path: "/foo/bar"}, now requires the given variable (or expression) to explicitly pattern match on the struct before it can be updated. This is because, thanks to the type system, pattern matching on structs can find more errors, more reliably, and we want to promote its usage. Once pattern matching is added, you may optionally convert the struct update syntax into the map update syntax %{uri | path: "/foo/bar"} with no less of typing guarantees

1 Like

explicitly pattern match on the struct before it can be updated.

then it seems like the code in Warning about missing pattern match, despite pattern matching - #5 by Awlexus should not generate a warning? Isn’t the first line of each function an “explicit pattern match”?

1 Like

The syntax was going to be deprecated but that decision changed. In this commit the changelog was updated to suggest that the syntax is allowed but you must pattern match on the struct first. Which is what all of the examples in this thread are doing.

Personally I stopped using the struct update syntax in anticipation of this change several months ago, but that doesn’t mean the compiler should be spitting out nonsensical warnings! I think this is just a bug.

This actually looks like a bug (or limitation) when inferring non-atom map keys.

I opened an issue Structs nested in domain-key maps are not inferred · Issue #14915 · elixir-lang/elixir · GitHub.

6 Likes