Create macro method that is instantiated for all types in the hierarchy

The documentation says:

Macro defs allow you to define a method for a class hierarchy which is then instantiated for each concrete subtype.

Is there a way to do this for all types in the hierarchy? I.e. if I have classes A < B < C, can I instantiate the macro method for all three classes, including C?

Pretty sure this is how it already works:

class C
  def type_name
    {{ @type.name.stringify }}
  end
end

class B < C; end
class A < B; end


pp C.new.type_name # => "C"
pp B.new.type_name # => "B"
pp A.new.type_name # => "A"

Unless you are wanting something different?

Since the documentation says “concrete” I wanted to test this for abstract classes, but encountered the following:

class Object
  def type_name
    "*"
  end
end

class C
  def type_name
    super.type_name + {{ @type.name.stringify }}
  end
end

class B < C; end

class A < B; end

pp A.new.type_name # actual: "*A", expected: "*CBA"
pp B.new.type_name # "*B"
pp C.new.type_name # "*C"

Now I’m confused. The method is be instantiated for A, B, & C, but calling the superclass’ method does not work?

The super keyword doesn’t work like how you’d expect it to. Unlike self, super automatically calls the parent method with the given arguments (if any), so super.type_name in A and B calls #type_name in C first which returns a string, then it calls #type_name again on Object, hence why you got “*”.

Despite that technicality, it doesn’t actually solve the issue — as you’ve discovered it’s only calling #type_name on C because the method is only defined on C so A and B just use that. Solving this requires a bit more macro magic using the macro inherited hook:

macro def_type_name
  def type_name
    super + {{ @type.name.stringify }}
  end
end

class C
  def_type_name

  macro inherited
    def_type_name
  end
end

Firstly we’ve moved the type_name definition into the def_type_name macro and call it in C; to put it simply, it will define the method on C for us. Next, we’re calling def_type_name in the inherited macro. That macro will run every time C is inherited which, in this case, is twice. That allows the macro interpreter to evaluate {{ @type.name.stringify }} in A and B.