Skip to content

Conversation

@HeyNonster
Copy link
Contributor

@HeyNonster HeyNonster commented Oct 15, 2025

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 irb1.

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 debugger2. Otherwise, we would end up in IRB::Context#evaluate and we would call set_last_value3.

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 it1. However, because we're using the irb console we use a new irb Workspace which nils out _ when initialized4. To get around that, we set the local variable just before evaluating.

Footnotes

  1. (https://github.com/ruby/irb/blob/d43c3d764ae439706aa1b26a3ec299cc45eaed5b/lib/irb/context.rb#L460-L465) 2

  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 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 <bquorning@zendesk.com>
Co-authored-by: Edyta Rozczypała <edyta.rozczypala@zendesk.com>
Co-authored-by: Jury Razumau <jury.razumau@zendesk.com>
Co-authored-by: Leonid Batizhevskii <leonid.batizhevskii@zendesk.com>
Co-authored-by: Luis Manotas <lgerman@gmail.com>
Co-authored-by: Thomas Countz <thomascountz@gmail.com>
@launchable-app
Copy link

launchable-app bot commented Oct 15, 2025

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">.

[-> View Test suite health in main branch]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat/Bug: Set the value of _ (underscore) to the value of the last ruby expression

1 participant