Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,18 @@ Stmt *BlockExpr::getBody() {
// Generic Expression Routines
//===----------------------------------------------------------------------===//

/// Helper to determine wether \c E is a CXXConstructExpr constructing
/// a DecompositionDecl. Used to skip Clang-generated calls to std::get
/// for structured bindings.
static bool IsDecompositionDeclRefExpr(const Expr *E) {
const auto *Unwrapped = E->IgnoreUnlessSpelledInSource();
const auto *Ref = dyn_cast<DeclRefExpr>(Unwrapped);
if (!Ref)
return false;

return isa_and_nonnull<DecompositionDecl>(Ref->getDecl());
}

bool Expr::isReadIfDiscardedInCPlusPlus11() const {
// In C++11, discarded-value expressions of a certain form are special,
// according to [expr]p10:
Expand Down Expand Up @@ -3159,10 +3171,39 @@ Expr *Expr::IgnoreUnlessSpelledInSource() {
}
return E;
};

// Used when Clang generates calls to std::get for decomposing
// structured bindings.
auto IgnoreImplicitCallSingleStep = [](Expr *E) {
auto *C = dyn_cast<CallExpr>(E);
if (!C)
return E;

// Looking for calls to a std::get, which usually just takes
// 1 argument (i.e., the structure being decomposed). If it has
// more than 1 argument, the others need to be defaulted.
unsigned NumArgs = C->getNumArgs();
if (NumArgs == 0 || (NumArgs > 1 && !isa<CXXDefaultArgExpr>(C->getArg(1))))
return E;

Expr *A = C->getArg(0);

// This was spelled out in source. Don't ignore.
if (A->getSourceRange() != E->getSourceRange())
return E;

// If the argument refers to a DecompositionDecl construction,
// ignore it.
if (IsDecompositionDeclRefExpr(A))
return A;

return E;
};

return IgnoreExprNodes(
this, IgnoreImplicitSingleStep, IgnoreImplicitCastsExtraSingleStep,
IgnoreParensOnlySingleStep, IgnoreImplicitConstructorSingleStep,
IgnoreImplicitMemberCallSingleStep);
IgnoreImplicitMemberCallSingleStep, IgnoreImplicitCallSingleStep);
}

bool Expr::isDefaultArgument() const {
Expand Down
64 changes: 63 additions & 1 deletion clang/test/DebugInfo/CXX/structured-binding.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// RUN: %clang_cc1 -emit-llvm -debug-info-kind=standalone -triple %itanium_abi_triple %s -o - | FileCheck %s --implicit-check-not="call void @llvm.dbg.declare"
// RUN: %clang_cc1 -std=c++23 -emit-llvm -debug-info-kind=standalone -triple %itanium_abi_triple %s -o - | FileCheck %s --implicit-check-not="call void @llvm.dbg.declare"

// CHECK: define {{.*}} i32 @_Z1fv
// CHECK: #dbg_declare(ptr %{{[a-z]+}}, ![[VAR_0:[0-9]+]], !DIExpression(),
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_1:[0-9]+]], !DIExpression(),
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_2:[0-9]+]], !DIExpression(DW_OP_plus_uconst, 4),
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_3:[0-9]+]], !DIExpression(DW_OP_deref),
// CHECK: #dbg_declare(ptr %{{[0-9]+}}, ![[VAR_4:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 4),
// CHECK: #dbg_declare(ptr %z1, ![[VAR_5:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %z2, ![[VAR_6:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %k, ![[VAR_7:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %v, ![[VAR_8:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %w, ![[VAR_9:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %m, ![[VAR_10:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %n, ![[VAR_11:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %s, ![[VAR_12:[0-9]+]], !DIExpression()
// CHECK: #dbg_declare(ptr %p, ![[VAR_13:[0-9]+]], !DIExpression()
// CHECK: getelementptr inbounds nuw %struct.A, ptr {{.*}}, i32 0, i32 1, !dbg ![[Y1_DEBUG_LOC:[0-9]+]]
// CHECK: getelementptr inbounds nuw %struct.A, ptr {{.*}}, i32 0, i32 1, !dbg ![[Y2_DEBUG_LOC:[0-9]+]]
// CHECK: load ptr, ptr %z2, {{.*}}!dbg ![[Z2_DEBUG_LOC:[0-9]+]]
Expand All @@ -20,6 +28,13 @@
// CHECK: ![[VAR_4]] = !DILocalVariable(name: "y2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_5]] = !DILocalVariable(name: "z1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_6]] = !DILocalVariable(name: "z2", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_7]] = !DILocalVariable(name: "k", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_8]] = !DILocalVariable(name: "v", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_9]] = !DILocalVariable(name: "w", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_10]] = !DILocalVariable(name: "m", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_11]] = !DILocalVariable(name: "n", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_12]] = !DILocalVariable(name: "s", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})
// CHECK: ![[VAR_13]] = !DILocalVariable(name: "p", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}})

