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 10 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 10 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 10 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.