Macros: How to select ALL methods with annotation?

Hello!

For the VM in AoC I’m heading toward something like this:

class IntCodeVM::Core
  annotation Opcode; end
end

class IntCodeVM::Day2 < IntCodeVM::Core
  @[Opcode(1, :add)]
  def op_add(val1, val2, to_addr)
  end

  # ...
end

class IntCodeVM::Day5 < IntCodeVM::Day2
  @[Opcode(3, :input)]
  def op_input(to_addr)
  end
end

Now here is the problem:

class IntCodeVM::Core
  def gather_instructions
    # Here I want to select all methods in the hierarchy annotated
    # with `Opcode`:

    # Does not work, because `@type.methods` only lists methods in the
    # current type, not its ancestors.. (as per the documentation)
    {% defs = @type.methods.select { |m| !!m.annotation(Opcode) } %}

    # So I thought
    # "we need `@type.all_methods` to get methods from everywhere!"
    # Can I make an issue/PR ?

    # But in the meantime, let's do it manually using `@type.ancestors`
    {% begin %}
      {% all_methods = [] of _ %}
      {% @type.ancestors.each { |ancestor| all_methods.concat(ancestor.methods) } %}
      # then select on all_methods...
    {% end %}
    # Does not work: some missing methods:
    # - ArrayLiteral#each
    # - ArrayLiteral#concat (we can use `+=` as a workaround)
  end
end

How can I achieve this with current stdlib?

(Note: it’s quite late here, maybe I missed something obvious)

cc @Blacksmoke16 #macro-fu-master

This is related to https://github.com/crystal-lang/crystal/pull/7648. In short the way to do this ATM would be like have each type inherit from a parent abstract class/struct, iterate over all_subclasses, then iterate over the methods of each of those. Something like

{% begin %}
  {% methods = [] of Nil %}
  {% for type in ParentType.all_subclasses %}
    {% methods = methods + type.methods.select { |m| m.annotation(MyAnnotation) } %}
  {% end %}

  {{pp methods.map &.name}}
{% end %}

Thanks!!!

I went for this method, to actually get all the methods and select on them:

{% all_methods = @type.methods %}
{% for ancestor in @type.ancestors %}
  {% all_methods = all_methods + ancestor.methods %}
{% end %}
{% opcode_methods = all_methods.select { |m| !!m.annotation(Opcode) } %}

Because I don’t want to have my VM for Day2 to have the instructions for Day5 for exemple.