[Lldb-commits] [lldb] [lldb] Add MSVC STL formatter for std::expected (PR #217243)

via lldb-commits lldb-commits at lists.llvm.org
Fri Aug 21 09:05:34 PDT 2026


https://github.com/Daedie-git updated https://github.com/llvm/llvm-project/pull/217243

>From 754120d02038acbd04ce45bb9c2a6914950b94b5 Mon Sep 17 00:00:00 2001
From: Bjorn Schobben <bjorn.schobben at aimsport.com>
Date: Wed, 19 Aug 2026 10:55:43 +0200
Subject: [PATCH 1/4] [lldb] Add MSVC STL formatter for std::expected

Pretty-print engaged values and unexpected alternatives, including
expected<void, E>. Walk the anonymous union the same way as optional
so PDB debug info still finds _Value/_Unexpected.

Part of #24834
---
 .../Plugins/Language/CPlusPlus/CMakeLists.txt |   1 +
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  24 +++
 .../Plugins/Language/CPlusPlus/MsvcStl.h      |   8 +
 .../Language/CPlusPlus/MsvcStlExpected.cpp    | 137 ++++++++++++++++++
 .../msvc-simulators/expected/Makefile         |   3 +
 ...stDataFormatterMsvcStlExpectedSimulator.py |  36 +++++
 .../msvc-simulators/expected/main.cpp         |  32 ++++
 7 files changed, 241 insertions(+)
 create mode 100644 lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
 create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile
 create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
 create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp

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 30050e0878e6e..4e1a499594117 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1725,6 +1725,23 @@ GenericStrongOrderingSummaryProvider(ValueObject &valobj, Stream &stream,
   return LibStdcppStrongOrderingSummaryProvider(valobj, stream, options);
 }
 
+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)
@@ -1983,6 +2000,13 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                   "MSVC STL/libstdc++ std::error_code/error_condition "
                   "synthetic children",
                   "^std::error_(code|condition)$", stl_synth_flags, true);
