When I delegate methods on instances of class A to $delegation_target as below:
$delegation_target = ""
class A
def method_missing *args, ≺ $delegation_target.send(*args, &pr) end
def respond_to_missing? *args; $delegation_target.respond_to?(*args) end
end
the arity of the methods on instances of A returns -1 irrespective of the arity of the methods on $delegation_target:
def $delegation_target.foo; end
A.new.method(:foo).arity # => -1
def $delegation_target.foo arg1; end
A.new.method(:foo).arity # => -1
def $delegation_target.foo arg1, arg2; end
A.new.method(:foo).arity # => -1
def $delegation_target.foo arg1, arg2, arg3; end
A.new.method(:foo).arity # => -1
Where does this -1 come from? And, is there a way to make it so that, for any possible method name m, A.new.method(m).arity returns the arity of $delegation_target.method(m) (if it is defined)?
Object#methodhandlesrespond_to_missing?&method_missingin a special way. Let’s dive into the Ruby C source and see what happens:Starting at
Object#method, we callmnew, which creates a newMethodobject for the object it’s called on and the id passed. In the source formnewwe can easily see the special handling when the method is not defined, butrespond_to_missing?returnstruewhen given the id:The
def->type = VM_METHOD_TYPE_MISSING;is important. Finding the definition forVM_METHOD_TYPE_MISSING, we see that it is a “wrapper formethod_missing(id)“. So essentially the method that is returned is really justmethod_missing, with the first argument already specified as the id of the method you were originally trying to get.We can confirm our suspicions by verifying that the arity of
method_missingis the same as what we’re getting:As an aside, an arity of
-1means that the method can take an unlimited number of arguments.As for whether you can have it return the “real” arity of the method being called… no, you can’t. For one, Ruby makes no assumptions about what happens in your
method_missing, and doesn’t even know that it’s simply delegating to some other method.