Search code examples
crystal-lang

Crystal compiler does not detect that object is not nil


I have the following class :

class X
    property son, val
    def initialize(@val : Int32)
        @son = nil.as X?
    end

    def add(other : X?)
        unless other.nil?
            if @son.nil?
                @son = other
            else
                @son.add(other)
            end
        end
    end
end

x = X.new 5
x.add(nil)
x.add(X.new 3)

But when I try to build I get

Showing last frame. Use --error-trace for full trace.

In nil-test.cr:12:22

 12 | @son.add(other)
           ^------
Error: undefined method 'include' for Nil (compile-time type is (X | Nil))

According to the manual, this is exactly the kind of situation where the compiler should recognize that @son cannot be nil in the else branch, yet it apparently fails to do so.

What am I doing wrong ?

Note : using @son.not_nil!.add(other) works, I'm just asking why the compiler can't do without.


Solution

  • That only works for local variables, not instance variables - since the instance variables may be mutated by another fiber between the condition and you accessing the variable. See this section (under "Limitations") in the Crystal docs.

    You can do it like this, assigning the instance variable to a local variable that will not change out from under you:

    def add(other : X?)
      unless other.nil?
        if s = @son
          s.add(other)
        else
          @son = other
        end
      end
    end