The variable "user" is unsafe as it has been set inside a case/cond/receive/if/&&/||

The code is from Phoenix Guardian example. I think it’s cleaner than using multiple case logics but the compiler give you this annoying warning. Is the compiler right and is there a better way of writing it?

  defp create_user_from_auth(auth, current_user, repo) do
    user = current_user
    if !user, do: user = repo.get_by(User, email: auth.info.email)
    if !user, do: user = create_user(auth, repo)
    authorization_from_auth(user, auth, repo)
    {:ok, user}
  end

warning: the variable "user" is unsafe as it has been set inside a case/cond/receive/if/&&/||. Please explicitly return the variable value instead. For example:

    case int do
      1 -> atom = :one
      2 -> atom = :two
    end

should be written as

    atom =
      case int do
        1 -> :one
        2 -> :two
      end

You can (hopefully, because I did not test it) do something like this

  defp create_user_from_auth(auth, current_user, repo) do
    user =
      if !current_user, do: repo.get_by(User, email: auth.info.email), else: nil
    user = 
      if !user, do: create_user(auth, repo), else: user
    case user do
      nil ->
        {:error, "reason for an error, ie: `user does not exist`"}
      _ ->
        authorization_from_auth(user, auth, repo)
        {:ok, user}
     end
  end

I might be totally wrong here though :wink:

Anyway the compiler is warning you, because, setting rest of code aside look at this line:
if !x, do: y = z
It’s similar to second line in your function. y is set only if x == false, it won’t be set if x == true and if you will try to use y later in code, when it is not yet set… you got a problem :wink:

So similarly
if !user, do: user = repo.get_by(User, email: auth.info.email)
user is set only when !user which means user == false and what about when user == true ? Then user won’t be set at all, and this may give you a problem :scream: Compiler is not smart enough to understand this code like a human does. So although from my point of view your code seems to be legit an will work, compiler just warns you, to make you aware of potential problems.

1 Like

Why not to use standard “or” construction?

user = current_user || repo.get_by(User, email: auth.info.email) || create_user(auth, repo)
8 Likes

@grych That fixes it in one go :smiley:
I still don’t think the compiler should be outputting a warning though. There is always a value in user from the first statement.

@omin: check the “Deprecation of imperative assignment” topic in http://elixir-lang.org/blog/2016/06/21/elixir-v1-3-0-released. This warning appears only in Elixir 1.3. And in my opinion, it is great! We should avoid such constructions in functional languages, even if they allow it.

2 Likes

Everything is an expression; everything will return some value.

As others has said, the compiler will warn you if you construct something like one would write in an imperative language. All of a sudden branching out and defining a variable for later use, outside the block, is hard to read and confusing.

2 Likes

:+1: Thanks guys

1 Like