Compiler not inferring generic type with meaningless error

This code compiles successfully.

class Item(V)
  def initialize(@value : V, @name : String? = nil )
  end
end

class Queue(V) < Array(Item(V))
  def unshift(value : V, name = nil)
    item = Item(V).new(value, name)
    unshift(item)
  end
end

queue = Queue(Array(String)).new
queue.unshift ["S"]

queue = Queue(Tuple(String)).new
queue.unshift({"S"})

queue = Queue(Array(Array(String))).new
queue.unshift([["S"]])

queue = Queue(Array(Array(Array(String)))).new
queue.unshift([[["S"]]])

queue = Queue(Array(Tuple(String))).new
queue.unshift([{"S"}])

But when the type V is Tuple(Array(String)), it throws a meaningless error.

queue = Queue(Tuple(Array(String))).new
queue.unshift({["S"]})

gshah@workstation expt]$ crystal run src/expt.cr
Showing last frame. Use --error-trace for full trace.

In /usr/lib/crystal/array.cr:1933:21

1933 | @buffer.value = object
^-----
Error: type must be Item(Tuple(Array(String))), not Item(Tuple(Array(String)))

This error can be mitigated if we explicitly specify the type. With this, the code compiles successfully.

queue.unshift({["S"]}.as(Tuple(Array(String))))

Why is explicit type specification needed for Tuple(Array(String))) but not for other complex types?

Yes, the error message is indeed weird.

But the reason this fails in the first place seems to be some interference with the Array class. Inheriting that is generally not recommended. It’s better to use composition over inheritance.

1 Like

Reduced:

class Item(V)
end

class Foo(T)
end

class Bar(V) < Foo(Item(V))
  @foo = uninitialized Item(V)

  def foo(value : V, name = nil)
    @foo = Item(V).new # Error: instance variable '@foo' of Bar(Tuple(Foo(String))) must be Item(Tuple(Foo(String))), not Item(Tuple(Foo(String)))
  end
end

x = Bar(Tuple(Foo(String))).new
x.foo({Foo(String).new})

It only happens if the : V restriction and the name parameter are both present and the name argument is not provided. This is probably another variant of #10133, the only difference being the timing when Item(Tuple(Foo(String)+)) is instantiated.

1 Like