diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index cdff160067fed..6ef7e54ec00b9 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -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(Unwrapped); + if (!Ref) + return false; + + return isa_and_nonnull(Ref->getDecl()); +} + bool Expr::isReadIfDiscardedInCPlusPlus11() const { // In C++11, discarded-value expressions of a certain form are special, // according to [expr]p10: @@ -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(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(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 { diff --git a/clang/test/DebugInfo/CXX/structured-binding.cpp b/clang/test/DebugInfo/CXX/structured-binding.cpp index 8032ce85c9e25..95457f477deeb 100644 --- a/clang/test/DebugInfo/CXX/structured-binding.cpp +++ b/clang/test/DebugInfo/CXX/structured-binding.cpp @@ -1,5 +1,6 @@ -// 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), @@ -7,6 +8,13 @@ // 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]+]] @@ -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; @@ -34,6 +49,22 @@ struct B { template<> int get<1>() { return z; } }; +struct C { + int w; + int z; + template 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 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 { @@ -44,7 +75,35 @@ struct tuple_size { static constexpr unsigned value = 2; }; +template<> +struct tuple_size { + static constexpr unsigned value = 2; +}; + +template<> +struct tuple_size { + static constexpr unsigned value = 2; +}; + template 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 { + static constexpr unsigned value = 3; +}; + +template 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() { @@ -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 // + // diff --git a/clang/unittests/AST/ASTTraverserTest.cpp b/clang/unittests/AST/ASTTraverserTest.cpp index 988e81d8e51de..bcbf01b6b0385 100644 --- a/clang/unittests/AST/ASTTraverserTest.cpp +++ b/clang/unittests/AST/ASTTraverserTest.cpp @@ -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 { @@ -1188,6 +1194,12 @@ namespace std static constexpr size_t value = 2; }; + template <> + struct tuple_size + { + static constexpr size_t value = 2; + }; + template struct tuple_element { @@ -1199,12 +1211,17 @@ namespace std template int &&get(Pair &&p); +template +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", @@ -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("decomp_call")), + R"cpp( +CallExpr +|-ImplicitCastExpr +| `-DeclRefExpr 'get' +`-ImplicitCastExpr + `-DeclRefExpr '' +)cpp"); + + EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource, + FN[0].getNodeAs("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( + "decomp_call_with_default")), + R"cpp( +CallExpr +|-ImplicitCastExpr +| `-DeclRefExpr 'get' +|-ImplicitCastExpr +| `-DeclRefExpr '' +`-CXXDefaultArgExpr + `-IntegerLiteral +)cpp"); + + EXPECT_EQ( + dumpASTString(TK_IgnoreUnlessSpelledInSource, + FN[0].getNodeAs("decomp_call_with_default")), + R"cpp( +DeclRefExpr '' )cpp"); } } diff --git a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py index 1e026cf8c237c..5f939ecfbef29 100644 --- a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py +++ b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py @@ -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 =", + ], + ) diff --git a/lldb/test/API/lang/cpp/structured-binding/main.cpp b/lldb/test/API/lang/cpp/structured-binding/main.cpp index 3fbfb18dbeff0..b649358ebdf66 100644 --- a/lldb/test/API/lang/cpp/structured-binding/main.cpp +++ b/lldb/test/API/lang/cpp/structured-binding/main.cpp @@ -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 +struct MyPair { + int m1; + int m2; + + // Helpers to enable tuple-like decomposition. + template int get(); + template <> int get<0>() { return m1; } + template <> int get<1>() { return m2; } +}; + +namespace std { +template struct mock_tuple { + T1 m1; + T2 m2; + T3 m3; +}; + +template struct tuple_size; + +template struct tuple_element; + +// Helpers to enable tuple-like decomposition for MyPair +template struct tuple_element { + using type = int; +}; + +template <> struct tuple_size { + static constexpr unsigned value = 2; +}; + +// Helpers to enable tuple-like decomposition for mock_tuple +template +struct tuple_element<0, mock_tuple> { + using type = T1; +}; + +template +struct tuple_element<1, mock_tuple> { + using type = T2; +}; + +template +struct tuple_element<2, mock_tuple> { + using type = T3; +}; + +template +struct tuple_size> { + static constexpr unsigned value = 3; +}; + +template +typename tuple_element>::type +get(mock_tuple 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; @@ -54,10 +121,12 @@ int main() { char y{'z'}; int z{10}; - std::tuple tpl(x, y, z); + std::mock_tuple 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 + @@ -65,5 +134,5 @@ int main() { 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 }