Bug #10564
closedDelegateClass, method_missing, and instance_eval -- different behavior in ruby 2.1
Description
This is an odd one, I don't completely understand. But there is different behavior in ruby 2.0 vs 2.1, and the 2.0 behavior is what I'd expect. Not sure if it's a bug, expected difference in behavior, or something else.
Here is a minimized reproduction:
require 'delegate'
class DelegateWithMethodMissing < DelegateClass(Object)
def method_missing sym, *args, &block
if sym == :lambda
Kernel.raise Exception.new("Why is it catching lambda?")
end
Kernel.puts "Caught method missing for `#{sym}` with args: #{args}"
end
end
o = DelegateWithMethodMissing.new( Object.new )
o.instance_eval do
missing_method "value"
missing_method_with_lambda_value lambda {|a| a}
end
On 2.0.0p576, this produces:
Caught method missing for missing_method
with args: ["value"]
Caught method missing for missing_method_with_lambda_value
with args: [#<Proc:0x007fa8532b3330@./spec/confstruct/test.rb:19 (lambda)>]
On 2.1.3p242, this instead raises the exception:
Caught method missing for missing_method
with args: ["value"]
.test.rb:7:in `method_missing': Why is it catching lambda? (Exception)
Somehow the lambda
inside the instance_eval
is being caught by method_missing
, instead of accessing the ordinary lambda keyword. If I remove the method_missing
definition, then lambda
can work as expected again, which is even more confusing.
Updated by jrochkind (jonathan rochkind) over 9 years ago
Oh, and you can note Kernel.raise
and Kernel.puts
in the reproduction -- this was neccesary to actually access raise
and puts
, otherwise just raw raise
or puts
also got unexpectedly caught by method_missing (causing an infinite recursion running out of stack in this case)
Updated by matz (Yukihiro Matsumoto) over 9 years ago
- Status changed from Open to Closed
It's not a bug.
Delegator which is superclass of DelegateClass, is a subclass of BasicObject, not Object, so that those delegation objects does not respond to methods defined in Kernel module, that means lambda etc. are not available in the instance_eval block (where the receiver is Delegator).
Matz.
Updated by jrochkind (jonathan rochkind) over 9 years ago
Okay, thanks.
What I don't understand is, if method_missing
is not defined in the < DelegateClass(Object)
class, then I can still do:
my_lambda = o.instance_eval do
lambda {|a| a}
end
my_lambda #=> Proc object
And it works, and no method_missing on a superclass is called.
But if method_missing
is defined on the subclass, then it gets called. Why is method_missing even being called, for a line that would work fine without calling superclass method_missing?
If not a bug, I still predict it's definitely going to be unexpected behavior for other developers that use DelegateClass, instance_eval, and method_missing together.