Author: Daedie-git
Date: 2026-08-21T21:57:59+02:00
New Revision: 9a9b50168182a0c51b2e8e12735d424a019932c2

URL: 
https://github.com/llvm/llvm-project/commit/9a9b50168182a0c51b2e8e12735d424a019932c2
DIFF: 
https://github.com/llvm/llvm-project/commit/9a9b50168182a0c51b2e8e12735d424a019932c2.diff

LOG: [lldb] Add MSVC STL formatter for std::expected (#217243)

Pretty-print MSVC STL `std::expected<T, E>` and `expected<void, E>`.
Engaged values show `Value`; unexpected alternatives show `Unexpected`.
The anonymous union is walked the same way as `optional` so PDB debug
info still finds `_Value` / `_Unexpected`.

Tests: generic C++23 MSVC STL coverage for engaged, unexpected, void,
and reference cases using PDB debug info.

Part of #24834

Assisted-by: Grok 4.6
Assisted-by: codex-5.6-high

---------

Co-authored-by: Bjorn Schobben <[email protected]>

Added: 
    lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
    
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
    
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/TestDataFormatterStdExpected.py
    
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/main.cpp

Modified: 
    lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
    lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
    lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt 
b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index c34f63637dbb4..1a4717fe5fe9c 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -43,6 +43,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   MsvcStl.cpp
   MsvcStlAtomic.cpp
   MsvcStlDeque.cpp
+  MsvcStlExpected.cpp
   MsvcStlSmartPointer.cpp
   MsvcStlSpan.cpp
   MsvcStlTree.cpp

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp 
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 1ee1de2bef2ff..ca5aeed182dda 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1736,6 +1736,23 @@ 
GenericBitsetSyntheticFrontEndCreator(CXXSyntheticChildren *children,
   return LibStdcppBitsetSyntheticFrontEndCreator(children, valobj_sp);
 }
 
+static SyntheticChildrenFrontEnd *
+GenericExpectedSyntheticFrontEndCreator(CXXSyntheticChildren *children,
+                                        lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+  if (IsMsvcStlExpected(*valobj_sp))
+    return MsvcStlExpectedSyntheticFrontEndCreator(children, valobj_sp);
+  return nullptr;
+}
+
+static bool GenericExpectedSummaryProvider(ValueObject &valobj, Stream &stream,
+                                           const TypeSummaryOptions &options) {
+  if (IsMsvcStlExpected(valobj))
+    return MsvcStlExpectedSummaryProvider(valobj, stream, options);
+  return false;
+}
+
 /// Load formatters that are formatting types from more than one STL
 static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   if (!cpp_category_sp)
@@ -2000,6 +2017,13 @@ static void 
LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
                 "MSVC STL/libstdc++ std::bitset summary provider",
                 "^std::bitset<.+>(( )?&)?$", stl_summary_flags, true);
+
+  AddCXXSynthetic(cpp_category_sp, GenericExpectedSyntheticFrontEndCreator,
+                  "MSVC STL std::expected synthetic children",
+                  "^std::expected<.+>(( )?&)?$", stl_deref_flags, true);
+  AddCXXSummary(cpp_category_sp, GenericExpectedSummaryProvider,
+                "MSVC STL std::expected summary provider",
+                "^std::expected<.+>(( )?&)?$", stl_summary_flags, true);
 }
 
 static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h 
b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
index 7e2657a1a37c1..b1f6fce2b1da4 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -151,6 +151,14 @@ SyntheticChildrenFrontEnd *
 MsvcStlBitsetSyntheticFrontEndCreator(CXXSyntheticChildren *,
                                       lldb::ValueObjectSP valobj_sp);
 
