I noticed that Object#delegate
wasn’t type-safe, it just takes splats for arguments rather than copying the argument types from the delegated-to method. And it didn’t handle blocks. So, I worked out a macro to make a type-safe one, that handles blocks, but I can’t get the syntax to be exactly like the Object#delegate
. Notice that I am calling A.delegate(method-name, to: object)
below, with the class-name at the start. Can you think of a way for me to make the syntax just like the existing Object#delegate
?
Note: Don’t look at this one, there is a much shorter version below!
class Object
macro delegate2(name, to)
{% methods = @type.methods %}
{% got_name = false %}
{% for m in methods %}
{% if m.name.id == name.id %}
{% got_name = true %}
{% has_yield = false %}
{% if m.body.class_name == "Expressions" %}
{% for node in m.body.expressions %}
{% if node.class_name == "Yield" %}
{% has_yield = true %}
{% end %}
{% end %}
{% end %}
{% if m.body.class_name == "Yield" %}
{% has_yield = true %}
{% end %}
def {{name}}({{m.args.join(",").id}}) {{ m.return_type.stringify != "" ? ": #{m.return_type}".id : "".id }}
{{to}}.{{name}}({{m.args.map{ |arg| arg.internal_name }.join(", ").id}}) {{has_yield ? "do |*args| yield *args end".id : "".id}}
end
{% end %}
{% end %}
{% if !got_name %}
{% raise "delegate: Method \"#{name}\" wasn't found in \"#{@type.name}\"." %}
{% end %}
{% debug %}
end
end
class A
def foo(a : Int32) : String
yield
end
end
class B
@a = A.new
A.delegate2(foo, to: @a)
end
b = B.new
p b.foo(1) { "Hello" }
To do: Macros::Yield doesn’t give enough data to make the block type-safe, unless a &block argument is defined. The code above doesn’t currently get the macro argument information from the block argument, it just always uses splat.