struct A {
int x;
Expand All @@ -34,6 +49,22 @@ struct B {
template<> int get<1>() { return z; }
};

struct C {
int w;
int z;
template<int> int get(this C&& self);
template<> int get<0>(this C&& self) { return self.w; }
template<> int get<1>(this C&& self) { return self.z; }
};

struct D {
int w;
int z;
template<int> int get(int unused = 0);
template<> int get<0>(int unused) { return w; }
template<> int get<1>(int unused) { return z; }
};

// Note: the following declarations are necessary for decomposition of tuple-like
// structured bindings
namespace std {
Expand All @@ -44,7 +75,35 @@ struct tuple_size<B> {
static constexpr unsigned value = 2;
};

template<>
struct tuple_size<C> {
static constexpr unsigned value = 2;
};

template<>
struct tuple_size<D> {
static constexpr unsigned value = 2;
};

template<unsigned, typename T> struct tuple_element { using type = int; };

// Decomposition of tuple-like bindings but where the `get` methods
// are declared as free functions.
struct triple {
int k;
int v;
int w;
};

template<>
struct tuple_size<triple> {
static constexpr unsigned value = 3;
};

template <unsigned I> int get(triple);
template <> int get<0>(triple p) { return p.k; }
template <> int get<1>(triple p) { return p.v; }
template <> int get<2>(triple p) { return p.w; }
} // namespace std

int f() {
Expand All @@ -58,6 +117,9 @@ int f() {
auto &[c1, c2] = cmplx;
int vctr __attribute__ ((vector_size (sizeof(int)*2)))= {1, 2};
auto &[v1, v2] = vctr;
auto [k, v, w] = std::triple{3, 4, 5};
auto [m, n] = C{2, 3};
auto [s, p] = D{2, 3};
return //
x1 //
+ //
Expand Down
73 changes: 73 additions & 0 deletions clang/unittests/AST/ASTTraverserTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,12 @@ struct Pair
int x, y;
};

// Tuple-like structure with a `get` method that has a default argument.
struct Pair2
{
int x, y;
};

// Note: these utilities are required to force binding to tuple like structure
namespace std
{
Expand All @@ -1188,6 +1194,12 @@ namespace std
static constexpr size_t value = 2;
};

template <>
struct tuple_size<Pair2>
{
static constexpr size_t value = 2;
};

template <size_t I, class T>
struct tuple_element
{
Expand All @@ -1199,12 +1211,17 @@ namespace std
template <size_t I>
int &&get(Pair &&p);

template <size_t I>
int &&get(Pair2 &&p, int unused = 0);

void decompTuple()
{
Pair p{1, 2};
auto [a, b] = p;

a = 3;

auto [c, d] = Pair2{3, 4};
}

)cpp",
Expand Down Expand Up @@ -1586,6 +1603,62 @@ DecompositionDecl ''
|-DeclRefExpr 'p'
|-BindingDecl 'a'
`-BindingDecl 'b'
)cpp");
}

{
auto FN = ast_matchers::match(
functionDecl(hasName("decompTuple"),
hasDescendant(callExpr(hasAncestor(varDecl(
hasName("a"),
hasAncestor(bindingDecl()))))
.bind("decomp_call"))),
AST2->getASTContext());
EXPECT_EQ(FN.size(), 1u);

EXPECT_EQ(dumpASTString(TK_AsIs, FN[0].getNodeAs<CallExpr>("decomp_call")),
R"cpp(
CallExpr
|-ImplicitCastExpr
| `-DeclRefExpr 'get'
`-ImplicitCastExpr
`-DeclRefExpr ''
)cpp");

EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
FN[0].getNodeAs<CallExpr>("decomp_call")),
R"cpp(
DeclRefExpr ''
)cpp");
}

