Macro defs allow you to define a method for a class hierarchy and have that method be evaluated at the end of the type-inference phase, as a macro, where type information is known, for each concrete subtype. For example:
class Object
macro def instance_vars_names : Array(String)
{{ @type.instance_vars.map &.name.stringify }}
end
end
class Person
def initialize(@name, @age)
end
end
person = Person.new "John", 30
person.instance_vars_names #=> ["name", "age"]
Note that in the case of macro defs you need to specify the return type.
In macro definitions arguments are passed as their AST nodes, giving you access to them in macro expansions ({{some_macro_argument}}
). However that is not true for macro defs, here the argument list is that of the method generated by the macro def, you cannot not access their compile time value.
class Object
macro def has_instance_var?(name) : Bool
# We cannot access name inside the macro expansion here,
# instead we need to use the macro language to construct an array
# and do the inclusion check at runtime.
{{ @type.instance_vars.map &.name.stringify }}.includes? name
end
end
person = Person.new "John", 30
person.has_instance_var?("name") #=> true
person.has_instance_var?("birthday") #=> false