Fix LLDB data formatter for llvm::Expected<T> with non-reference types (#179294)

This patch fixes LLDB data formatter support for llvm::Expected<T> with
the following changes:

llvm/utils/lldbDataFormatters.py: Fix ExpectedSynthProvider to handle
non-templated storage types (e.g., int, int*). Previously the formatter
only worked with templated storage types like std::reference_wrapper<T>.

cross-project-tests/lit.cfg.py:

Fix get_lldb_version_string() to use locally-built LLDB on non-Darwin
platforms instead of system LLDB
Fix minimum version from "1900" to "19.0.0" (typo in original code)
New test files: Added expected.cpp and expected.test to test the
formatter with Expected<int> and Expected<int*>.

---------

Co-authored-by: Jeffrey Tan <jeffreytan@fb.com>
This commit is contained in:
jeffreytan81 2026-02-09 19:23:40 -08:00 committed by GitHub
parent 9eca0a3b8b
commit d6ae568d58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 117 additions and 5 deletions

View File

@ -10,11 +10,13 @@ macro(add_lldb_test target source)
endmacro()
add_lldb_test(check-lldb-llvm-support-arrayref arrayref.cpp)
add_lldb_test(check-lldb-llvm-support-expected expected.cpp)
add_lldb_test(check-lldb-llvm-support-pointer-int-pair pointer-int-pair.cpp)
add_lldb_test(check-lldb-llvm-support-pointer-union pointer-union.cpp)
set(LLDB_FORMATTER_TESTS
check-lldb-llvm-support-arrayref
check-lldb-llvm-support-expected
check-lldb-llvm-support-pointer-int-pair
check-lldb-llvm-support-pointer-union
PARENT_SCOPE)

View File

@ -0,0 +1,36 @@
// Test llvm::Expected<T> data formatters.
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Error.h"
#include <cstdio>
using namespace llvm;
int main() {
// Test primitive type (storage is T directly).
Expected<int> ExpectedInt = 42;
(void)static_cast<bool>(ExpectedInt);
// Test pointer type (storage is T* directly).
int x = 10;
Expected<int *> ExpectedPtr = &x;
(void)static_cast<bool>(ExpectedPtr);
// Test reference type (storage is std::reference_wrapper<T>).
int y = 100;
Expected<int &> ExpectedRef = y;
(void)static_cast<bool>(ExpectedRef);
// Test templated type (storage is the template type directly).
Expected<SmallVector<int, 2>> ExpectedVec = SmallVector<int, 2>{1, 2};
(void)static_cast<bool>(ExpectedVec);
// Test templated reference type (storage is std::reference_wrapper<T>).
SmallVector<int, 2> vec{3, 4};
Expected<SmallVector<int, 2> &> ExpectedVecRef = vec;
(void)static_cast<bool>(ExpectedVecRef);
puts("Break here");
return 0;
}

View File

@ -0,0 +1,52 @@
# REQUIRES: lldb-formatters-compatibility
#
# RUN: split-file %s %t
# RUN: lldb -x \
# RUN: -o 'command script import %llvm_src_root/utils/lldbDataFormatters.py' \
# RUN: -s %t/commands.input %llvm_tools_dir/check-lldb-llvm-support-expected \
# RUN: -o quit \
# RUN: | FileCheck %t/checks
#--- commands.input
break set -p "Break here"
run
v -T ExpectedInt
v -T ExpectedPtr
v -T ExpectedRef
v -T ExpectedVec
v -T ExpectedVecRef
#--- checks
# CHECK: (lldb) v -T ExpectedInt
# CHECK-NEXT: (llvm::Expected<int>) ExpectedInt = {
# CHECK-NEXT: (llvm::Expected<int>::storage_type) value = 42
# CHECK-NEXT: }
# CHECK: (lldb) v -T ExpectedPtr
# CHECK-NEXT: (llvm::Expected<int *>) ExpectedPtr = {
# CHECK-NEXT: (llvm::Expected<int *>::storage_type) value = 0x{{[0-9a-fA-F]+}}
# CHECK-NEXT: }
# Reference types are unwrapped from std::reference_wrapper<T> to T.
# CHECK: (lldb) v -T ExpectedRef
# CHECK-NEXT: (llvm::Expected<int &>) ExpectedRef = {
# CHECK-NEXT: (int) value = 100
# CHECK-NEXT: }
# CHECK: (lldb) v -T ExpectedVec
# CHECK-NEXT: (llvm::Expected<llvm::SmallVector<int, 2> >) ExpectedVec = {
# CHECK-NEXT: (llvm::Expected<llvm::SmallVector<int, 2> >::storage_type) value = size=2 {
# CHECK-NEXT: (int) [0] = 1
# CHECK-NEXT: (int) [1] = 2
# CHECK-NEXT: }
# CHECK-NEXT: }
# Templated reference types are also unwrapped from std::reference_wrapper<T> to T.
# CHECK: (lldb) v -T ExpectedVecRef
# CHECK-NEXT: (llvm::Expected<llvm::SmallVector<int, 2> &>) ExpectedVecRef = {
# CHECK-NEXT: (llvm::SmallVector<int, 2>) value = size=2 {
# CHECK-NEXT: (int) [0] = 3
# CHECK-NEXT: (int) [1] = 4
# CHECK-NEXT: }
# CHECK-NEXT: }

View File

@ -285,9 +285,16 @@ def get_lldb_version_string():
--version output is formatted unexpectedly.
"""
try:
cmd = ["lldb", "--version"]
if platform.system() == "Darwin":
cmd = ["xcrun"] + cmd
# On Darwin, use system lldb which has Apple-specific versioning.
cmd = ["xcrun", "lldb", "--version"]
else:
# On non-Darwin, use the locally-built lldb from llvm_tools_dir.
lldb_path = os.path.join(config.llvm_tools_dir, "lldb")
if not os.path.exists(lldb_path):
print(f"LLDB not found at {lldb_path}", file=sys.stderr)
return None
cmd = [lldb_path, "--version"]
lldb_vers_lines = subprocess.check_output(cmd).decode().splitlines()
except:
@ -311,7 +318,9 @@ def set_lldb_formatters_compatibility_feature():
# The Apple LLDB version doesn't follow the LLVM release versioning.
min_required_lldb_version = "1700"
else:
min_required_lldb_version = "1900"
# Minimum version required for SBType::FindDirectNestedType API
# which some LLVM data formatters depend on.
min_required_lldb_version = "19.0.0"
try:
from packaging import version

View File

@ -488,8 +488,21 @@ class ExpectedSynthetic:
# Anonymous union.
union = self.expected.child[0]
storage = union.GetChildMemberWithName(member)
stored_type = storage.type.template_args[0]
self.stored_value = storage.Cast(stored_type).Clone(name)
# For reference types, storage is std::reference_wrapper<T>, so we
# unwrap to get T. For non-reference types, storage is T directly.
# Use GetCanonicalType() to resolve the typedef to the underlying type.
canonical_type_name = storage.type.GetCanonicalType().name
if "reference_wrapper<" in canonical_type_name:
# reference_wrapper<T> stores a T* pointer to the referenced value.
# Get the first child (the pointer member) and dereference it.
ptr_member = storage.GetChildAtIndex(0)
if ptr_member and ptr_member.IsValid() and ptr_member.type.IsPointerType():
self.stored_value = ptr_member.Dereference().Clone(name)
else:
# Fallback: just use storage as-is.
self.stored_value = storage.Clone(name)
else:
self.stored_value = storage.Clone(name)
def num_children(self) -> int:
return 1