Abstract each method error

The following code

abstract class Test
  abstract def each
end

class MyTest < Test
  def initialize(@ary : Array(Int32))
  end
  def each
    @ary.each do |e|
      yield e
    end
  end
end

displays the error:

5 | class MyTest < Test
^
Error: abstract def Test#each() must be implemented by MyTest

Hmm, :thinking: what is the problem with this code ?

Try the following in Test abstract class:

  abstract def each(& : T ->)

Since the implementation MyTest#each has a yield, it changes the inferred “type signature” of the method, which then no longer matches the vanilla abstract def each which does not declare that the implementation will yield (and thus require a block when called.)

(Btw, I was surprised that I could use T in that abstract class that is not explicitly “generic”; so there is some nuance to the type inference that I don’t yet understand. :star_struck:)

1 Like

The important part is designating the abstract method to be yielding. The given signature corresponds to a non-yielding method, but you only implementation does yield.

The correct signature for that would be abstract def each(& : Int32 ->).
The version with T works, but I think that’s more of an accident. The abstract method is never instantiated, so the compiler doesn’t need to resolve the type. If it would, it would discover that it doesn’t exist. But it’s enough to inform the abstract method def checker :person_shrugging:

1 Like

Is there a way, besides making Test explicitly generic, to declare the abstract method’s yield/block without a concrete type (i.e. Int32) so that a future class can yield anything?

  ## this does not compile
  abstract def each(& ->)

I’m more curious than anything else as I’ve been able to declare a method arg without a type and have it aggregate all future calls’ arg types (which is pretty nifty).

I think it would just be abstract def each(&).

That was the first thing I tried and it did not work. I’ve simplified the example below and identified what I tried.

abstract class Test
  abstract def each(& : Int32 ->) # this works
  #abstract def each(&)              # this does not
  #abstract def each(& ->)         # this does not
  #abstract def each                  # this does not
end

class MyTest < Test
  def each
    yield 10
  end
end

Interestingly, as my first attempt using T showed, that type could be anything, as the specific type isn’t as important as the structure of the declaration which sets up the abstract method definition for later type checking whe implementing the subclass.

(One of the fascinating things about Crystal is how the compiler defers some syntax/semantic checking until when a method needs to have its code generated. This is tree-shaking at its best.)

Okay, I think i see what’s going on. each(&) is technically not matched with your implementation because it is yielding a value where the abstract def is just looking for a method that yields, but with no args. In the case of wanting to restrict the method to yield something, you could do abstract def each(& : _ ->) which basically says it yields one argument of any type, and returns nil. E.g. yield 10 or yield "foo" would satisfy it, but not yield 10, "foo" (two arguments).

5 Likes