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 its descendants, 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].

Depending on the macro hook used, a hook can either be stacked or overridden.

Stacking

When stacked, a hook is executed multiple times in its defined context for as many times as the hook is defined. Hooks executed in this way will execute in order of definition. Consider the following example:

  1. # Stack the top-level finished macro
  2. macro finished
  3. {% puts "I will execute!" %}
  4. end
  5. macro finished
  6. {% puts "I will also execute!" %}
  7. end

In the above example, both finished macros will execute. Stacking works for the following hooks: inherited, included, extended, method_added, finished

Overriding

A definition of the method_missing macro hook overrides any previous definition of this hook in the same context. Only the last defined macro executes. For example:

  1. macro method_missing(name)
  2. {% puts "I didnt run! :(" %}
  3. end
  4. class Example
  5. macro method_missing(name)
  6. {% puts "I didnt run! :(" %}
  7. end
  8. macro method_missing(name)
  9. {% puts "I am the only one that will run!" %}
  10. end
  11. end
  12. macro method_missing(name)
  13. {% puts "I am the only one that will run!" %}
  14. end
  15. Example.new.call_a_missing_method # => I am the only one that will run!
  16. call_a_missing_method # => I am the only one that will run!