Se uma variável for a condição de um if, dentro do bloco then a variável será considerada como não tendo o tipo Nil:
a = alguma_condicao ? nil : 3
# a é Int32 ou Nil
if a
  # Já que o único jeito de cair aqui é se "a" for verdadeiro,
  # "a" não pode ser nil. Então aqui "a" é Int32.
  a.abs
end
Isso também se aplica quando uma variável é atribuída em uma condição if:
if a = alguma_expressao
  # aqui "a" não é nil
end
Essa lógica também se aplica se houver ands (&&) na condição:
if a && b
  # garante-se que aqui tanto "a" quanto "b" não são Nil
end
Aqui, no lado direito da expressão &&, também é garantido que a não tem o valor Nil.
É claro, se o for atribuído novamente o valor da variável dentro do bloco then, essa variável terá um novo tipo baseado na expressão atribuída.
A lógica acima não funciona com variáveis de instância, variáveis de classe ou variáveis globais:
if @a
  # aqui @a pode ser nil
end
Isso acontece porque qualquer chamada de método pode potencialmente afetar essa variável de instância, tornando ela nil. Outro motivo é que outra thread poderia mudar essa variável de instância após checá-la na condição.
Para fazer algo com @a somente se ele não for nil, você tem duas opções:
# Primeira opção: atribua ele a uma variável
if a = @a
  # aqui "a" não pode ser nil
end
# Segunda opção: use `Object#try`, encontrado na biblioteca padrão
@a.try do |a|
  # aqui "a" não pode ser nil
end
Essa lógica também não funciona com procs e chamadas de métodos, inclusive getters e propriedades, porque não se pode garantir que procs e métodos que podem ser nil (ou, mais genericamente, procs e métodos do tipo união) retornem o mesmo tipo específico em duas chamadas sucessivas.
if method # primeira chamada ao método que pode retornar Int32 ou Nil
          # aqui sabemos que a primeira chamada não retornou Nil
  method  # a segunda chamada também ainda pode retornar Int32 ou Nil
end
A técnica descrita acima para variáveis de instância também funcionará com procs e chamadas de métodos.