[Lldb-commits] [lldb] 4ba8f4e - [lldb][lldb-dap] Migrate ScopesRequest to structured types (#138116)

via lldb-commits lldb-commits at lists.llvm.org
Thu May 15 02:16:09 PDT 2025


Author: Ebuka Ezike
Date: 2025-05-15T10:16:05+01:00
New Revision: 4ba8f4e213c97733e3b61e5856b0e85e3d7d6a7f

URL: https://github.com/llvm/llvm-project/commit/4ba8f4e213c97733e3b61e5856b0e85e3d7d6a7f
DIFF: https://github.com/llvm/llvm-project/commit/4ba8f4e213c97733e3b61e5856b0e85e3d7d6a7f.diff

LOG: [lldb][lldb-dap] Migrate ScopesRequest to  structured types (#138116)

Migrate ScopesRequest To use the Protocol Types

Added: 
    

Modified: 
    lldb/tools/lldb-dap/DAP.cpp
    lldb/tools/lldb-dap/DAP.h
    lldb/tools/lldb-dap/Handler/RequestHandler.h
    lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
    lldb/tools/lldb-dap/JSONUtils.h
    lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
    lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
    lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
    lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
    lldb/unittests/DAP/ProtocolTypesTest.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 51f9da854f4b6..56a0c38b00037 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -559,17 +559,6 @@ lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
   return GetLLDBFrame(frame_id);
 }
 
-llvm::json::Value DAP::CreateTopLevelScopes() {
-  llvm::json::Array scopes;
-  scopes.emplace_back(
-      CreateScope("Locals", VARREF_LOCALS, variables.locals.GetSize(), false));
-  scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS,
-                                  variables.globals.GetSize(), false));
-  scopes.emplace_back(CreateScope("Registers", VARREF_REGS,
-                                  variables.registers.GetSize(), false));
-  return llvm::json::Value(std::move(scopes));
-}
-
 ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
                              bool partial_expression) {
   // Check for the escape hatch prefix.

diff  --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index c2e4c2dea582e..9065995f5d722 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -283,10 +283,10 @@ struct DAP {
   lldb::SBThread GetLLDBThread(const llvm::json::Object &arguments);
 
   lldb::SBFrame GetLLDBFrame(uint64_t frame_id);
+  /// TODO: remove this function when we finish migrating to the
+  /// new protocol types.
   lldb::SBFrame GetLLDBFrame(const llvm::json::Object &arguments);
 
-  llvm::json::Value CreateTopLevelScopes();
-
   void PopulateExceptionBreakpoints();
 
   /// Attempt to determine if an expression is a variable expression or

diff  --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index b0002440cf72e..eaebaf6619bbd 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -452,11 +452,15 @@ class PauseRequestHandler : public LegacyRequestHandler {
   void operator()(const llvm::json::Object &request) const override;
 };
 
-class ScopesRequestHandler : public LegacyRequestHandler {
+class ScopesRequestHandler final
+    : public RequestHandler<protocol::ScopesArguments,
+                            llvm::Expected<protocol::ScopesResponseBody>> {
 public:
-  using LegacyRequestHandler::LegacyRequestHandler;
+  using RequestHandler::RequestHandler;
   static llvm::StringLiteral GetCommand() { return "scopes"; }
-  void operator()(const llvm::json::Object &request) const override;
+
+  llvm::Expected<protocol::ScopesResponseBody>
+  Run(const protocol::ScopesArguments &args) const override;
 };
 
 class SetVariableRequestHandler final

diff  --git a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
index 7d1608f59f9a4..aaad0e20f9c21 100644
--- a/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ScopesRequestHandler.cpp
@@ -7,69 +7,56 @@
 //===----------------------------------------------------------------------===//
 
 #include "DAP.h"
-#include "EventHelper.h"
-#include "JSONUtils.h"
 #include "RequestHandler.h"
 
+using namespace lldb_dap::protocol;
 namespace lldb_dap {
 
-// "ScopesRequest": {
-//   "allOf": [ { "$ref": "#/definitions/Request" }, {
-//     "type": "object",
-//     "description": "Scopes request; value of command field is 'scopes'. The
-//     request returns the variable scopes for a given stackframe ID.",
-//     "properties": {
-//       "command": {
-//         "type": "string",
-//         "enum": [ "scopes" ]
-//       },
-//       "arguments": {
-//         "$ref": "#/definitions/ScopesArguments"
-//       }
-//     },
-//     "required": [ "command", "arguments"  ]
-//   }]
-// },
-// "ScopesArguments": {
-//   "type": "object",
-//   "description": "Arguments for 'scopes' request.",
-//   "properties": {
-//     "frameId": {
-//       "type": "integer",
-//       "description": "Retrieve the scopes for this stackframe."
-//     }
-//   },
-//   "required": [ "frameId" ]
-// },
-// "ScopesResponse": {
-//   "allOf": [ { "$ref": "#/definitions/Response" }, {
-//     "type": "object",
-//     "description": "Response to 'scopes' request.",
-//     "properties": {
-//       "body": {
-//         "type": "object",
-//         "properties": {
-//           "scopes": {
-//             "type": "array",
-//             "items": {
-//               "$ref": "#/definitions/Scope"
-//             },
-//             "description": "The scopes of the stackframe. If the array has
-//             length zero, there are no scopes available."
-//           }
-//         },
-//         "required": [ "scopes" ]
-//       }
-//     },
-//     "required": [ "body" ]
-//   }]
-// }
-void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
-  llvm::json::Object response;
-  FillResponse(request, response);
-  llvm::json::Object body;
-  const auto *arguments = request.getObject("arguments");
-  lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
+/// Creates a `protocol::Scope` struct.
+///
+///
+/// \param[in] name
+///     The value to place into the "name" key
+///
+/// \param[in] variablesReference
+///     The value to place into the "variablesReference" key
+///
+/// \param[in] namedVariables
+///     The value to place into the "namedVariables" key
+///
+/// \param[in] expensive
+///     The value to place into the "expensive" key
+///
+/// \return
+///     A `protocol::Scope`
+static Scope CreateScope(const llvm::StringRef name, int64_t variablesReference,
+                         int64_t namedVariables, bool expensive) {
+  Scope scope;
+  scope.name = name;
+
+  // TODO: Support "arguments" and "return value" scope.
+  // At the moment lldb-dap includes the arguments and return_value  into the
+  // "locals" scope.
+  // vscode only expands the first non-expensive scope, this causes friction
+  // if we add the arguments above the local scope as the locals scope will not
+  // be expanded if we enter a function with arguments. It becomes more
+  // annoying when the scope has arguments, return_value and locals.
+  if (variablesReference == VARREF_LOCALS)
+    scope.presentationHint = Scope::eScopePresentationHintLocals;
+  else if (variablesReference == VARREF_REGS)
+    scope.presentationHint = Scope::eScopePresentationHintRegisters;
+
+  scope.variablesReference = variablesReference;
+  scope.namedVariables = namedVariables;
+  scope.expensive = expensive;
+
+  return scope;
+}
+
+llvm::Expected<ScopesResponseBody>
+ScopesRequestHandler::Run(const ScopesArguments &args) const {
+  lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
+
   // As the user selects 
diff erent stack frames in the GUI, a "scopes" request
   // will be sent to the DAP. This is the only way we know that the user has
   // selected a frame in a thread. There are no other notifications that are
@@ -78,9 +65,9 @@ void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
   // are sent, this allows users to type commands in the debugger console
   // with a backtick character to run lldb commands and these lldb commands
   // will now have the right context selected as they are run. If the user
-  // types "`bt" into the debugger console and we had another thread selected
+  // types "`bt" into the debugger console, and we had another thread selected
   // in the LLDB library, we would show the wrong thing to the user. If the
-  // users switches threads with a lldb command like "`thread select 14", the
+  // users switch threads with a lldb command like "`thread select 14", the
   // GUI will not update as there are no "event" notification packets that
   // allow us to change the currently selected thread or frame in the GUI that
   // I am aware of.
@@ -88,7 +75,6 @@ void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
     frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
     frame.GetThread().SetSelectedFrame(frame.GetFrameID());
   }
-
   dap.variables.locals = frame.GetVariables(/*arguments=*/true,
                                             /*locals=*/true,
                                             /*statics=*/false,
@@ -98,9 +84,15 @@ void ScopesRequestHandler::operator()(const llvm::json::Object &request) const {
                                              /*statics=*/true,
                                              /*in_scope_only=*/true);
   dap.variables.registers = frame.GetRegisters();
-  body.try_emplace("scopes", dap.CreateTopLevelScopes());
-  response.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(response)));
+
+  std::vector scopes = {CreateScope("Locals", VARREF_LOCALS,
+                                    dap.variables.locals.GetSize(), false),
+                        CreateScope("Globals", VARREF_GLOBALS,
+                                    dap.variables.globals.GetSize(), false),
+                        CreateScope("Registers", VARREF_REGS,
+                                    dap.variables.registers.GetSize(), false)};
+
+  return ScopesResponseBody{std::move(scopes)};
 }
 
 } // namespace lldb_dap

diff  --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 9c4dd0584bd21..783f291338d8c 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -238,27 +238,6 @@ llvm::json::Object CreateEventObject(const llvm::StringRef event_name);
 protocol::ExceptionBreakpointsFilter
 CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
 
-/// Create a "Scope" JSON object as described in the debug adapter definition.
-///
-/// \param[in] name
-///     The value to place into the "name" key
-//
-/// \param[in] variablesReference
-///     The value to place into the "variablesReference" key
-//
-/// \param[in] namedVariables
-///     The value to place into the "namedVariables" key
-//
-/// \param[in] expensive
-///     The value to place into the "expensive" key
-///
-/// \return
-///     A "Scope" JSON object with that follows the formal JSON
-///     definition outlined by Microsoft.
-llvm::json::Value CreateScope(const llvm::StringRef name,
-                              int64_t variablesReference,
-                              int64_t namedVariables, bool expensive);
-
 /// Create a "Source" JSON object as described in the debug adapter definition.
 ///
 /// \param[in] file

diff  --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 316e146d43a0f..7efab87d39986 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -335,6 +335,20 @@ llvm::json::Value toJSON(const SetVariableResponseBody &SVR) {
 
   return llvm::json::Value(std::move(Body));
 }
+bool fromJSON(const llvm::json::Value &Params, ScopesArguments &SCA,
+              llvm::json::Path P) {
+  json::ObjectMapper O(Params, P);
+  return O && O.map("frameId", SCA.frameId);
+}
+
+llvm::json::Value toJSON(const ScopesResponseBody &SCR) {
+  llvm::json::Array scopes;
+  for (const Scope &scope : SCR.scopes) {
+    scopes.emplace_back(toJSON(scope));
+  }
+
+  return llvm::json::Object{{"scopes", std::move(scopes)}};
+}
 
 bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) {
   json::ObjectMapper O(Params, P);

diff  --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 710fa5d2c57ed..4e08b4728453b 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -439,6 +439,19 @@ struct SetVariableResponseBody {
 };
 llvm::json::Value toJSON(const SetVariableResponseBody &);
 
+struct ScopesArguments {
+  /// Retrieve the scopes for the stack frame identified by `frameId`. The
+  /// `frameId` must have been obtained in the current suspended state. See
+  /// 'Lifetime of Object References' in the Overview section for details.
+  uint64_t frameId = LLDB_INVALID_FRAME_ID;
+};
+bool fromJSON(const llvm::json::Value &, ScopesArguments &, llvm::json::Path);
+
+struct ScopesResponseBody {
+  std::vector<Scope> scopes;
+};
+llvm::json::Value toJSON(const ScopesResponseBody &);
+
 /// Arguments for `source` request.
 struct SourceArguments {
   /// Specifies the source content to load. Either `source.path` or

diff  --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index 857503b3a0084..ce7519e3b16b8 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -16,17 +16,18 @@ using namespace llvm;
 
 namespace lldb_dap::protocol {
 
-bool fromJSON(const json::Value &Params, PresentationHint &PH, json::Path P) {
+bool fromJSON(const json::Value &Params, Source::PresentationHint &PH,
+              json::Path P) {
   auto rawHint = Params.getAsString();
   if (!rawHint) {
     P.report("expected a string");
     return false;
   }
-  std::optional<PresentationHint> hint =
-      StringSwitch<std::optional<PresentationHint>>(*rawHint)
-          .Case("normal", ePresentationHintNormal)
-          .Case("emphasize", ePresentationHintEmphasize)
-          .Case("deemphasize", ePresentationHintDeemphasize)
+  std::optional<Source::PresentationHint> hint =
+      StringSwitch<std::optional<Source::PresentationHint>>(*rawHint)
+          .Case("normal", Source::eSourcePresentationHintNormal)
+          .Case("emphasize", Source::eSourcePresentationHintEmphasize)
+          .Case("deemphasize", Source::eSourcePresentationHintDeemphasize)
           .Default(std::nullopt);
   if (!hint) {
     P.report("unexpected value");
@@ -43,13 +44,13 @@ bool fromJSON(const json::Value &Params, Source &S, json::Path P) {
          O.map("sourceReference", S.sourceReference);
 }
 
-llvm::json::Value toJSON(PresentationHint hint) {
+llvm::json::Value toJSON(Source::PresentationHint hint) {
   switch (hint) {
-  case ePresentationHintNormal:
+  case Source::eSourcePresentationHintNormal:
     return "normal";
-  case ePresentationHintEmphasize:
+  case Source::eSourcePresentationHintEmphasize:
     return "emphasize";
-  case ePresentationHintDeemphasize:
+  case Source::eSourcePresentationHintDeemphasize:
     return "deemphasize";
   }
   llvm_unreachable("unhandled presentation hint.");
@@ -435,6 +436,90 @@ json::Value toJSON(const Capabilities &C) {
   return result;
 }
 
+bool fromJSON(const json::Value &Params, Scope::PresentationHint &PH,
+              json::Path P) {
+  auto rawHint = Params.getAsString();
+  if (!rawHint) {
+    P.report("expected a string");
+    return false;
+  }
+  const std::optional<Scope::PresentationHint> hint =
+      StringSwitch<std::optional<Scope::PresentationHint>>(*rawHint)
+          .Case("arguments", Scope::eScopePresentationHintArguments)
+          .Case("locals", Scope::eScopePresentationHintLocals)
+          .Case("registers", Scope::eScopePresentationHintRegisters)
+          .Case("returnValue", Scope::eScopePresentationHintReturnValue)
+          .Default(std::nullopt);
+  if (!hint) {
+    P.report("unexpected value");
+    return false;
+  }
+  PH = *hint;
+  return true;
+}
+
+bool fromJSON(const json::Value &Params, Scope &S, json::Path P) {
+  json::ObjectMapper O(Params, P);
+  return O && O.map("name", S.name) &&
+         O.mapOptional("presentationHint", S.presentationHint) &&
+         O.map("variablesReference", S.variablesReference) &&
+         O.mapOptional("namedVariables", S.namedVariables) &&
+         O.map("indexedVariables", S.indexedVariables) &&
+         O.mapOptional("source", S.source) && O.map("expensive", S.expensive) &&
+         O.mapOptional("line", S.line) && O.mapOptional("column", S.column) &&
+         O.mapOptional("endLine", S.endLine) &&
+         O.mapOptional("endColumn", S.endColumn);
+}
+
+llvm::json::Value toJSON(const Scope &SC) {
+  llvm::json::Object result{{"name", SC.name},
+                            {"variablesReference", SC.variablesReference},
+                            {"expensive", SC.expensive}};
+
+  if (SC.presentationHint.has_value()) {
+    llvm::StringRef presentationHint;
+    switch (*SC.presentationHint) {
+    case Scope::eScopePresentationHintArguments:
+      presentationHint = "arguments";
+      break;
+    case Scope::eScopePresentationHintLocals:
+      presentationHint = "locals";
+      break;
+    case Scope::eScopePresentationHintRegisters:
+      presentationHint = "registers";
+      break;
+    case Scope::eScopePresentationHintReturnValue:
+      presentationHint = "returnValue";
+      break;
+    }
+
+    result.insert({"presentationHint", presentationHint});
+  }
+
+  if (SC.namedVariables.has_value())
+    result.insert({"namedVariables", SC.namedVariables});
+
+  if (SC.indexedVariables.has_value())
+    result.insert({"indexedVariables", SC.indexedVariables});
+
+  if (SC.source.has_value())
+    result.insert({"source", SC.source});
+
+  if (SC.line.has_value())
+    result.insert({"line", SC.line});
+
+  if (SC.column.has_value())
+    result.insert({"column", SC.column});
+
+  if (SC.endLine.has_value())
+    result.insert({"endLine", SC.endLine});
+
+  if (SC.endColumn.has_value())
+    result.insert({"endColumn", SC.endColumn});
+
+  return result;
+}
+
 bool fromJSON(const llvm::json::Value &Params, Capabilities &C,
               llvm::json::Path P) {
   auto *Object = Params.getAsObject();

diff  --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 757037a7b6ed2..3df77ee7374a7 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -27,6 +27,8 @@
 #include <optional>
 #include <string>
 
+#define LLDB_DAP_INVALID_VARRERF UINT64_MAX
+
 namespace lldb_dap::protocol {
 
 /// An `ExceptionBreakpointsFilter` is shown in the UI as an filter option for
@@ -283,18 +285,16 @@ struct Capabilities {
 bool fromJSON(const llvm::json::Value &, Capabilities &, llvm::json::Path);
 llvm::json::Value toJSON(const Capabilities &);
 
-enum PresentationHint : unsigned {
-  ePresentationHintNormal,
-  ePresentationHintEmphasize,
-  ePresentationHintDeemphasize
-};
-bool fromJSON(const llvm::json::Value &, PresentationHint &, llvm::json::Path);
-llvm::json::Value toJSON(PresentationHint hint);
-
 /// A `Source` is a descriptor for source code. It is returned from the debug
 /// adapter as part of a `StackFrame` and it is used by clients when specifying
 /// breakpoints.
 struct Source {
+  enum PresentationHint : unsigned {
+    eSourcePresentationHintNormal,
+    eSourcePresentationHintEmphasize,
+    eSourcePresentationHintDeemphasize,
+  };
+
   /// The short name of the source. Every source returned from the debug adapter
   /// has a name. When sending a source to the debug adapter this name is
   /// optional.
@@ -318,9 +318,82 @@ struct Source {
 
   // unsupported keys: origin, sources, adapterData, checksums
 };
+bool fromJSON(const llvm::json::Value &, Source::PresentationHint &,
+              llvm::json::Path);
+llvm::json::Value toJSON(Source::PresentationHint);
 bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path);
 llvm::json::Value toJSON(const Source &);
 
+/// A `Scope` is a named container for variables. Optionally a scope can map to
+/// a source or a range within a source.
+struct Scope {
+  enum PresentationHint : unsigned {
+    eScopePresentationHintArguments,
+    eScopePresentationHintLocals,
+    eScopePresentationHintRegisters,
+    eScopePresentationHintReturnValue
+  };
+  /// Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This
+  /// string is shown in the UI as is and can be translated.
+  ////
+  std::string name;
+
+  /// A hint for how to present this scope in the UI. If this attribute is
+  /// missing, the scope is shown with a generic UI.
+  /// Values:
+  /// 'arguments': Scope contains method arguments.
+  /// 'locals': Scope contains local variables.
+  /// 'registers': Scope contains registers. Only a single `registers` scope
+  /// should be returned from a `scopes` request.
+  /// 'returnValue': Scope contains one or more return values.
+  /// etc.
+  std::optional<PresentationHint> presentationHint;
+
+  /// The variables of this scope can be retrieved by passing the value of
+  /// `variablesReference` to the `variables` request as long as execution
+  /// remains suspended. See 'Lifetime of Object References' in the Overview
+  /// section for details.
+  ////
+  uint64_t variablesReference = LLDB_DAP_INVALID_VARRERF;
+
+  /// The number of named variables in this scope.
+  /// The client can use this information to present the variables in a paged UI
+  /// and fetch them in chunks.
+  std::optional<uint64_t> namedVariables;
+
+  /// The number of indexed variables in this scope.
+  /// The client can use this information to present the variables in a paged UI
+  /// and fetch them in chunks.
+  std::optional<uint64_t> indexedVariables;
+
+  /// The source for this scope.
+  std::optional<Source> source;
+
+  /// If true, the number of variables in this scope is large or expensive to
+  /// retrieve.
+  bool expensive = false;
+
+  /// The start line of the range covered by this scope.
+  std::optional<uint64_t> line;
+
+  /// Start position of the range covered by the scope. It is measured in UTF-16
+  /// code units and the client capability `columnsStartAt1` determines whether
+  /// it is 0- or 1-based.
+  std::optional<uint64_t> column;
+
+  /// The end line of the range covered by this scope.
+  std::optional<uint64_t> endLine;
+
+  /// End position of the range covered by the scope. It is measured in UTF-16
+  /// code units and the client capability `columnsStartAt1` determines whether
+  /// it is 0- or 1-based.
+  std::optional<uint64_t> endColumn;
+};
+bool fromJSON(const llvm::json::Value &Params, Scope::PresentationHint &PH,
+              llvm::json::Path);
+bool fromJSON(const llvm::json::Value &, Scope &, llvm::json::Path);
+llvm::json::Value toJSON(const Scope &);
+
 /// The granularity of one `step` in the stepping requests `next`, `stepIn`,
 /// `stepOut` and `stepBack`.
 enum SteppingGranularity : unsigned {

diff  --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index d97bbaffa2bc0..0c119bdb544d8 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -50,7 +50,7 @@ TEST(ProtocolTypesTest, Source) {
   source.name = "testName";
   source.path = "/path/to/source";
   source.sourceReference = 12345;
-  source.presentationHint = ePresentationHintEmphasize;
+  source.presentationHint = Source::eSourcePresentationHintEmphasize;
 
   llvm::Expected<Source> deserialized_source = roundtrip(source);
   ASSERT_THAT_EXPECTED(deserialized_source, llvm::Succeeded());
@@ -101,8 +101,8 @@ TEST(ProtocolTypesTest, Breakpoint) {
   breakpoint.id = 42;
   breakpoint.verified = true;
   breakpoint.message = "Breakpoint set successfully";
-  breakpoint.source =
-      Source{"test.cpp", "/path/to/test.cpp", 123, ePresentationHintNormal};
+  breakpoint.source = Source{"test.cpp", "/path/to/test.cpp", 123,
+                             Source::eSourcePresentationHintNormal};
   breakpoint.line = 10;
   breakpoint.column = 5;
   breakpoint.endLine = 15;
@@ -292,12 +292,53 @@ TEST(ProtocolTypesTest, Capabilities) {
             deserialized_capabilities->lldbExtVersion);
 }
 
+TEST(ProtocolTypesTest, Scope) {
+  Scope scope;
+  scope.name = "Locals";
+  scope.presentationHint = Scope::eScopePresentationHintLocals;
+  scope.variablesReference = 1;
+  scope.namedVariables = 2;
+  scope.indexedVariables = std::nullopt;
+  scope.expensive = false;
+  scope.line = 2;
+  scope.column = 3;
+  scope.endLine = 10;
+  scope.endColumn = 20;
+
+  scope.source =
+      Source{.name = "testName",
+             .path = "/path/to/source",
+             .sourceReference = 12345,
+             .presentationHint = Source::eSourcePresentationHintNormal};
+
+  llvm::Expected<Scope> deserialized_scope = roundtrip(scope);
+  ASSERT_THAT_EXPECTED(deserialized_scope, llvm::Succeeded());
+  EXPECT_EQ(scope.name, deserialized_scope->name);
+  EXPECT_EQ(scope.presentationHint, deserialized_scope->presentationHint);
+  EXPECT_EQ(scope.variablesReference, deserialized_scope->variablesReference);
+  EXPECT_EQ(scope.namedVariables, deserialized_scope->namedVariables);
+  EXPECT_EQ(scope.indexedVariables, deserialized_scope->indexedVariables);
+  EXPECT_EQ(scope.expensive, deserialized_scope->expensive);
+  EXPECT_EQ(scope.line, deserialized_scope->line);
+  EXPECT_EQ(scope.column, deserialized_scope->column);
+  EXPECT_EQ(scope.endLine, deserialized_scope->endLine);
+  EXPECT_EQ(scope.endColumn, deserialized_scope->endColumn);
+
+  EXPECT_THAT(deserialized_scope->source.has_value(), true);
+  const Source &source = scope.source.value();
+  const Source &deserialized_source = deserialized_scope->source.value();
+
+  EXPECT_EQ(source.path, deserialized_source.path);
+  EXPECT_EQ(source.sourceReference, deserialized_source.sourceReference);
+  EXPECT_EQ(source.presentationHint, deserialized_source.presentationHint);
+}
+
 TEST(ProtocolTypesTest, PresentationHint) {
   // Test all PresentationHint values.
-  std::vector<std::pair<PresentationHint, llvm::StringRef>> test_cases = {
-      {ePresentationHintNormal, "normal"},
-      {ePresentationHintEmphasize, "emphasize"},
-      {ePresentationHintDeemphasize, "deemphasize"}};
+  std::vector<std::pair<Source::PresentationHint, llvm::StringRef>> test_cases =
+      {{Source::eSourcePresentationHintNormal, "normal"},
+       {Source::eSourcePresentationHintEmphasize, "emphasize"},
+       {Source::eSourcePresentationHintDeemphasize, "deemphasize"}};
 
   for (const auto &test_case : test_cases) {
     // Serialize the PresentationHint to JSON.
@@ -306,7 +347,7 @@ TEST(ProtocolTypesTest, PresentationHint) {
     EXPECT_EQ(serialized.getAsString(), test_case.second);
 
     // Deserialize the JSON back to PresentationHint.
-    PresentationHint deserialized;
+    Source::PresentationHint deserialized;
     llvm::json::Path::Root root;
     ASSERT_TRUE(fromJSON(serialized, deserialized, root))
         << llvm::toString(root.getError());
@@ -315,7 +356,7 @@ TEST(ProtocolTypesTest, PresentationHint) {
 
   // Test invalid value.
   llvm::json::Value invalid_value = "invalid_hint";
-  PresentationHint deserialized_invalid;
+  Source::PresentationHint deserialized_invalid;
   llvm::json::Path::Root root;
   EXPECT_FALSE(fromJSON(invalid_value, deserialized_invalid, root));
 }


        


More information about the lldb-commits mailing list