Calling in the dark

June 7th, 2008

Some of you may have overheard me on the first day of RailsConf bitching about some crazy piece of code in RSpec that was totally broken in Rubinius. If not, well, here is that code:

eval("caller", registration_binding_block.binding)

Wow, what is going on here? After tracing through the RSpec code, I learned that ‘registration_binding_block’ was a block being turned into a Proc via block_pass.

For example:

def describe(&block)
  @registration_binding_block = block
  yield
end

So now we know what’s being returned by this method, but what about the rest of that line? Some people may not know that eval can take a Proc as a second argument. For the purposes of eval, a Proc and that Proc’s binding produce the same result. So we should also be able to say:

eval("caller", registration_binding_block)

What does it even mean to ask for “caller” in a Proc’s binding? What are we asking for? The Proc in question may not even have executed yet. It could just be sitting around, waiting to be called.

It turns out that RSpec wants to know the stack trace, not from this call to ‘eval’, but back from where the “registration_binding_block” was originally written; for example, one of the user’s spec files. RSpec uses this fairly extreme meta-programming trick in order to match your specs back to the file and line they were written on.

After the massive headache of understanding what the fix was, it turned out to be fairly easy to hack into Rubinius, and most of the RSpec specs now pass.

For everyone running benchmarks on unfinished Ruby implementations.. good luck keeping your speed when you are done with the really fun Ruby features.

Anyone out there know an easier way to ask a Proc what its “definition trace” is than what RSpec uses? I can’t think of one myself yet..

6 Responses Follows

  1. eddie says

    I noticed that recently, and I am gonna look at it soon. Will let you know if any results will be found..

    Thanks

  2. Brian Takita says

    I wrote that line of code, so please be kind :) Thanks for the simplification. I committed it.

    We used to parse the file, but that technique doesn’t work well for with nested ExampgeGroups & shared ExampleGroups.

    Unfortunately I don’t know of a better way to get the stack trace of an invocation on MRI.

    I’m suprised that the following does not work:

    registration_binding_block.send(:caller)
  3. Piers Cawley says

    The paucity of reflective methods on potentially useful classes like Proc, Lambda, Binding et al is one of those things that I find frustrating about Ruby. I come to this from Perl, where the methods you need to deploy to get at this stuff is even more arcane than the ruby example you show, but where it’s actually possible to pull out more information before you have to resort to eval STRING.

    I’m hoping that turtles all the way down approaches like Rubinius will let people experiment with adding features like Block#send, hashlike accessors on Binding, metaquote type facilities for dynamic code generation, malleable parsers a million and one other good ideas that could be usefully lifted from languages like Lisp…

  4. Wilson Bilkovich says

    I totally agree, Brian. After today in Rubinius, you will be able to ask a Proc this question directly.

  5. Wilson Bilkovich says

    Proc#caller works in Rubinius now. Seems like something MRI could easily add as well.

  6. Brian Takita says

    Wilson, that is awesome. Thank you.

Sorry, comments are closed for this article.