Revert "[lldb] Add support for using integral const static data members in the expression evaluator"

This reverts commit 486787210d.

This broke the windows lldb bot: https://lab.llvm.org/buildbot/#/builders/83/builds/21186
This commit is contained in:
Stella Stamenova 2022-07-14 10:47:01 -07:00
parent 64d63f4823
commit c3a28e8a99
11 changed files with 4 additions and 497 deletions

View File

@ -197,27 +197,6 @@ bool ASTResultSynthesizer::SynthesizeObjCMethodResult(
return ret; return ret;
} }
/// Returns true if LLDB can take the address of the given lvalue for the sake
/// of capturing the expression result. Returns false if LLDB should instead
/// store the expression result in a result variable.
static bool CanTakeAddressOfLValue(const Expr *lvalue_expr) {
assert(lvalue_expr->getValueKind() == VK_LValue &&
"lvalue_expr not a lvalue");
QualType qt = lvalue_expr->getType();
// If the lvalue has const-qualified non-volatile integral or enum type, then
// the underlying value might come from a const static data member as
// described in C++11 [class.static.data]p3. If that's the case, then the
// value might not have an address if the user didn't also define the member
// in a namespace scope. Taking the address would cause that LLDB later fails
// to link the expression, so those lvalues should be stored in a result
// variable.
if (qt->isIntegralOrEnumerationType() && qt.isConstQualified() &&
!qt.isVolatileQualified())
return false;
return true;
}
bool ASTResultSynthesizer::SynthesizeBodyResult(CompoundStmt *Body, bool ASTResultSynthesizer::SynthesizeBodyResult(CompoundStmt *Body,
DeclContext *DC) { DeclContext *DC) {
Log *log = GetLog(LLDBLog::Expressions); Log *log = GetLog(LLDBLog::Expressions);
@ -286,10 +265,6 @@ bool ASTResultSynthesizer::SynthesizeBodyResult(CompoundStmt *Body,
// - During dematerialization, $0 is marked up as a load address with value // - During dematerialization, $0 is marked up as a load address with value
// equal to the contents of the structure entry. // equal to the contents of the structure entry.
// //
// - Note: if we cannot take an address of the resulting Lvalue (e.g. it's
// a static const member without an out-of-class definition), then we
// follow the Rvalue route.
//
// For Rvalues // For Rvalues
// //
// - In AST result synthesis the expression E is transformed into an // - In AST result synthesis the expression E is transformed into an
@ -329,7 +304,7 @@ bool ASTResultSynthesizer::SynthesizeBodyResult(CompoundStmt *Body,
clang::VarDecl *result_decl = nullptr; clang::VarDecl *result_decl = nullptr;
if (is_lvalue && CanTakeAddressOfLValue(last_expr)) { if (is_lvalue) {
IdentifierInfo *result_ptr_id; IdentifierInfo *result_ptr_id;
if (expr_type->isFunctionType()) if (expr_type->isFunctionType())

View File

@ -2390,7 +2390,6 @@ struct MemberAttributes {
uint64_t data_bit_offset = UINT64_MAX; uint64_t data_bit_offset = UINT64_MAX;
AccessType accessibility = eAccessNone; AccessType accessibility = eAccessNone;
llvm::Optional<uint64_t> byte_size; llvm::Optional<uint64_t> byte_size;
llvm::Optional<DWARFFormValue> const_value_form;
DWARFFormValue encoding_form; DWARFFormValue encoding_form;
/// Indicates the byte offset of the word from the base address of the /// Indicates the byte offset of the word from the base address of the
/// structure. /// structure.
@ -2437,9 +2436,6 @@ MemberAttributes::MemberAttributes(const DWARFDIE &die,
case DW_AT_byte_size: case DW_AT_byte_size:
byte_size = form_value.Unsigned(); byte_size = form_value.Unsigned();
break; break;
case DW_AT_const_value:
const_value_form = form_value;
break;
case DW_AT_data_bit_offset: case DW_AT_data_bit_offset:
data_bit_offset = form_value.Unsigned(); data_bit_offset = form_value.Unsigned();
break; break;
@ -2591,65 +2587,12 @@ void DWARFASTParserClang::ParseObjCProperty(
propAttrs.prop_getter_name, propAttrs.prop_attributes, &metadata)); propAttrs.prop_getter_name, propAttrs.prop_attributes, &metadata));
} }
llvm::Expected<llvm::APInt> DWARFASTParserClang::ExtractIntFromFormValue(
const CompilerType &int_type, const DWARFFormValue &form_value) const {
clang::QualType qt = ClangUtil::GetQualType(int_type);
assert(qt->isIntegralOrEnumerationType());
TypeSystemClang &ts = *llvm::cast<TypeSystemClang>(int_type.GetTypeSystem());
clang::ASTContext &ast = ts.getASTContext();
const unsigned type_bits = ast.getIntWidth(qt);
const bool is_unsigned = qt->isUnsignedIntegerType();
// The maximum int size supported at the moment by this function. Limited
// by the uint64_t return type of DWARFFormValue::Signed/Unsigned.
constexpr std::size_t max_bit_size = 64;
// For values bigger than 64 bit (e.g. __int128_t values),
// DWARFFormValue's Signed/Unsigned functions will return wrong results so
// emit an error for now.
if (type_bits > max_bit_size) {
auto msg = llvm::formatv("Can only parse integers with up to {0} bits, but "
"given integer has {1} bits.",
max_bit_size, type_bits);
return llvm::createStringError(llvm::inconvertibleErrorCode(), msg.str());
}
// Construct an APInt with the maximum bit size and the given integer.
llvm::APInt result(max_bit_size, form_value.Unsigned(), !is_unsigned);
// Calculate how many bits are required to represent the input value.
// For unsigned types, take the number of active bits in the APInt.
// For signed types, ask APInt how many bits are required to represent the
// signed integer.
const unsigned required_bits =
is_unsigned ? result.getActiveBits() : result.getMinSignedBits();
// If the input value doesn't fit into the integer type, return an error.
if (required_bits > type_bits) {
std::string value_as_str = is_unsigned
? std::to_string(form_value.Unsigned())
: std::to_string(form_value.Signed());
auto msg = llvm::formatv("Can't store {0} value {1} in integer with {2} "
"bits.",
(is_unsigned ? "unsigned" : "signed"),
value_as_str, type_bits);
return llvm::createStringError(llvm::inconvertibleErrorCode(), msg.str());
}
// Trim the result to the bit width our the int type.
if (result.getBitWidth() > type_bits)
result = result.trunc(type_bits);
return result;
}
void DWARFASTParserClang::ParseSingleMember( void DWARFASTParserClang::ParseSingleMember(
const DWARFDIE &die, const DWARFDIE &parent_die, const DWARFDIE &die, const DWARFDIE &parent_die,
const lldb_private::CompilerType &class_clang_type, const lldb_private::CompilerType &class_clang_type,
lldb::AccessType default_accessibility, lldb::AccessType default_accessibility,
lldb_private::ClangASTImporter::LayoutInfo &layout_info, lldb_private::ClangASTImporter::LayoutInfo &layout_info,
FieldInfo &last_field_info) { FieldInfo &last_field_info) {
Log *log = GetLog(DWARFLog::TypeCompletion | DWARFLog::Lookups);
// This function can only parse DW_TAG_member. // This function can only parse DW_TAG_member.
assert(die.Tag() == DW_TAG_member); assert(die.Tag() == DW_TAG_member);
@ -2680,27 +2623,9 @@ void DWARFASTParserClang::ParseSingleMember(
if (var_type) { if (var_type) {
if (attrs.accessibility == eAccessNone) if (attrs.accessibility == eAccessNone)
attrs.accessibility = eAccessPublic; attrs.accessibility = eAccessPublic;
CompilerType ct = var_type->GetForwardCompilerType(); TypeSystemClang::AddVariableToRecordType(
clang::VarDecl *v = TypeSystemClang::AddVariableToRecordType( class_clang_type, attrs.name, var_type->GetForwardCompilerType(),
class_clang_type, attrs.name, ct, attrs.accessibility); attrs.accessibility);
if (!v) {
LLDB_LOG(log, "Failed to add variable to the record type");
return;
}
bool unused;
// TODO: Support float/double static members as well.
if (!attrs.const_value_form || !ct.IsIntegerOrEnumerationType(unused))
return;
llvm::Expected<llvm::APInt> const_value_or_err =
ExtractIntFromFormValue(ct, *attrs.const_value_form);
if (!const_value_or_err) {
LLDB_LOG_ERROR(log, const_value_or_err.takeError(),
"Failed to add const value to variable {1}: {0}",
v->getQualifiedNameAsString());
return;
}
TypeSystemClang::SetIntegerInitializerForVariable(v, *const_value_or_err);
} }
return; return;
} }

View File

@ -68,22 +68,6 @@ public:
lldb_private::ClangASTImporter &GetClangASTImporter(); lldb_private::ClangASTImporter &GetClangASTImporter();
/// Extracts an value for a given Clang integer type from a DWARFFormValue.
///
/// \param int_type The Clang type that defines the bit size and signedness
/// of the integer that should be extracted. Has to be either
/// an integer type or an enum type. For enum types the
/// underlying integer type will be considered as the
/// expected integer type that should be extracted.
/// \param form_value The DWARFFormValue that contains the integer value.
/// \return An APInt containing the same integer value as the given
/// DWARFFormValue with the bit width of the given integer type.
/// Returns an error if the value in the DWARFFormValue does not fit
/// into the given integer type or the integer type isn't supported.
llvm::Expected<llvm::APInt>
ExtractIntFromFormValue(const lldb_private::CompilerType &int_type,
const DWARFFormValue &form_value) const;
protected: protected:
/// Protected typedefs and members. /// Protected typedefs and members.
/// @{ /// @{

View File

@ -1,3 +0,0 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -1,94 +0,0 @@
"""
Tests const static data members as specified by C++11 [class.static.data]p3.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
def test(self):
self.build()
lldbutil.run_to_source_breakpoint(self, "// break here",
lldb.SBFileSpec("main.cpp"))
# Test using a simple const static integer member.
self.expect_expr("A::int_val", result_value="1")
# Try accessing the int member via some expressions that still produce
# an lvalue.
self.expect_expr("a.int_val", result_value="1")
self.expect_expr("(A::int_val)", result_value="1")
self.expect_expr("+A::int_val", result_value="1")
self.expect_expr("1,A::int_val", result_value="1")
self.expect_expr("true ? A::int_val : A::int_val", result_value="1")
# Test a simple integer member that was also defined in a namespace
# scope and has an address.
self.expect_expr("A::int_val_with_address", result_value="2")
# Try to take the address of the data member. Should produce a linker
# error only if the member doesn't have an address.
self.expect("expr const int *i = &A::int_val; *i", error=True,
substrs=["Couldn't lookup symbols:"])
self.expect_expr("const int *i = &A::int_val_with_address; *i",
result_value="2")
# Test a bool member.
self.expect_expr("A::bool_val", result_value="true")
# Test that minimum and maximum values for each data type are right.
self.expect_expr("A::char_max == char_max", result_value="true")
self.expect_expr("A::uchar_max == uchar_max", result_value="true")
self.expect_expr("A::int_max == int_max", result_value="true")
self.expect_expr("A::uint_max == uint_max", result_value="true")
self.expect_expr("A::long_max == long_max", result_value="true")
self.expect_expr("A::ulong_max == ulong_max", result_value="true")
self.expect_expr("A::longlong_max == longlong_max", result_value="true")
self.expect_expr("A::ulonglong_max == ulonglong_max", result_value="true")
self.expect_expr("A::char_min == char_min", result_value="true")
self.expect_expr("A::uchar_min == uchar_min", result_value="true")
self.expect_expr("A::int_min == int_min", result_value="true")
self.expect_expr("A::uint_min == uint_min", result_value="true")
self.expect_expr("A::long_min == long_min", result_value="true")
self.expect_expr("A::ulong_min == ulong_min", result_value="true")
self.expect_expr("A::longlong_min == longlong_min", result_value="true")
self.expect_expr("A::ulonglong_min == ulonglong_min", result_value="true")
# Test an unscoped enum.
self.expect_expr("A::enum_val", result_value="enum_case2")
# Test an unscoped enum with an invalid enum case.
self.expect_expr("A::invalid_enum_val", result_value="enum_case1 | enum_case2 | 0x4")
# Test a scoped enum.
self.expect_expr("A::scoped_enum_val", result_value="scoped_enum_case2")
# Test an scoped enum with an invalid enum case.
self.expect_expr("A::invalid_scoped_enum_val", result_value="scoped_enum_case1 | 0x4")
# Test an enum with fixed underlying type.
self.expect_expr("A::scoped_char_enum_val", result_value="case2")
self.expect_expr("A::scoped_ll_enum_val_neg", result_value="case0")
self.expect_expr("A::scoped_ll_enum_val", result_value="case2")
# Try to take the address of a member that doesn't have one.
self.expect("expr const int *i = &A::int_val; *i", error=True,
substrs=["Couldn't lookup symbols:"])
# dsymutil strips the debug info for classes that only have const static
# data members without a definition namespace scope.
@expectedFailureAll(debug_info=["dsym"])
def test_class_with_only_const_static(self):
self.build()
lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.cpp"))
self.expect_expr("ClassWithOnlyConstStatic::member", result_value="3")
# Test `constexpr static`.
self.expect_expr("ClassWithConstexprs::member", result_value="2")
self.expect_expr("ClassWithConstexprs::enum_val", result_value="enum_case2")
self.expect_expr("ClassWithConstexprs::scoped_enum_val", result_value="scoped_enum_case2")

View File

@ -1,102 +0,0 @@
#include <limits>
enum Enum {
enum_case1 = 1,
enum_case2 = 2,
};
enum class ScopedEnum {
scoped_enum_case1 = 1,
scoped_enum_case2 = 2,
};
enum class ScopedCharEnum : char {
case1 = 1,
case2 = 2,
};
enum class ScopedLongLongEnum : long long {
case0 = std::numeric_limits<long long>::min(),
case1 = 1,
case2 = std::numeric_limits<long long>::max(),
};
struct A {
const static int int_val = 1;
const static int int_val_with_address = 2;
const static bool bool_val = true;
const static auto char_max = std::numeric_limits<signed char>::max();
const static auto uchar_max = std::numeric_limits<unsigned char>::max();
const static auto int_max = std::numeric_limits<int>::max();
const static auto uint_max = std::numeric_limits<unsigned>::max();
const static auto long_max = std::numeric_limits<long>::max();
const static auto ulong_max = std::numeric_limits<unsigned long>::max();
const static auto longlong_max = std::numeric_limits<long long>::max();
const static auto ulonglong_max =
std::numeric_limits<unsigned long long>::max();
const static auto char_min = std::numeric_limits<char>::min();
const static auto uchar_min = std::numeric_limits<unsigned char>::min();
const static auto int_min = std::numeric_limits<int>::min();
const static auto uint_min = std::numeric_limits<unsigned>::min();
const static auto long_min = std::numeric_limits<long>::min();
const static auto ulong_min = std::numeric_limits<unsigned long>::min();
const static auto longlong_min = std::numeric_limits<long long>::min();
const static auto ulonglong_min =
std::numeric_limits<unsigned long long>::min();
const static Enum enum_val = enum_case2;
const static Enum invalid_enum_val = static_cast<Enum>(enum_case2 + 5);
const static ScopedEnum scoped_enum_val = ScopedEnum::scoped_enum_case2;
const static ScopedEnum invalid_scoped_enum_val = static_cast<ScopedEnum>(5);
const static ScopedCharEnum scoped_char_enum_val = ScopedCharEnum::case2;
const static ScopedLongLongEnum scoped_ll_enum_val_neg =
ScopedLongLongEnum::case0;
const static ScopedLongLongEnum scoped_ll_enum_val =
ScopedLongLongEnum::case2;
};
const int A::int_val_with_address;
struct ClassWithOnlyConstStatic {
const static int member = 3;
};
struct ClassWithConstexprs {
constexpr static int member = 2;
constexpr static Enum enum_val = enum_case2;
constexpr static ScopedEnum scoped_enum_val = ScopedEnum::scoped_enum_case2;
} cwc;
int main() {
A a;
auto char_max = A::char_max;
auto uchar_max = A::uchar_max;
auto int_max = A::int_max;
auto uint_max = A::uint_max;
auto long_max = A::long_max;
auto ulong_max = A::ulong_max;
auto longlong_max = A::longlong_max;
auto ulonglong_max = A::ulonglong_max;
auto char_min = A::char_min;
auto uchar_min = A::uchar_min;
auto int_min = A::int_min;
auto uint_min = A::uint_min;
auto long_min = A::long_min;
auto ulong_min = A::ulong_min;
auto longlong_min = A::longlong_min;
auto ulonglong_min = A::ulonglong_min;
int member_copy = ClassWithOnlyConstStatic::member;
Enum e = A::enum_val;
e = A::invalid_enum_val;
ScopedEnum se = A::scoped_enum_val;
se = A::invalid_scoped_enum_val;
ScopedCharEnum sce = A::scoped_char_enum_val;
ScopedLongLongEnum sle = A::scoped_ll_enum_val;
return 0; // break here
}

View File

@ -1,3 +0,0 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -1,31 +0,0 @@
"""
Tests const static data members as specified by C++11 [class.static.data]p3
with (u)int128_t types.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
def test_int128(self):
self.build()
lldbutil.run_to_source_breakpoint(self, "// break here",
lldb.SBFileSpec("main.cpp"))
# Try to use the (u)int128_t data members which are not supported at
# the moment. Just verify that LLDB doesn't report an incorrect value
# for them and just treats them as normal variables (which will lead
# to linker errors as they are not defined anywhere).
self.expect("expr A::int128_max", error=True,
substrs=["Couldn't lookup symbols:"])
self.expect("expr A::uint128_max", error=True,
substrs=["Couldn't lookup symbols:"])
self.expect("expr A::int128_min", error=True,
substrs=["Couldn't lookup symbols:"])
self.expect("expr A::uint128_min", error=True,
substrs=["Couldn't lookup symbols:"])

View File

@ -1,18 +0,0 @@
#include <limits>
struct A {
const static auto uint128_max = std::numeric_limits<__uint128_t>::max();
const static auto uint128_min = std::numeric_limits<__uint128_t>::min();
const static auto int128_max = std::numeric_limits<__int128_t>::max();
const static auto int128_min = std::numeric_limits<__int128_t>::min();
};
int main() {
A a;
auto int128_max = A::int128_max;
auto uint128_max = A::uint128_max;
auto int128_min = A::int128_min;
auto uint128_min = A::uint128_min;
return 0; // break here
}

View File

@ -17,7 +17,6 @@ add_lldb_unittest(SymbolFileDWARFTests
lldbPluginPlatformMacOSX lldbPluginPlatformMacOSX
lldbUtilityHelpers lldbUtilityHelpers
lldbSymbolHelpers lldbSymbolHelpers
LLVMTestingSupport
LINK_COMPONENTS LINK_COMPONENTS
Support Support
DebugInfoPDB DebugInfoPDB

View File

@ -273,128 +273,3 @@ DWARF:
}; };
ASSERT_EQ(found_function_types, expected_function_types); ASSERT_EQ(found_function_types, expected_function_types);
} }
struct ExtractIntFromFormValueTest : public testing::Test {
SubsystemRAII<FileSystem, HostInfo> subsystems;
TypeSystemClang ts;
DWARFASTParserClang parser;
ExtractIntFromFormValueTest()
: ts("dummy ASTContext", HostInfoBase::GetTargetTriple()), parser(ts) {}
/// Takes the given integer value, stores it in a DWARFFormValue and then
/// tries to extract the value back via
/// DWARFASTParserClang::ExtractIntFromFormValue.
/// Returns the string representation of the extracted value or the error
/// that was returned from ExtractIntFromFormValue.
llvm::Expected<std::string> Extract(clang::QualType qt, uint64_t value) {
DWARFFormValue form_value;
form_value.SetUnsigned(value);
llvm::Expected<llvm::APInt> result =
parser.ExtractIntFromFormValue(ts.GetType(qt), form_value);
if (!result)
return result.takeError();
llvm::SmallString<16> result_str;
result->toStringUnsigned(result_str);
return std::string(result_str.str());
}
/// Same as ExtractIntFromFormValueTest::Extract but takes a signed integer
/// and treats the result as a signed integer.
llvm::Expected<std::string> ExtractS(clang::QualType qt, int64_t value) {
DWARFFormValue form_value;
form_value.SetSigned(value);
llvm::Expected<llvm::APInt> result =
parser.ExtractIntFromFormValue(ts.GetType(qt), form_value);
if (!result)
return result.takeError();
llvm::SmallString<16> result_str;
result->toStringSigned(result_str);
return std::string(result_str.str());
}
};
TEST_F(ExtractIntFromFormValueTest, TestBool) {
using namespace llvm;
clang::ASTContext &ast = ts.getASTContext();
EXPECT_THAT_EXPECTED(Extract(ast.BoolTy, 0), HasValue("0"));
EXPECT_THAT_EXPECTED(Extract(ast.BoolTy, 1), HasValue("1"));
EXPECT_THAT_EXPECTED(Extract(ast.BoolTy, 2), Failed());
EXPECT_THAT_EXPECTED(Extract(ast.BoolTy, 3), Failed());
}
TEST_F(ExtractIntFromFormValueTest, TestInt) {
using namespace llvm;
clang::ASTContext &ast = ts.getASTContext();
// Find the min/max values for 'int' on the current host target.
constexpr int64_t int_max = std::numeric_limits<int>::max();
constexpr int64_t int_min = std::numeric_limits<int>::min();
// Check that the bit width of int matches the int width in our type system.
ASSERT_EQ(sizeof(int) * 8, ast.getIntWidth(ast.IntTy));
// Check values around int_min.
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min - 2), llvm::Failed());
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min - 1), llvm::Failed());
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min),
HasValue(std::to_string(int_min)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min + 1),
HasValue(std::to_string(int_min + 1)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min + 2),
HasValue(std::to_string(int_min + 2)));
// Check values around 0.
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, -128), HasValue("-128"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, -10), HasValue("-10"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, -1), HasValue("-1"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, 0), HasValue("0"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, 1), HasValue("1"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, 10), HasValue("10"));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, 128), HasValue("128"));
// Check values around int_max.
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max - 2),
HasValue(std::to_string(int_max - 2)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max - 1),
HasValue(std::to_string(int_max - 1)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max),
HasValue(std::to_string(int_max)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max + 1), llvm::Failed());
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max + 5), llvm::Failed());
// Check some values not near an edge case.
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_max / 2),
HasValue(std::to_string(int_max / 2)));
EXPECT_THAT_EXPECTED(ExtractS(ast.IntTy, int_min / 2),
HasValue(std::to_string(int_min / 2)));
}
TEST_F(ExtractIntFromFormValueTest, TestUnsignedInt) {
using namespace llvm;
clang::ASTContext &ast = ts.getASTContext();
constexpr uint64_t uint_max = std::numeric_limits<uint32_t>::max();
// Check values around 0.
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, 0), HasValue("0"));
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, 1), HasValue("1"));
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, 1234), HasValue("1234"));
// Check some values not near an edge case.
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max / 2),
HasValue(std::to_string(uint_max / 2)));
// Check values around uint_max.
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max - 2),
HasValue(std::to_string(uint_max - 2)));
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max - 1),
HasValue(std::to_string(uint_max - 1)));
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max),
HasValue(std::to_string(uint_max)));
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max + 1),
llvm::Failed());
EXPECT_THAT_EXPECTED(Extract(ast.UnsignedIntTy, uint_max + 2),
llvm::Failed());
}