Eval and implicit passing of blocks

Hi,

I’m trying to understand if/how/when/why Ruby implicitly passes
blocks to
instance_eval, class_eval and eval, but not to plain methods.

It seems that 1.9 behavior is consistent across all versions of
eval: a
block, passed into a method that does an eval, is available for use
by
that eval. It also seems that 1.8.x has a bug in that the first
level
method called does not make the block available to eval, but that
it is available to more deeply nested method calls.

   class Mystery1
     def level_1(&block)
       level_2(&block)

       # 1.8.x: LocalJumpError ;  1.9.x: Ok
       puts "Mystery1 level_1: #{instance_eval('yield')}"
     end

     def level_2(&block)
       level_3(&block)
       puts "Mystery1 level_2: #{instance_eval('yield')}" # OK
     end

     def level_3(&block)
       puts "Mystery1 level_3: #{instance_eval('yield')}" # OK
     end
   end

   o = Mystery1.new
   begin
     o.level_1 { 12 }
   rescue Exception => e
     puts "#{o.class} raised #{e.class}: #{e.message}"
     p e.backtrace
   end

Running this against 1.8.[67] and 1.9.2 gives different results:

   ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)

[i686-darwin9.8.0]

   Mystery1 level_3: 12
   Mystery1 level_2: 12
   Mystery1 raised LocalJumpError: (eval):1:in `level_1': no block

given
["/Users/pmclain/tmp/m1.rb:6:in `level_1’", “/Users/pmclain/tmp/
m1.rb:21”]

   ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-

darwin9.8.0]

   Mystery1 level_3: 12
   Mystery1 level_2: 12
   Mystery1 raised LocalJumpError: (eval):1:in `level_1': no block

given
["/Users/pmclain/tmp/m1.rb:6:in `level_1’", “/Users/pmclain/tmp/
m1.rb:21”]

   ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
  1. [i386-darwin9.8.0]

    Mystery1 level_3: 12
    Mystery1 level_2: 12
    Mystery1 level_1: 12

Question 1: Is the 1.8 behavior a bug?

Question 2: Is the 1.9 behavior intended? If so, is it documented
anywhere? Perhaps the various eval methods should document that
they pass
the calling frame’s block to the eval.

If I replace “instance_eval” with “Mystery1.class_eval”, I get the
same
result. If I replace “instance_eval” with “eval”, I get different
results:

   ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)

[i686-darwin9.8.0]

   Mystery1 raised LocalJumpError: (eval):1:in `level_3': no block

given
["/Users/pmclain/tmp/m1.rb:15:in level_3'", "/Users/pmclain/ tmp/m1.rb:10:ineval’", “/Users/pmclain/tmp/m1.rb:15:in level_3'", "/ Users/pmclain/tmp/m1.rb:10:inlevel_2’”, “/Users/pmclain/tmp/m1.rb:
3:in `level_1’”, “/Users/pmclain/tmp/m1.rb:21”]

   ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-

darwin9.8.0]

   Mystery1 raised LocalJumpError: (eval):1:in `level_3': no block

given
["/Users/pmclain/tmp/m1.rb:15:in level_3'", "/Users/pmclain/ tmp/m1.rb:10:ineval’", “/Users/pmclain/tmp/m1.rb:15:in level_3'", "/ Users/pmclain/tmp/m1.rb:10:inlevel_2’”, “/Users/pmclain/tmp/m1.rb:
3:in `level_1’”, “/Users/pmclain/tmp/m1.rb:21”]

   ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
  1. [i386-darwin9.8.0]

    Mystery1 level_3: 12
    Mystery1 level_2: 12
    Mystery1 level_1: 12

So it looks like 1.9 is consistent in making the block available to
eval
at all levels, but 1.8 treats eval differently than class_eval and
instance_eval.

It also looks like 1.8 has another bug in that it tries too hard to
pass blocks along, whereas 1.9 gives reasonable behavior. In
Mystery2 I
don’t pass the block from level_2 to level_3, and 1.9 complains
that no
block was given; 1.8 seems to pass the block implicitly…

   class Mystery2
     def level_1(&block)
       level_2(&block)

       # 1.8.x: LocalJumpError ;  1.9.x: Ok
       #puts "Mystery2 level_1: #{instance_eval('yield')}"
     end

     def level_2(&block)
       level_3
       puts "Mystery2 level_2: #{instance_eval('yield')}" # OK
     end

     def level_3
       puts "Mystery2 level_3: #{instance_eval('yield')}" # 1.8:

OK 1.9: LocalJumpError
end
end

   o = Mystery2.new
   begin
     o.level_1 { 12 }
   rescue Exception => e
     puts "#{o.class} raised #{e.class}: #{e.message}"
     p e.backtrace
   end

And the output:

   ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)

[i686-darwin9.8.0]

   Mystery2 level_3: 12
   Mystery2 level_2: 12

   ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-

darwin9.8.0]

   Mystery2 level_3: 12
   Mystery2 level_2: 12

   ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
  1. [i386-darwin9.8.0]

    Mystery2 raised LocalJumpError: no block given (yield)
    ["/Users/pmclain/tmp/m2.rb:15:in instance_eval'", "/Users/ pmclain/tmp/m2.rb:15:ininstance_eval’", “/Users/pmclain/tmp/m2.rb:
    15:in level_3'", "/Users/pmclain/tmp/m2.rb:10:inlevel_2’”, “/Users/
    pmclain/tmp/m2.rb:3:in level_1'", "/Users/pmclain/tmp/m2.rb:21:in'”]

Question 3: Is the 1.8 behavior a bug?
Question 4: Is the 1.9 behavior correct?

Is there any documentation (other than the code) that describes
this in
detail? Neither the Pick Axe book, nor the Flanagan/Matz book give
detail on this. The RubySpecs also seem silent on this.


Peter McLain
[email protected]