+
+  AddCXXSynthetic(cpp_category_sp, GenericExpectedSyntheticFrontEndCreator,
+                  "MSVC STL std::expected synthetic children",
+                  "^std::expected<.+>(( )?&)?$", stl_synth_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 0bdd548dd99e3..3edb47ff800b6 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -142,6 +142,14 @@ SyntheticChildrenFrontEnd *
 MsvcStlSpanSyntheticFrontEndCreator(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..ac2bef91ea4b5
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
@@ -0,0 +1,137 @@
+//===-- MsvcStlExpected.cpp -----------------------------------------------===//
+//
+// 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 {
+
+/// `_Value` / `_Unexpected` live in an anonymous union. Direct
+/// GetChildMemberWithName from the class works on DWARF; PDB often needs the
+/// same parent/index walk GenericOptional uses for `_Value`.
+ValueObjectSP GetExpectedUnionMember(ValueObject &ns, const char *name) {
+  if (ValueObjectSP direct = ns.GetChildMemberWithName(name))
+    return direct;
+
+  ValueObjectSP has_sp = ns.GetChildMemberWithName("_Has_value");
+  if (!has_sp)
+    return {};
+  ValueObject *parent = has_sp->GetParent();
+  if (!parent)
+    return {};
+  ValueObjectSP first_sp = parent->GetChildAtIndex(0);
+  if (!first_sp)
+    return {};
+  if (ValueObjectSP nested = first_sp->GetChildMemberWithName(name))
+    return nested;
+  if (first_sp->GetName() == name)
+    return first_sp;
+  return {};
+}
+
+class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
+public:
+  MsvcStlExpectedFrontend(ValueObject &valobj)
+      : SyntheticChildrenFrontEnd(valobj) {
+    if (valobj.GetTargetSP())
+      Update();
+  }
+
+  llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
+    if (!m_engaged)
+      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_engaged ? 1U : 0U;
+  }
+
+  ValueObjectSP GetChildAtIndex(uint32_t idx) override {
+    if (!m_engaged || idx != 0)
+      return {};
+
+    ValueObjectSP ns = m_backend.GetNonSyntheticValue();
+    if (!ns)
+      return {};
+
+    if (m_has_value) {
+      if (ValueObjectSP val_sp = GetExpectedUnionMember(*ns, "_Value"))
+        return val_sp->Clone("Value");
+      return {};
+    }
+
+    if (ValueObjectSP err_sp = GetExpectedUnionMember(*ns, "_Unexpected"))
+      return err_sp->Clone("Unexpected");
+    return {};
+  }
+
+  lldb::ChildCacheState Update() override {
+    m_engaged = false;
+    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.
+    if (m_has_value)
+      m_engaged = GetExpectedUnionMember(*ns, "_Value") != nullptr;
+    else
+      m_engaged = GetExpectedUnionMember(*ns, "_Unexpected") != nullptr;
+    return lldb::ChildCacheState::eRefetch;
+  }
+
+private:
+  bool m_engaged = false;
+  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;
+  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/msvc-simulators/expected/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile
new file mode 100644
index 0000000000000..2698b0794a3ff
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+override CXXFLAGS_EXTRAS += -std=c++17
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
new file mode 100644
index 0000000000000..b2c349ca76ad0
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
@@ -0,0 +1,36 @@
+"""Simulate MSVC STL std::expected and check the formatter."""
+
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class MsvcStlExpectedSimulatorTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp")
+        )
+
+        ok = self.frame().FindVariable("ok")
+        self.assertEqual(ok.GetNumChildren(), 1)
+        self.assertEqual(ok.GetChildAtIndex(0).GetName(), "Value")
+        self.assertEqual(ok.GetChildAtIndex(0).GetValueAsSigned(), 7)
+        self.assertFalse(ok.GetChildMemberWithName("Unexpected").IsValid())
+        self.expect("frame variable ok", substrs=["Has Value=true", "Value = 7"])
+
+        err = self.frame().FindVariable("err")
+        self.assertEqual(err.GetNumChildren(), 1)
+        self.assertEqual(err.GetChildAtIndex(0).GetName(), "Unexpected")
+        self.assertFalse(err.GetChildMemberWithName("Value").IsValid())
+        self.expect("frame variable err", substrs=["Has Value=false", "boom"])
+
+        void_ok = self.frame().FindVariable("void_ok")
+        self.assertEqual(void_ok.GetNumChildren(), 0)
+        self.expect("frame variable void_ok", substrs=["Has Value=true"])
+
+        void_err = self.frame().FindVariable("void_err")
+        self.assertEqual(void_err.GetNumChildren(), 1)
+        self.assertEqual(void_err.GetChildAtIndex(0).GetValueAsSigned(), 11)
+        self.expect("frame variable void_err", substrs=["Has Value=false"])
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp
new file mode 100644
index 0000000000000..93f05bf2e514a
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp
@@ -0,0 +1,32 @@
+#include <stddef.h>
+
+namespace std {
+template <class T, class E> class expected {
+public:
+  expected(const T &value) : _Value(value), _Has_value(true) {}
+  expected(const E &error, bool) : _Unexpected(error), _Has_value(false) {}
+  union {
+    T _Value;
+    E _Unexpected;
+  };
+  bool _Has_value;
+};
+
+template <class E> class expected<void, E> {
+public:
+  expected() : _Has_value(true) {}
+  expected(const E &error, bool) : _Unexpected(error), _Has_value(false) {}
+  union {
+    E _Unexpected;
+  };
+  bool _Has_value;
+};
+} // namespace std
+
+int main() {
+  std::expected<int, const char *> ok(7);
+  std::expected<int, const char *> err("boom", true);
+  std::expected<void, int> void_ok;
+  std::expected<void, int> void_err(11, true);
+  return 0; // break here
+}

>From 0524631bf5918f1822835efb2f1dc1f4d19b5de1 Mon Sep 17 00:00:00 2001
From: Bjorn Schobben <bjorn.schobben at aimsport.com>
Date: Thu, 20 Aug 2026 19:25:12 +0200
Subject: [PATCH 2/4] [lldb] Test the MSVC expected formatter with the real STL

---
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  2 +-
 .../expected/Makefile                         |  3 +-
 .../expected/TestDataFormatterStdExpected.py  | 46 +++++++++++++++++++
 .../generic/expected/main.cpp                 | 12 +++++
 ...stDataFormatterMsvcStlExpectedSimulator.py | 36 ---------------
 .../msvc-simulators/expected/main.cpp         | 32 -------------
 6 files changed, 61 insertions(+), 70 deletions(-)
 rename lldb/test/API/functionalities/data-formatter/data-formatter-stl/{msvc-simulators => generic}/expected/Makefile (54%)
 create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/TestDataFormatterStdExpected.py
 create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/main.cpp
 delete mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
 delete mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp

diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 4e1a499594117..ec13c473449a4 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -2003,7 +2003,7 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
 
   AddCXXSynthetic(cpp_category_sp, GenericExpectedSyntheticFrontEndCreator,
                   "MSVC STL std::expected synthetic children",
-                  "^std::expected<.+>(( )?&)?$", stl_synth_flags, true);
+                  "^std::expected<.+>(( )?&)?$", stl_deref_flags, true);
   AddCXXSummary(cpp_category_sp, GenericExpectedSummaryProvider,
                 "MSVC STL std::expected summary provider",
                 "^std::expected<.+>(( )?&)?$", stl_summary_flags, true);
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
similarity index 54%
rename from lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile
rename to lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
index 2698b0794a3ff..96c46af546658 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/Makefile
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/expected/Makefile
@@ -1,3 +1,4 @@
 CXX_SOURCES := main.cpp
-override CXXFLAGS_EXTRAS += -std=c++17
+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
+}
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
deleted file mode 100644
index b2c349ca76ad0..0000000000000
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/TestDataFormatterMsvcStlExpectedSimulator.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""Simulate MSVC STL std::expected and check the formatter."""
-
-from lldbsuite.test.lldbtest import *
-from lldbsuite.test import lldbutil
-
-
-class MsvcStlExpectedSimulatorTestCase(TestBase):
-    NO_DEBUG_INFO_TESTCASE = True
-
-    def test(self):
-        self.build()
-        lldbutil.run_to_source_breakpoint(
-            self, "break here", lldb.SBFileSpec("main.cpp")
-        )
-
-        ok = self.frame().FindVariable("ok")
-        self.assertEqual(ok.GetNumChildren(), 1)
-        self.assertEqual(ok.GetChildAtIndex(0).GetName(), "Value")
-        self.assertEqual(ok.GetChildAtIndex(0).GetValueAsSigned(), 7)
-        self.assertFalse(ok.GetChildMemberWithName("Unexpected").IsValid())
-        self.expect("frame variable ok", substrs=["Has Value=true", "Value = 7"])
-
-        err = self.frame().FindVariable("err")
-        self.assertEqual(err.GetNumChildren(), 1)
-        self.assertEqual(err.GetChildAtIndex(0).GetName(), "Unexpected")
-        self.assertFalse(err.GetChildMemberWithName("Value").IsValid())
-        self.expect("frame variable err", substrs=["Has Value=false", "boom"])
-
-        void_ok = self.frame().FindVariable("void_ok")
-        self.assertEqual(void_ok.GetNumChildren(), 0)
-        self.expect("frame variable void_ok", substrs=["Has Value=true"])
-
-        void_err = self.frame().FindVariable("void_err")
-        self.assertEqual(void_err.GetNumChildren(), 1)
-        self.assertEqual(void_err.GetChildAtIndex(0).GetValueAsSigned(), 11)
-        self.expect("frame variable void_err", substrs=["Has Value=false"])
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp
deleted file mode 100644
index 93f05bf2e514a..0000000000000
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/msvc-simulators/expected/main.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include <stddef.h>
-
-namespace std {
-template <class T, class E> class expected {
-public:
-  expected(const T &value) : _Value(value), _Has_value(true) {}
-  expected(const E &error, bool) : _Unexpected(error), _Has_value(false) {}
-  union {
-    T _Value;
-    E _Unexpected;
-  };
-  bool _Has_value;
-};
-
-template <class E> class expected<void, E> {
-public:
-  expected() : _Has_value(true) {}
-  expected(const E &error, bool) : _Unexpected(error), _Has_value(false) {}
-  union {
-    E _Unexpected;
-  };
-  bool _Has_value;
-};
-} // namespace std
-
-int main() {
-  std::expected<int, const char *> ok(7);
-  std::expected<int, const char *> err("boom", true);
-  std::expected<void, int> void_ok;
-  std::expected<void, int> void_err(11, true);
-  return 0; // break here
-}

>From cb22297e91b9982c3537532d6f3d52fd819bf0e6 Mon Sep 17 00:00:00 2001
From: Bjorn Schobben <bjorn.schobben at aimsport.com>
Date: Thu, 20 Aug 2026 22:32:15 +0200
Subject: [PATCH 3/4] [lldb] Cache the active MSVC expected member

---
 .../Language/CPlusPlus/MsvcStlExpected.cpp    | 54 +++----------------
 1 file changed, 8 insertions(+), 46 deletions(-)

diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
index ac2bef91ea4b5..565573b7cd167 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
@@ -17,29 +17,6 @@ using namespace lldb_private::formatters;
 
 namespace {
 
-/// `_Value` / `_Unexpected` live in an anonymous union. Direct
-/// GetChildMemberWithName from the class works on DWARF; PDB often needs the
-/// same parent/index walk GenericOptional uses for `_Value`.
-ValueObjectSP GetExpectedUnionMember(ValueObject &ns, const char *name) {
-  if (ValueObjectSP direct = ns.GetChildMemberWithName(name))
-    return direct;
-
-  ValueObjectSP has_sp = ns.GetChildMemberWithName("_Has_value");
-  if (!has_sp)
-    return {};
-  ValueObject *parent = has_sp->GetParent();
-  if (!parent)
-    return {};
-  ValueObjectSP first_sp = parent->GetChildAtIndex(0);
-  if (!first_sp)
-    return {};
-  if (ValueObjectSP nested = first_sp->GetChildMemberWithName(name))
-    return nested;
-  if (first_sp->GetName() == name)
-    return first_sp;
-  return {};
-}
-
 class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
 public:
   MsvcStlExpectedFrontend(ValueObject &valobj)
@@ -49,7 +26,7 @@ class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
   }
 
   llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
-    if (!m_engaged)
+    if (!m_active_sp)
       return llvm::createStringErrorV("type has no child named '{0}'", name);
     if (m_has_value) {
       if (name == "Value" || name == "$$dereference$$")
@@ -61,30 +38,17 @@ class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
   }
 
   llvm::Expected<uint32_t> CalculateNumChildren() override {
-    return m_engaged ? 1U : 0U;
+    return m_active_sp ? 1U : 0U;
   }
 
   ValueObjectSP GetChildAtIndex(uint32_t idx) override {
-    if (!m_engaged || idx != 0)
+    if (!m_active_sp || idx != 0)
       return {};
-
-    ValueObjectSP ns = m_backend.GetNonSyntheticValue();
-    if (!ns)
-      return {};
-
-    if (m_has_value) {
-      if (ValueObjectSP val_sp = GetExpectedUnionMember(*ns, "_Value"))
-        return val_sp->Clone("Value");
-      return {};
-    }
-
-    if (ValueObjectSP err_sp = GetExpectedUnionMember(*ns, "_Unexpected"))
-      return err_sp->Clone("Unexpected");
-    return {};
+    return m_active_sp->Clone(m_has_value ? "Value" : "Unexpected");
   }
 
   lldb::ChildCacheState Update() override {
-    m_engaged = false;
+    m_active_sp.reset();
     m_has_value = false;
     ValueObjectSP ns = m_backend.GetNonSyntheticValue();
     if (!ns)
@@ -96,15 +60,13 @@ class MsvcStlExpectedFrontend : public SyntheticChildrenFrontEnd {
 
     m_has_value = has_sp->GetValueAsUnsigned(0) != 0;
     // expected<void, E> has no value child when engaged.
-    if (m_has_value)
-      m_engaged = GetExpectedUnionMember(*ns, "_Value") != nullptr;
-    else
-      m_engaged = GetExpectedUnionMember(*ns, "_Unexpected") != nullptr;
+    m_active_sp =
+        ns->GetChildMemberWithName(m_has_value ? "_Value" : "_Unexpected");
     return lldb::ChildCacheState::eRefetch;
   }
 
 private:
-  bool m_engaged = false;
+  ValueObjectSP m_active_sp;
   bool m_has_value = false;
 };
 } // namespace

>From 03bace66ebde7a24c51e8d036f4d7bd32160e99c Mon Sep 17 00:00:00 2001
From: Daedie-git <HC_Daedalus at hotmail.com>
Date: Fri, 21 Aug 2026 18:05:23 +0200
Subject: [PATCH 4/4] Update
 lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp

Co-authored-by: Nerixyz <nero.9 at hotmail.de>
---
 lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
index 565573b7cd167..7f3ccd2420435 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlExpected.cpp
@@ -1,4 +1,4 @@
-//===-- MsvcStlExpected.cpp -----------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.



More information about the lldb-commits mailing list