[lldb-dap] Emit declarations along with variables (#74865)

This is an extension to the protocol that emits the declaration
information along with the metadata of each variable. This can be used
by vscode extensions to implement, for example, a "goToDefinition"
action in the debug tab, or for showing the value of a variable right
next to where it's declared during a debug session.
As this is cheap, I'm not gating this information under any setting.

GitOrigin-RevId: 0ea19bd3333af71dd3aaf7c0a6ef9a0930958c12
diff --git a/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index d2a21ad..9b0755e 100644
--- a/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -4,8 +4,8 @@
 
 import os
 
-import lldbdap_testcase
 import dap_server
+import lldbdap_testcase
 from lldbsuite.test import lldbutil
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
@@ -152,7 +152,13 @@
         globals = self.dap_server.get_global_variables()
         buffer_children = make_buffer_verify_dict(0, 32)
         verify_locals = {
-            "argc": {"equals": {"type": "int", "value": "1"}},
+            "argc": {
+                "equals": {"type": "int", "value": "1"},
+                "declaration": {
+                    "equals": {"line": 12, "column": 14},
+                    "contains": {"path": ["lldb-dap", "variables", "main.cpp"]},
+                },
+            },
             "argv": {
                 "equals": {"type": "const char **"},
                 "startswith": {"value": "0x"},
diff --git a/tools/lldb-dap/JSONUtils.cpp b/tools/lldb-dap/JSONUtils.cpp
index 62d7fa5..c8e5304 100644
--- a/tools/lldb-dap/JSONUtils.cpp
+++ b/tools/lldb-dap/JSONUtils.cpp
@@ -1103,6 +1103,29 @@
 //                       can use this optional information to present the
 //                       children in a paged UI and fetch them in chunks."
 //     }
+//     "declaration": {
+//       "type": "object | undefined",
+//       "description": "Extension to the protocol that indicates the source
+//                       location where the variable was declared. This value
+//                       might not be present if no declaration is available.",
+//       "properties": {
+//         "path": {
+//           "type": "string | undefined",
+//           "description": "The source file path where the variable was
+//                           declared."
+//         },
+//         "line": {
+//           "type": "number | undefined",
+//           "description": "The 1-indexed source line where the variable was
+//                          declared."
+//         },
+//         "column": {
+//           "type": "number | undefined",
+//           "description": "The 1-indexed source column where the variable was
+//                          declared."
+//         }
+//       }
+//     }
 //   },
 //   "required": [ "name", "value", "variablesReference" ]
 // }
@@ -1167,6 +1190,24 @@
   const char *evaluateName = evaluateStream.GetData();
   if (evaluateName && evaluateName[0])
     EmplaceSafeString(object, "evaluateName", std::string(evaluateName));
+
+  if (lldb::SBDeclaration decl = v.GetDeclaration(); decl.IsValid()) {
+    llvm::json::Object decl_obj;
+    if (lldb::SBFileSpec file = decl.GetFileSpec(); file.IsValid()) {
+      char path[PATH_MAX] = "";
+      if (file.GetPath(path, sizeof(path)) &&
+          lldb::SBFileSpec::ResolvePath(path, path, PATH_MAX)) {
+        decl_obj.try_emplace("path", std::string(path));
+      }
+    }
+
+    if (int line = decl.GetLine())
+      decl_obj.try_emplace("line", line);
+    if (int column = decl.GetColumn())
+      decl_obj.try_emplace("column", column);
+
+    object.try_emplace("declaration", std::move(decl_obj));
+  }
   return llvm::json::Value(std::move(object));
 }