{
auto FN = ast_matchers::match(
functionDecl(hasName("decompTuple"),
hasDescendant(callExpr(hasAncestor(varDecl(
hasName("c"),
hasAncestor(bindingDecl()))))
.bind("decomp_call_with_default"))),
AST2->getASTContext());
EXPECT_EQ(FN.size(), 1u);

EXPECT_EQ(dumpASTString(TK_AsIs, FN[0].getNodeAs<CallExpr>(
"decomp_call_with_default")),
R"cpp(
CallExpr
|-ImplicitCastExpr
| `-DeclRefExpr 'get'
|-ImplicitCastExpr
| `-DeclRefExpr ''
`-CXXDefaultArgExpr
`-IntegerLiteral
)cpp");

EXPECT_EQ(
dumpASTString(TK_IgnoreUnlessSpelledInSource,
FN[0].getNodeAs<CallExpr>("decomp_call_with_default")),
R"cpp(
DeclRefExpr ''
)cpp");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,17 @@ def test(self):
self.expect_expr("tx2", result_value="4")
self.expect_expr("ty2", result_value="'z'")
self.expect_expr("tz2", result_value="10")

self.expect(
"frame variable",
substrs=[
"tx1 =",
"ty1 =",
"tz1 =",
"tx2 =",
"ty2 =",
"tz2 =",
"mp1 =",
"mp2 =",
],
)
81 changes: 75 additions & 6 deletions lldb/test/API/lang/cpp/structured-binding/main.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,80 @@
// Structured binding in C++ can bind identifiers to subobjects of an object.
//
// There are three cases we need to test:
// There are four cases we need to test:
// 1) arrays
// 2) tuples like objects
// 3) non-static data members
// 2) tuple-like objects with `get` member functions
// 3) tuple-like objects with `get` free functions
// 4) non-static data members
//
// They can also bind by copy, reference or rvalue reference.

#include <tuple>
Copy link
Member Author

@Michael137 Michael137 Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in favour of mock_tuple so we can better control what kind of tuple-like decomposition we're testing here.

struct MyPair {
int m1;
int m2;

// Helpers to enable tuple-like decomposition.
template <unsigned> int get();
template <> int get<0>() { return m1; }
template <> int get<1>() { return m2; }
};

namespace std {
template <typename T1, typename T2, typename T3> struct mock_tuple {
T1 m1;
T2 m2;
T3 m3;
};

template <typename T> struct tuple_size;

template <unsigned, typename T> struct tuple_element;

// Helpers to enable tuple-like decomposition for MyPair
template <unsigned I> struct tuple_element<I, MyPair> {
using type = int;
};

template <> struct tuple_size<MyPair> {
static constexpr unsigned value = 2;
};

// Helpers to enable tuple-like decomposition for mock_tuple
template <typename T1, typename T2, typename T3>
struct tuple_element<0, mock_tuple<T1, T2, T3>> {
using type = T1;
};

template <typename T1, typename T2, typename T3>
struct tuple_element<1, mock_tuple<T1, T2, T3>> {
using type = T2;
};

template <typename T1, typename T2, typename T3>
struct tuple_element<2, mock_tuple<T1, T2, T3>> {
using type = T3;
};

template <typename T1, typename T2, typename T3>
struct tuple_size<mock_tuple<T1, T2, T3>> {
static constexpr unsigned value = 3;
};

template <unsigned I, typename T1, typename T2, typename T3>
typename tuple_element<I, mock_tuple<T1, T2, T3>>::type
get(mock_tuple<T1, T2, T3> p) {
switch (I) {
case 0:
return p.m1;
case 1:
return p.m2;
case 2:
return p.m3;
default:
__builtin_trap();
}
}

} // namespace std

struct A {
int x;
Expand Down Expand Up @@ -54,16 +121,18 @@ int main() {
char y{'z'};
int z{10};

std::tuple<float, char, int> tpl(x, y, z);
std::mock_tuple<float, char, int> tpl{.m1 = x, .m2 = y, .m3 = z};
auto [tx1, ty1, tz1] = tpl;
auto &[tx2, ty2, tz2] = tpl;

auto [mp1, mp2] = MyPair{.m1 = 1, .m2 = 2};

return a1.x + b1 + c1 + d1 + e1 + f1 + a2.y + b2 + c2 + d2 + e2 + f2 + a3.x +
b3 + c3 + d3 + e3 + f3 + carr_copy1 + carr_copy2 + carr_copy3 +
sarr_copy1 + sarr_copy2 + sarr_copy3 + iarr_copy1 + iarr_copy2 +
iarr_copy3 + carr_ref1 + carr_ref2 + carr_ref3 + sarr_ref1 +
sarr_ref2 + sarr_ref3 + iarr_ref1 + iarr_ref2 + iarr_ref3 +
carr_rref1 + carr_rref2 + carr_rref3 + sarr_rref1 + sarr_rref2 +
sarr_rref3 + iarr_rref1 + iarr_rref2 + iarr_rref3 + tx1 + ty1 + tz1 +
tx2 + ty2 + tz2; // break here
tx2 + ty2 + tz2 + mp1 + mp2; // break here
}