From: "Eregon (Benoit Daloze) via ruby-core" <ruby-core@...>
Date: 2023-07-03T15:59:41+00:00
Subject: [ruby-core:114078] [Ruby master Feature#19755] Module#class_eval and Binding#eval use caller location by default

Issue #19755 has been updated by Eregon (Benoit Daloze).


byroot (Jean Boussier) wrote in #note-3:
> Decorating `class_eval / eval` should be quite rare anyways.

Indeed, and `class_eval`/`eval` is broken if decorated e.g. for `a = 3; class_eval "a"`.
The direct caller of `Module#class_eval` should always be the code around it.
Full example:
```ruby
# works as-is, breaks with DebugEval
module M
  a = 3
  class_eval "p a"
end
```



----------------------------------------
Feature #19755: Module#class_eval and Binding#eval use caller location by default
https://bugs.ruby-lang.org/issues/19755#change-103750

* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
----------------------------------------
### 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:

```ruby
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:

  - https://github.com/ruby/mutex_m/pull/11
  - https://github.com/rails/execjs/pull/120
  - https://github.com/jnunemaker/httparty/pull/776
  - https://github.com/SiftScience/sift-ruby/pull/76
  - https://github.com/activemerchant/active_merchant/pull/4675
  - https://github.com/rails/thor/pull/807
  - https://github.com/dry-rb/dry-initializer/pull/104
  - https://github.com/rmosolgo/graphql-ruby/pull/4288

### 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:

```ruby
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> 
```





-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/