-1

Having updated my project to use Elixir 1.12 I've noticed Dialyzer complaining about certain places where I've accessed the property of a struct using dot notation. For example here's a graphql resolver function in my app:

def update(_root, %{input: input}, %{context: %{current_user: user}}) do
  case user
        |> User.update_changeset(input)
        |> Repo.update() do
    {:ok, updated_user} ->
      if user.email !== updated_user.email do
        Email.email_changed(user.email, updated_user.email)
        |> Mailer.deliver_later()
      end

      {:ok, updated_user}

    err ->
      err
  end
end

Dialyser has highlighted the expression user.email !== updated_user.email with the following error:

The call _.'email'/() requires that _@1 is of type atom(), not {map(), map()}

Any idea what this error means and how to fix it?

(this all compiles and runs fine, I'm just keen to learn why it doesn't seem to satisfy Dialyzer)

harryg
  • 23,311
  • 45
  • 125
  • 198

1 Answers1

1

While it’s hard to tell whether is wrong, or your code has glitches inducing this issue, without seeing how context is built before passing to this function, there is a bit of general advice.

Use deep pattern matching everywhere to narrow the issue.

def update(_root, %{input: input},
  %{context: %{current_user: %User{email: email}}}) do
    user
    |> User.update_changeset(input)
    |> Repo.update() 
    |> case do
      {:ok, %User{email: ^email} = user} ->
        {:ok, user}

      {:ok, %User{email: updated_email} = user} ->
        email
        |> Email.email_changed(updated_email)
        |> Mailer.deliver_later()

        {:ok, user}

      err ->
        err
    end
end

That way the code is cleaner, the intent is clearer, and the error (if happens) is more evident and glued to where it actually belongs.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • That's a fair comment, but I need access to use the whole `user` anyway, hence why I haven't destructured the whole thing (even though one could do something like `%{context: %{current_user: user = %User{email: email}}}` but this gets pretty verbose). FWIW, this does make the Dialyzer error go away. I'm more interested in what the error is about though. – harryg Jun 25 '21 at 09:22
  • Then at least show the spec of `update/3`, please. Turns out, [tag:dialyzer] fails to get the type of context correctly. – Aleksei Matiushkin Jun 25 '21 at 09:37
  • 1
    Fair enough, yes I think it's a problem of Dialyzer not having enough info to know the type of `user` so it guesses. Adding some hints in the pattern matches solves it. – harryg Jun 25 '21 at 09:40
  • 1
    Please also note how I got rid of `if/2` with a direct pattern matching, which makes the code more ahem idiomatic :) – Aleksei Matiushkin Jun 25 '21 at 09:42