Project

General

Profile

Actions

Feature #19755

closed

Module#class_eval and Binding#eval use caller location by default

Added by byroot (Jean Boussier) 10 months ago. Updated 10 months ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:114074]

Description

Background

In Ruby we're very reliant on Method#source_location as well as caller_locations to locate source code.

However, code generated via Binding#eval, Module#class_eval etc defeat this if called without a location:

Foo.class_eval <<~RUBY
  def bar
  end
RUBY

p Foo.instance_method(:bar).source_location # => ["(eval)", 1]

The overwhelming majority of open source code properly pass a filename and lineno, however a small minority doesn't and make locating the code much harder than it needs to be.

Here's some example of anonymous eval uses I fixed in the past:

Proposal

I suggest that instead of defaulting to "(eval)", the optional filename argument of the eval family of methods should instead default to: "(eval in #{caller_locations(1, 1).first.path})" and lineno to caller_locations(1, 1).first.lineno.

Which can pretty much be monkey patched as this:

module ModuleEval
  def class_eval(code, location = "(eval in #{caller_locations(1, 1).first.path})", lineno = caller_locations(1, 1).first.lineno)
    super
  end
end

Module.prepend(ModuleEval)

module Foo
  class_eval <<~RUBY
    def foo
    end
  RUBY
end

p Foo.instance_method(:foo)

before:

#<UnboundMethod: Foo#foo() (eval):1>

after:

#<UnboundMethod: Foo#foo() (eval in /tmp/foo.rb):10> 

Of course the lineno part is likely to not be fully correct, but the reasoning is that it's better than defaulting to 0 or 1.

Another possiblity would be to include the caller lineo inside the filename part, and leave the actual lineo default to 1:

#<UnboundMethod: Foo#foo() (eval at /tmp/foo.rb:10):1> 
Actions

Also available in: Atom PDF

Like3
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0