Hooks

Special macros exist that are invoked in some situations as hooks, at compile time:

  • inherited is invoked when a subclass is defined. @type is the inheriting type.
  • included is invoked when a module is included. @type is the including type.
  • extended is invoked when a module is extended. @type is the extending type.
  • method_missing is invoked when a method is not found.
  • method_added is invoked when a new method is defined in the current scope.
  • finished is invoked after parsing finished, so all types and their methods are known.

Example of inherited:

  1. class Parent
  2. macro inherited
  3. def lineage
  4. "{{@type.name.id}} < Parent"
  5. end
  6. end
  7. end
  8. class Child < Parent
  9. end
  10. Child.new.lineage # => "Child < Parent"

Example of method_missing:

  1. macro method_missing(call)
  2. print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
  3. end
  4. foo # Prints: Got foo with 0 arguments
  5. bar 'a', 'b' # Prints: Got bar with 2 arguments

Example of method_added:

  1. macro method_added(method)
  2. {% puts "Method added:", method.name.stringify %}
  3. end
  4. def generate_random_number
  5. 4
  6. end
  7. # => Method added: generate_random_number

Both method_missing and method_added only apply to calls or methods in the same class that the macro is defined in, or only in the top level if the macro is defined outside of a class. For example:

  1. macro method_missing(call)
  2. puts "In outer scope, got call: ", {{ call.name.stringify }}
  3. end
  4. class SomeClass
  5. macro method_missing(call)
  6. puts "Inside SomeClass, got call: ", {{ call.name.stringify }}
  7. end
  8. end
  9. class OtherClass
  10. end
  11. # This call is handled by the top-level `method_missing`
  12. foo # => In outer scope, got call: foo
  13. obj = SomeClass.new
  14. # This is handled by the one inside SomeClass
  15. obj.bar # => Inside SomeClass, got call: bar
  16. other = OtherClass.new
  17. # Neither OtherClass or its parents define a `method_missing` macro
  18. other.baz # => Error: Undefined method 'baz' for OtherClass

finished is called once a type has been completely defined - this includes extensions on that class. Consider the following program:

  1. macro print_methods
  2. {% puts @type.methods.map &.name %}
  3. end
  4. class Foo
  5. macro finished
  6. {% puts @type.methods.map &.name %}
  7. end
  8. print_methods
  9. end
  10. class Foo
  11. def bar
  12. puts "I'm a method!"
  13. end
  14. end
  15. Foo.new.bar

The print_methods macro will be run as soon as it is encountered - and will print an empty list as there are no methods defined at that point. Once the second declaration of Foo is compiled the finished macro will be run, which will print [bar].