Set evaluation result to `_` local variable
This resolves #1069.
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.
:x: 1/689 Tests Failed
/home/runner/work/debug/debug/test/protocol/catch_raw_dap_test.rb#test_catching_any_exception_works_correctly
-------------------------
| All Protocol Messages |
-------------------------
V>D {"seq":1,"command":"initialize","arguments":{"clientID":"vscode","clientName":"Visual Studio Code","adapterID":"rdbg","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,"supportsVariableType":true,"supportsVariablePaging":true,"supportsRunInTerminalRequest":true,"locale":"en-us","supportsProgressReporting":true,"supportsInvalidatedEvent":true,"supportsMemoryReferences":true},"type":"request"}
V>D {"seq":2,"command":"attach","arguments":{"type":"rdbg","name":"Attach with rdbg","request":"attach","rdbgPath":"/home/runner/work/debug/debug/exe/rdbg","debugPort":"/var/folders/kv/w1k6nh1x5fl7vx47b2pd005w0000gn/T/ruby-debug-sock-501/ruby-debug-naotto-8845","autoAttach":true,"__sessionId":"141d9c79-3669-43ec-ac1f-e62598c5a65a"},"type":"request"}
V>D {"seq":3,"command":"setFunctionBreakpoints","arguments":{"breakpoints":[]},"type":"request"}
V>D {"seq":4,"command":"setExceptionBreakpoints","arguments":{"filters":[],"filterOptions":[{"filterId":"RuntimeError"}]},"type":"request"}
V>D {"seq":5,"command":"configurationDone","type":"request"}
V<D {"type":"response","command":"initialize","request_seq":1,"success":true,"message":"Success","body":{"supportsConfigurationDoneRequest":true,"supportsFunctionBreakpoints":true,"supportsConditionalBreakpoints":true,"supportTerminateDebuggee":true,"supportsTerminateRequest":true,"exceptionBreakpointFilters":[{"filter":"any","label":"rescue any exception","supportsCondition":true},{"filter":"RuntimeError","label":"rescue RuntimeError","supportsCondition":true}],"supportsExceptionFilterOptions":true,"supportsStepBack":true,"supportsEvaluateForHovers":true,"supportsCompletionsRequest":true},"seq":1}
V<D {"type":"event","event":"initialized","seq":2}
V<D {"type":"event","event":"output","body":{"category":"console","output":"Ruby REPL: You can run any Ruby expression here.\nNote that output to the STDOUT/ERR printed on the TERMINAL.\n[experimental]\n `,COMMAND` runs `COMMAND` debug command (ex: `,info`).\n `,help` to list all debug commands.\n"},"seq":3}
V<D {"type":"response","command":"attach","request_seq":2,"success":true,"message":"Success","seq":4}
V<D {"type":"response","command":"setFunctionBreakpoints","request_seq":3,"success":true,"message":"Success","seq":5}
V<D {"type":"response","command":"setExceptionBreakpoints","request_seq":4,"success":true,"message":"Success","body":{"breakpoints":[{"verified":true,"message":"#<DEBUGGER__::CatchBreakpoint:0x00007f71f6f45420 @pat=\"RuntimeError\", @key=[:catch, \"RuntimeError\"], @last_exc=nil, @deleted=false, @cond=nil, @command=nil, @path=nil, @tp=#<TracePoint:enabled>>"}]},"seq":6}
V<D {"type":"response","command":"configurationDone","request_seq":5,"success":true,"message":"Success","seq":7}
V<D {"type":"event","event":"stopped","body":{"reason":"pause","threadId":1,"allThreadsStopped":true},"seq":8}
V>D {"seq":6,"command":"threads","type":"request"}
V<D {"type":"response","command":"threads","request_seq":6,"success":true,"message":"Success","body":{"threads":[{"id":1,"name":"#1 /tmp/debug-20251015-2727-y0wy3o.rb:1:in '<main>'"}]},"seq":9}
V>D {"seq":7,"command":"threads","type":"request"}
V<D {"type":"response","command":"threads","request_seq":7,"success":true,"message":"Success","body":{"threads":[{"id":1,"name":"#1 /tmp/debug-20251015-2727-y0wy3o.rb:1:in '<main>'"}]},"seq":10}
V>D {"seq":8,"command":"stackTrace","arguments":{"threadId":1,"startFrame":0,"levels":20},"type":"request"}
V<D {"type":"response","command":"stackTrace","request_seq":8,"success":true,"message":"Success","body":{"stackFrames":[{"id":1,"name":"<main>","line":1,"column":1,"source":{"name":"debug-20251015-2727-y0wy3o.rb","path":"/tmp/debug-20251015-2727-y0wy3o.rb","sourceReference":0}}],"totalFrames":1},"seq":11}
V>D {"seq":9,"command":"scopes","arguments":{"frameId":1},"type":"request"}
V<D {"type":"response","command":"scopes","request_seq":9,"success":true,"message":"Success","body":{"scopes":[{"name":"Local variables","presentationHint":"locals","namedVariables":0,"indexedVariables":0,"expensive":false,"variablesReference":2},{"name":"Global variables","presentationHint":"globals","variablesReference":1,"namedVariables":41,"indexedVariables":0,"expensive":false}]},"seq":12}
V>D {"seq":10,"command":"variables","arguments":{"variablesReference":2},"type":"request"}
V<D {"type":"response","command":"variables","request_seq":10,"success":true,"message":"Success","body":{"variables":[{"name":"%self","value":"main","type":"Object","variablesReference":3,"indexedVariables":0,"namedVariables":1}]},"seq":13}
V>D {"seq":11,"command":"setExceptionBreakpoints","arguments":{"filters":[],"filterOptions":[{"filterId":"any"},{"filterId":"RuntimeError"}]},"type":"request"}
V<D {"type":"response","command":"setExceptionBreakpoints","request_seq":11,"success":true,"message":"Success","body":{"breakpoints":[{"verified":true,"message":"#<DEBUGGER__::CatchBreakpoint:0x00007f71f6f41ee0 @pat=\"Exception\", @key=[:catch, \"Exception\"], @last_exc=nil, @deleted=false, @cond=nil, @command=nil, @path=nil, @tp=#<TracePoint:enabled>>"},{"verified":true,"message":"#<DEBUGGER__::CatchBreakpoint:0x00007f71f6f41e20 @pat=\"RuntimeError\", @key=[:catch, \"RuntimeError\"], @last_exc=nil, @deleted=false, @cond=nil, @command=nil, @path=nil, @tp=#<TracePoint:enabled>>"}]},"seq":14}
V>D {"seq":12,"command":"continue","arguments":{"threadId":1},"type":"request"}
V<D {"type":"response","command":"continue","request_seq":12,"success":true,"message":"Success","body":{"allThreadsContinued":true},"seq":15}
V<D {"type":"event","event":"stopped","body":{"reason":"exception","description":"#<ZeroDivisionError: divided by 0> is raised.","text":"#<ZeroDivisionError: divided by 0> is raised.","threadId":1,"allThreadsStopped":true},"seq":16}
V>D {"seq":13,"command":"threads","type":"request"}
V<D {"type":"response","command":"threads","request_seq":13,"success":true,"message":"Success","body":{"threads":[{"id":1,"name":"#1 /tmp/debug-20251015-2727-y0wy3o.rb:1:in '<main>'"}]},"seq":17}
V>D {"seq":14,"command":"stackTrace","arguments":{"threadId":1,"startFrame":0,"levels":20},"type":"request"}
V<D {"type":"response","command":"stackTrace","request_seq":14,"success":true,"message":"Success","body":{"stackFrames":[{"id":2,"name":"Foo::Bar.a","line":4,"column":1,"source":{"name":"debug-20251015-2727-y0wy3o.rb","path":"/tmp/debug-20251015-2727-y0wy3o.rb","sourceReference":0}},{"id":3,"name":"<module:Foo>","line":7,"column":1,"source":{"name":"debug-20251015-2727-y0wy3o.rb","path":"/tmp/debug-20251015-2727-y0wy3o.rb","sourceReference":0}},{"id":4,"name":"<main>","line":1,"column":1,"source":{"name":"debug-20251015-2727-y0wy3o.rb","path":"/tmp/debug-20251015-2727-y0wy3o.rb","sourceReference":0}}],"totalFrames":4},"seq":18}
--------------------------
| Last Protocol Messages |
--------------------------
{
"seq": 13,
"command": "threads",
"type": "request"
}
{
"type": "response",
"command": "threads",
"request_seq": 13,
"success": true,
"message": "Success",
"body": {
"threads": [
{
"id": 1,
"name": "#1 /tmp/debug-20251015-2727-y0wy3o.rb:1:in '<main>'"
}
]
},
"seq": 17
}
{
"seq": 14,
"command": "stackTrace",
"arguments": {
"threadId": 1,
"startFrame": 0,
"levels": 20
},
"type": "request"
}
{
"type": "response",
"command": "stackTrace",
"request_seq": 14,
"success": true,
"message": "Success",
"body": {
"stackFrames": [
{
"id": 2,
"name": "Foo::Bar.a",
"line": 4,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
},
{
"id": 3,
"name": "<module:Foo>",
"line": 7,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
},
{
"id": 4,
"name": "<main>",
"line": 1,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
}
],
"totalFrames": 4
},
"seq": 18
}
--------------------
| Debuggee Session |
--------------------
> DEBUGGER: Debugger can attach via UNIX domain socket (/run/user/1001/rdbg-2727-17)
> DEBUGGER: wait for debugger connection...
> DEBUGGER: Connected.
-------------------
| Failure Message |
-------------------
expected:
{
"type": "response",
"command": "stackTrace",
"request_seq": 14,
"success": true,
"message": "Success",
"body": {
"stackFrames": [
{
"name": "[C] Integer#/",
"line": 4,
"column": 1,
"source": {
"name": "(?-mix:debug-20251015-2727-y0wy3o.rb)",
"path": "(?-mix:\\/tmp\\/debug-20251015-2727-y0wy3o.rb)",
"sourceReference": 0
},
"id": 2
},
{
"name": "Foo::Bar.a",
"line": 4,
"column": 1,
"source": {
"name": "(?-mix:debug-20251015-2727-y0wy3o.rb)",
"path": "(?-mix:\\/tmp\\/debug-20251015-2727-y0wy3o.rb)",
"sourceReference": 0
},
"id": 3
},
{
"name": "<module:Foo>",
"line": 7,
"column": 1,
"source": {
"name": "(?-mix:debug-20251015-2727-y0wy3o.rb)",
"path": "(?-mix:\\/tmp\\/debug-20251015-2727-y0wy3o.rb)",
"sourceReference": 0
},
"id": 4
},
{
"name": "<main>",
"line": 1,
"column": 1,
"source": {
"name": "(?-mix:debug-20251015-2727-y0wy3o.rb)",
"path": "(?-mix:\\/tmp\\/debug-20251015-2727-y0wy3o.rb)",
"sourceReference": 0
},
"id": 5
}
]
}
}
result:
{
"type": "response",
"command": "stackTrace",
"request_seq": 14,
"success": true,
"message": "Success",
"body": {
"stackFrames": [
{
"id": 2,
"name": "Foo::Bar.a",
"line": 4,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
},
{
"id": 3,
"name": "<module:Foo>",
"line": 7,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
},
{
"id": 4,
"name": "<main>",
"line": 1,
"column": 1,
"source": {
"name": "debug-20251015-2727-y0wy3o.rb",
"path": "/tmp/debug-20251015-2727-y0wy3o.rb",
"sourceReference": 0
}
}
],
"totalFrames": 4
},
"seq": 18
}.
<"[C] Integer#/"> expected but was
<"Foo::Bar.a">.
how to handle _ if it is already available?