From d36c3f4550e70d0f30c4e1d3a5bc48a50d4375d2 Mon Sep 17 00:00:00 2001 From: Nony Dutton Date: Wed, 15 Oct 2025 09:46:03 +0200 Subject: [PATCH] Set evaluation result to `_` local variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR makes a change to the thread client to set the `_` local variable to the result of the last evaluation similar to the behavior in irb[^1]. The reason `_` doesn't work even when using the `irb` console is because we leave the `irb` evaluation early if the input should be handled by the debugger[^2]. Otherwise, we would end up in IRB::Context#evaluate` and we would call `set_last_value`[^3]. There's a bit of a quirk here when using the `irb` console. I was hoping in `thread_client` to evaluate the result then set the `_` local variable and the `@last_value` ivar all in the same method, similar to how `irb` does it[^1]. However, because we're using the `irb` console we use a new `irb` `Workspace` which nils out `_` when initialized[^4]. To get around that, we set the local variable just before evaluating. [^1]:(https://github.com/ruby/irb/blob/d43c3d764ae439706aa1b26a3ec299cc45eaed5b/lib/irb/context.rb#L460-L465) [^2]:(https://github.com/ruby/irb/blob/d43c3d764ae439706aa1b26a3ec299cc45eaed5b/lib/irb.rb#L195-L198) [^3]:(https://github.com/ruby/irb/blob/d43c3d764ae439706aa1b26a3ec299cc45eaed5b/lib/irb/context.rb#L550-L558) [^4]:(https://github.com/ruby/irb/blob/d43c3d764ae439706aa1b26a3ec299cc45eaed5b/lib/irb/workspace.rb#L81) This PR was created by Zendesk's Ruby infrastructure team: Co-authored-by: Benjamin Quorning Co-authored-by: Edyta RozczypaƂa Co-authored-by: Jury Razumau Co-authored-by: Leonid Batizhevskii Co-authored-by: Luis Manotas Co-authored-by: Thomas Countz --- lib/debug/thread_client.rb | 2 ++ test/console/debugger_local_test.rb | 12 ++++++++++++ test/console/irb_test.rb | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/lib/debug/thread_client.rb b/lib/debug/thread_client.rb index 0e1fa42a1..346076bf2 100644 --- a/lib/debug/thread_client.rb +++ b/lib/debug/thread_client.rb @@ -443,7 +443,9 @@ def frame_eval src, re_raise: false, binding_location: false b.local_variable_set(name, var) if /\%/ !~ name end + b.local_variable_set(:_, @last_result) result = frame_eval_core(src, b, binding_location: binding_location) + @last_result = result @success_last_eval = true result diff --git a/test/console/debugger_local_test.rb b/test/console/debugger_local_test.rb index f32f43d0c..cfd18f2b0 100644 --- a/test/console/debugger_local_test.rb +++ b/test/console/debugger_local_test.rb @@ -19,6 +19,18 @@ def test_locals_added_in_locals_are_accessible_between_evaluations type "c" end end + + def test_evaluated_result_is_set_as_underscore_local + debug_code(program) do + type "_" + assert_line_text(/nil/) + type "3 * 3" + type "foo = _" + type "9 == foo" + assert_line_text(/true/) + type "c" + end + end end class RaisedTest < ConsoleTestCase diff --git a/test/console/irb_test.rb b/test/console/irb_test.rb index 746f9acdb..de4290050 100644 --- a/test/console/irb_test.rb +++ b/test/console/irb_test.rb @@ -84,6 +84,20 @@ def test_irb_console_config_activates_irb ENV["RUBY_DEBUG_IRB_CONSOLE"] = nil end + def test_irb_console_evaluated_result_is_set_as_underscore_local + debug_code(program, remote: false) do + type 'irb' + type "_" + assert_line_text(/nil/) + type "3 * 3" + assert_raw_line_text 'irb:rdbg(main):003> 3 * 3' + type "foo = _" + type "9 == foo" + assert_line_text(/true/) + type "c" + end + end + private # assert_line_text ignores the prompt line, so we can't use it to assert the prompt transition