+// MSVC STL std::expected<>
+bool IsMsvcStlExpected(ValueObject &valobj);
+bool MsvcStlExpectedSummaryProvider(ValueObject &valobj, Stream &stream,
+                                    const TypeSummaryOptions &options);
+SyntheticChildrenFrontEnd *
+MsvcStlExpectedSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                        lldb::ValueObjectSP valobj_sp);
+
 } // namespace formatters
 } // namespace lldb_private
 

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp 
b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
new file mode 100644
index 0000000000000..e122fadb1ec76
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MsvcStl.h"
+
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "llvm/Support/ErrorExtras.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::formatters;
+
+namespace {
+
+class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
+public:
+  MsvcStlExpectedFrontend(ValueObject &valobj)
+      : SyntheticChildrenFrontEnd(valobj) {
+    if (valobj.GetTargetSP())
+      Update();
+  }
+
+  llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
+    if (!m_active)
+      return llvm::createStringErrorV("type has no child named '{0}'", name);
+    if (m_has_value) {
+      if (name == "Value" || name == "$$dereference$$")
+        return 0;
+    } else if (name == "Unexpected") {
+      return 0;
+    }
+    return llvm::createStringErrorV("type has no child named '{0}'", name);
+  }
+
+  llvm::Expected<uint32_t> CalculateNumChildren() override {
+    return m_active ? 1U : 0U;
+  }
+
+  ValueObjectSP GetChildAtIndex(uint32_t idx) override {
+    if (!m_active || idx != 0)
+      return {};
+    return m_active->Clone(m_has_value ? "Value" : "Unexpected");
+  }
+
+  lldb::ChildCacheState Update() override {
+    m_active = nullptr;
+    m_has_value = false;
+    ValueObjectSP ns = m_backend.GetNonSyntheticValue();
+    if (!ns)
+      return lldb::ChildCacheState::eRefetch;
+
+    ValueObjectSP has_sp = ns->GetChildMemberWithName("_Has_value");
+    if (!has_sp)
+      return lldb::ChildCacheState::eRefetch;
+
+    m_has_value = has_sp->GetValueAsUnsigned(0) != 0;
+    // expected<void, E> has no value child when engaged.
+    m_active =
+        ns->GetChildMemberWithName(m_has_value ? "_Value" : "_Unexpected")
+            .get();
+    return lldb::ChildCacheState::eRefetch;
+  }
+
+private:
+  ValueObject *m_active = nullptr;
+  bool m_has_value = false;
+};
+} // namespace
+
+bool formatters::IsMsvcStlExpected(ValueObject &valobj) {
+  if (auto valobj_sp = valobj.GetNonSyntheticValue())
+    return valobj_sp->GetChildMemberWithName("_Has_value") != nullptr;
+  return false;
+}
+
+bool formatters::MsvcStlExpectedSummaryProvider(ValueObject &valobj,
+                                                Stream &stream,
+                                                const TypeSummaryOptions &) {
+  ValueObjectSP ns = valobj.GetNonSyntheticValue();
+  if (!ns)
+    return false;
+  ValueObjectSP has_sp = ns->GetChildMemberWithName("_Has_value");
+  if (!has_sp)
+    return false;
+  // Keep the summary consistent with std::optional.
+  stream.Printf(" Has Value=%s ",
+                has_sp->GetValueAsUnsigned(0) ? "true" : "false");
+  return true;
+}
+
+SyntheticChildrenFrontEnd *formatters::MsvcStlExpectedSyntheticFrontEndCreator(
+    CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
+  if (valobj_sp)
+    return new MsvcStlExpectedFrontend(*valobj_sp);
+  return nullptr;
+}

diff  --git 
a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
new file mode 100644
index 0000000000000..96c46af546658
--- /dev/null
+++ 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+CXXFLAGS_EXTRAS := -std=c++23
+
+include Makefile.rules

diff  --git 
a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/TestDataFormatterStdExpected.py
 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/TestDataFormatterStdExpected.py
new file mode 100644
index 0000000000000..c465f8998d6c4
--- /dev/null
+++ 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/TestDataFormatterStdExpected.py
@@ -0,0 +1,46 @@
+"""Test the MSVC STL std::expected formatter."""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class StdExpectedDataFormatterTestCase(TestBase):
+    SHARED_BUILD_TESTCASE = False
+    TEST_WITH_PDB_DEBUG_INFO = True
+
+    @add_test_categories(["msvcstl"])
+    def test_msvcstl(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+
+        self.expect_var_path(
+            "ok",
+            summary=" Has Value=true ",
+            children=[ValueCheck(name="Value", value="7")],
+        )
+        self.expect_var_path(
+            "err",
+            summary=" Has Value=false ",
+            children=[ValueCheck(name="Unexpected", value="42")],
+        )
+        self.expect_var_path("void_ok", summary=" Has Value=true ", 
children=[])
+        self.expect_var_path(
+            "void_err",
+            summary=" Has Value=false ",
+            children=[ValueCheck(name="Unexpected", value="11")],
+        )
+
+        self.expect_var_path(
+            "ok_ref",
+            summary=" Has Value=true ",
+            children=[ValueCheck(name="Value", value="7")],
+        )
+        self.expect_var_path(
+            "err_ref",
+            summary=" Has Value=false ",
+            children=[ValueCheck(name="Unexpected", value="42")],
+        )

diff  --git 
a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/main.cpp
 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/main.cpp
new file mode 100644
index 0000000000000..5079781c1017b
--- /dev/null
+++ 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/main.cpp
@@ -0,0 +1,12 @@
+#include <expected>
+
+int main() {
+  std::expected<int, int> ok = 7;
+  std::expected<int, int> err = std::unexpected(42);
+  std::expected<void, int> void_ok;
+  std::expected<void, int> void_err = std::unexpected(11);
+  std::expected<int, int> &ok_ref = ok;
+  std::expected<int, int> &err_ref = err;
+
+  return ok.value(); // break here
+}


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to