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/