if var

If a variable is the condition of an if, inside the then branch the variable will be considered as not having the Nil type:

  1. a = some_condition ? nil : 3
  2. # a is Int32 or Nil
  3. if a
  4. # Since the only way to get here is if a is truthy,
  5. # a can't be nil. So here a is Int32.
  6. a.abs
  7. end

This also applies when a variable is assigned in an if‘s condition:

  1. if a = some_expression
  2. # here a is not nil
  3. end

This logic also applies if there are ands (&&) in the condition:

  1. if a && b
  2. # here both a and b are guaranteed not to be Nil
  3. end

Here, the right-hand side of the && expression is also guaranteed to have a as not Nil.

Of course, reassigning a variable inside the then branch makes that variable have a new type based on the expression assigned.

Limitations

The above logic works only for local variables. It doesn’t work with instance variables, class variables, or variables bound in a closure. The value of these kinds of variables could potentially be affected by another fiber after the condition was checked, rendering it nil. It also does not work with constants.

  1. if @a
  2. # here `@a` can be nil
  3. end
  4. if @@a
  5. # here `@@a` can be nil
  6. end
  7. a = nil
  8. closure = ->{ a = "foo" }
  9. if a
  10. # here `a` can be nil
  11. end

This can be circumvented by assigning the value to a new local variable:

  1. if a = @a
  2. # here `a` can't be nil
  3. end

Another option is to use Object#try found in the standard library which only executes the block if the value is not nil:

  1. @a.try do |a|
  2. # here `a` can't be nil
  3. end

Method calls

That logic also doesn’t work with proc and method calls, including getters and properties, because nilable (or, more generally, union-typed) procs and methods aren’t guaranteed to return the same more-specific type on two successive calls.

  1. if method # first call to a method that can return Int32 or Nil
  2. # here we know that the first call did not return Nil
  3. method # second call can still return Int32 or Nil
  4. end

The techniques described above for instance variables will also work for proc and method calls.