Hooks¶
Special macros exist that are invoked in some situations as hooks, at compile time:
inheritedis invoked when a subclass is defined.@typeis the inheriting type.includedis invoked when a module is included.@typeis the including type.extendedis invoked when a module is extended.@typeis the extending type.method_missingis invoked when a method is not found.method_addedis invoked when a new method is defined in the current scope.finishedis invoked after parsing finished, so all types and their methods are known.
Example of inherited:
class Parent
macro inherited
def lineage
"{{@type.name.id}} < Parent"
end
end
end
class Child < Parent
end
Child.new.lineage # => "Child < Parent"
Example of method_missing:
macro method_missing(call)
print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end
foo # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments
Example of method_added:
macro method_added(method)
{% puts "Method added:", method.name.stringify %}
end
def generate_random_number
4
end
# => 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:
macro method_missing(call)
puts "In outer scope, got call: ", {{ call.name.stringify }}
end
class SomeClass
macro method_missing(call)
puts "Inside SomeClass, got call: ", {{ call.name.stringify }}
end
end
class OtherClass
end
# This call is handled by the top-level `method_missing`
foo # => In outer scope, got call: foo
obj = SomeClass.new
# This is handled by the one inside SomeClass
obj.bar # => Inside SomeClass, got call: bar
other = OtherClass.new
# Neither OtherClass or its parents define a `method_missing` macro
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:
macro print_methods
{% puts @type.methods.map &.name %}
end
class Foo
macro finished
{% puts @type.methods.map &.name %}
end
print_methods
end
class Foo
def bar
puts "I'm a method!"
end
end
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:
# Stack the top-level finished macro
macro finished
{% puts "I will execute!" %}
end
macro finished
{% puts "I will also execute!" %}
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:
macro method_missing(name)
{% puts "I didnt run! :(" %}
end
class Example
macro method_missing(name)
{% puts "I didnt run! :(" %}
end
macro method_missing(name)
{% puts "I am the only one that will run!" %}
end
end
macro method_missing(name)
{% puts "I am the only one that will run!" %}
end
Example.new.call_a_missing_method # => I am the only one that will run!
call_a_missing_method # => I am the only one that will run!