Search code examples
compile-timeweak-typingcrystal-lang

"undefined method 'zero' for Nil:Class" when #sum the Array without Nils


The issue happens when the variable, that the array was built from, was a nil initially.

y = (1..2).map do
  v = nil
  v = 1
  v
end
p y       # => [1, 1]
p y.class # => Array(Int32)
p y.sum   # => 2

When v stops being nil on a condition, that is potentially computational and not solvable while compiling:

z = (1..2).map do
  v = nil
  v = 1 if true
  v
end
p z       # [1, 1]
p z.class # => Array(Nil | Int32)

The array gets more complex type, that isn't compatible with current sum implementation, so p z.sum causes compile time error:

undefined method 'zero' for Nil:Class (compile-time type is (Nil | Int32):Class)
 def sum(initial = T.zero)
                     ^~~~

How am I supposed to fight this properly?
Or maybe it waits for some better implementation of stdlib sum method or anything else?

UPD: inject gives the same:

p z.inject{ |i, j| i + j }

undefined method '+' for Nil (compile-time type is (Nil | Int32))

Solution

  • You can use Iterator#compact_map to select non-nil values. The compiler will be able to infer a Array(Int32) in that case.

    http://play.crystal-lang.org/#/r/e85

    z = (1..2).map do
      v = nil
      v = 1 if true
      v
    end
    
    pp typeof(z) # => Array(Nil | Int32)
    pp z # => z = [1, 1]
    
    y = z.compact_map(&.itself)
    pp typeof(y) # => Array(Int32)
    pp y # => y = [1, 1]
    

    Also, notice that typeof(Expr) and Expr.class might lead to different results. The first is the compile time type and the later is the runtime type.