Outsmarted by the compiler

Given this POC:

class Test
  getter test : String -> String?

  def initialize(&handler : String -> _)
    @test = ->(input : String) do
      res = handler.call("banana")
      res.is_a?(String) ? res : nil
    end
  end
end

then p Test.new { "123" }.test.call("test") works as expected, but p Test.new { 123 }.test.call("test") throws Error: instance variable '@test' of Test must be Proc(String, (String | Nil)), not Proc(String, Nil)

Apparently the compiler is smart enough to figure out in the latter case that the proc can never return anything but nil. But I would assume it’s an acceptable sub-set.

What am I failing to grasp here?

I’ts not. Proc(String, Nil) is not compatible with Proc(String, String?) for the same reason Array(Nil) is not compatible with Array(String?). EDIT: Wow, okay yea that’s not actually true. I think it’s just because procs are more strongly typed versus other types?

The easiest fix for this is to stop letting the @test proc’s return type be inferred and instead state it explicitly. E.g. @test = Proc(String, String?).new do.

Related issue: Instance variable '@callback' of Foo must be (Proc((T | Nil)) | Nil), not Proc(Nil) · Issue #3131 · crystal-lang/crystal · GitHub

1 Like

Ah thanks, that helped.

Can use @test = ->(input : String) : String? do instead.

1 Like