https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/122265
>From b43ffd0c1bb6e4f1ca5b8458848f574480021b08 Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Thu, 9 Jan 2025 10:01:31 +0000 Subject: [PATCH 1/5] [clang][DebugInfo] Expand detection of structured bindings to account for std::get free function When we generate the debug-info for a `VarDecl` we try to determine whether it was introduced as part of a structure binding (aka a "holding var"). If it was, we don't mark it as `artificial`. The heuristic to determine a holding var uses `IgnoreUnlessSpelledInSource` to unwrap the `VarDecl` initializer until we hit a `DeclRefExpr` that refers to a `Decomposition`. For "tuple-like decompositions", Clang will generate a call to a `template<size_t I> Foo get(Bar)` function that retrieves the `Ith` element from the tuple-like structure. If that function is a member function, we get an AST that looks as follows: ``` VarDecl implicit used z1 'std::tuple_element<0, B>::type &&' cinit `-ExprWithCleanups <col:10> 'int' xvalue `-MaterializeTemporaryExpr <col:10> 'int' xvalue extended by Var 0x11d110cf8 'z1' 'std::tuple_element<0, B>::type &&' `-CXXMemberCallExpr <col:10> 'int' `-MemberExpr <col:10> '<bound member function type>' .get 0x11d104390 `-ImplicitCastExpr <col:10> 'B' xvalue <NoOp> `-DeclRefExpr <col:10> 'B' lvalue Decomposition 0x11d1100a8 '' 'B' ``` `IgnoreUnlessSpelledInSource` happily unwraps this down to the `DeclRefExpr`. However, when the `get` helper is a free function (which it is for `std::pair` in libc++ for example), then the AST is: ``` VarDecl col:16 implicit used k 'std::tuple_element<0, const std::tuple<int, int>>::type &' cinit `-CallExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type':'const int' lvalue adl |-ImplicitCastExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type &(*)(const tuple<int, int> &) noexcept' <FunctionToPointerDecay> | `-DeclRefExpr <col:16> 'const typename tuple_element<0UL, tuple<int, int>>::type &(const tuple<int, int> &) noexcept' lvalue Function 0x1210262d8 'get' 'const typename tuple_element<0UL, tuple<int, int>>::type &(const tuple<int, int> &) noexcept' (FunctionTemplate 0x11d068088 'get') `-DeclRefExpr <col:16> 'const std::tuple<int, int>' lvalue Decomposition 0x121021518 '' 'const std::tuple<int, int> &' ``` `IgnoreUnlessSpelledInSource` doesn't unwrap this `CallExpr`, so we incorrectly mark the binding as `artificial` in debug-info. This patch adjusts our heuristic to account for `CallExpr` nodes. Fixes https://github.com/llvm/llvm-project/issues/122028 --- clang/lib/CodeGen/CGDebugInfo.cpp | 28 ++++++- .../debug-info-structured-binding.cpp | 28 ++++++- .../TestStructuredBinding.py | 11 +++ .../API/lang/cpp/structured-binding/main.cpp | 81 +++++++++++++++++-- 4 files changed, 140 insertions(+), 8 deletions(-) diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index d7e5e95b7873a0..f49e759e50ea3d 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -85,12 +85,38 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) { if (!Init) return false; + Init = Init->IgnoreUnlessSpelledInSource(); + if (!Init) + return false; + + // For tuple-like decompositions, if the `get` function + // is not a member of the decomposed type, but instead a + // free function (e.g., how std::get is specialized for + // std::pair), then the initializer is a `CallExpr` and + // we need to dig into the argument before unwrapping it + // with IgnoreUnlessSpelledInSource. + if (auto const *CE = llvm::dyn_cast<CallExpr>(Init)) { + if (CE->getNumArgs() == 0) + return false; + + // The first argument will be the type we're decomposing. + // Technically the getter could have more than 1 argument + // (e.g., if they're defaulted arguments), but we don't care + // about them because we just need to check whether the + // first argument is a DecompositionDecl. + auto const *Arg = CE->getArg(0); + if (!Arg) + return false; + + Init = Arg; + } + auto const *RefExpr = llvm::dyn_cast_or_null<DeclRefExpr>(Init->IgnoreUnlessSpelledInSource()); if (!RefExpr) return false; - return llvm::dyn_cast_or_null<DecompositionDecl>(RefExpr->getDecl()); + return llvm::isa_and_nonnull<DecompositionDecl>(RefExpr->getDecl()); } /// Returns true if \ref VD is a compiler-generated variable diff --git a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp index 5fbd54c16382c0..55a84a65015842 100644 --- a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp +++ b/clang/test/CodeGenCXX/debug-info-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" +// CHECK: define noundef 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,9 @@ // 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: ![[VAR_0]] = !DILocalVariable(name: "a" // CHECK: ![[VAR_1]] = !DILocalVariable(name: "x1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) // CHECK: ![[VAR_2]] = !DILocalVariable(name: "y1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) @@ -14,6 +18,9 @@ // 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]+}}) struct A { int x; @@ -39,6 +46,24 @@ struct tuple_size<B> { }; 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() { @@ -46,5 +71,6 @@ int f() { auto [x1, y1] = a; auto &[x2, y2] = a; auto [z1, z2] = B{1, 2}; - return x1 + y1 + x2 + y2 + z1 + z2; + auto [k, v, w] = std::triple{3, 4, 5}; + return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w; } diff --git a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py index 4b26d9f139c01e..cfa01b30068b8f 100644 --- a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py +++ b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py @@ -98,3 +98,14 @@ 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 3fbfb18dbeff0c..b649358ebdf667 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 <tuple> +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; @@ -54,10 +121,12 @@ 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 + @@ -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 } >From f660e288efcde4b6a5740b705ef28c0de7c6c0de Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Thu, 9 Jan 2025 12:37:56 +0000 Subject: [PATCH 2/5] fixup! python format --- .../lang/cpp/structured-binding/TestStructuredBinding.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py index cfa01b30068b8f..09d9bd1b894485 100644 --- a/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py +++ b/lldb/test/API/lang/cpp/structured-binding/TestStructuredBinding.py @@ -99,7 +99,8 @@ def test(self): self.expect_expr("ty2", result_value="'z'") self.expect_expr("tz2", result_value="10") - self.expect("frame variable", + self.expect( + "frame variable", substrs=[ "tx1 =", "ty1 =", @@ -108,4 +109,6 @@ def test(self): "ty2 =", "tz2 =", "mp1 =", - "mp2 ="]) + "mp2 =", + ], + ) >From ad5c1a9b2e2277cbb612c52117332a8031c9be0d Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Thu, 9 Jan 2025 18:53:43 +0000 Subject: [PATCH 3/5] fixup! relax regex in test --- clang/test/CodeGenCXX/debug-info-structured-binding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp index 55a84a65015842..237846d6006c6e 100644 --- a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp +++ b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp @@ -1,6 +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" -// CHECK: define noundef i32 @_Z1fv +// 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), >From 60cfb2cef201f948b435897c93f23f3f8114245c Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Mon, 13 Jan 2025 11:33:43 +0000 Subject: [PATCH 4/5] fixup! ignore implicit function call in IgnoreUnlessSpelledInSource --- clang/lib/AST/Expr.cpp | 16 +++++++- clang/lib/CodeGen/CGDebugInfo.cpp | 26 ------------ .../debug-info-structured-binding.cpp | 40 ++++++++++++++++++- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index f6a4ed970cb23f..abbb56d4905fa5 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -3183,10 +3183,24 @@ Expr *Expr::IgnoreUnlessSpelledInSource() { } return E; }; + + auto IgnoreImplicitCallSingleStep = [](Expr *E) { + if (auto *C = dyn_cast<CallExpr>(E)) { + auto NumArgs = C->getNumArgs(); + if (NumArgs == 1 || + (NumArgs > 1 && isa<CXXDefaultArgExpr>(C->getArg(1)))) { + Expr *A = C->getArg(0); + if (A->getSourceRange() == E->getSourceRange()) + return A; + } + } + return E; + }; + return IgnoreExprNodes( this, IgnoreImplicitSingleStep, IgnoreImplicitCastsExtraSingleStep, IgnoreParensOnlySingleStep, IgnoreImplicitConstructorSingleStep, - IgnoreImplicitMemberCallSingleStep); + IgnoreImplicitMemberCallSingleStep, IgnoreImplicitCallSingleStep); } bool Expr::isDefaultArgument() const { diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index f49e759e50ea3d..9f5f0c187bfcf9 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -85,32 +85,6 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) { if (!Init) return false; - Init = Init->IgnoreUnlessSpelledInSource(); - if (!Init) - return false; - - // For tuple-like decompositions, if the `get` function - // is not a member of the decomposed type, but instead a - // free function (e.g., how std::get is specialized for - // std::pair), then the initializer is a `CallExpr` and - // we need to dig into the argument before unwrapping it - // with IgnoreUnlessSpelledInSource. - if (auto const *CE = llvm::dyn_cast<CallExpr>(Init)) { - if (CE->getNumArgs() == 0) - return false; - - // The first argument will be the type we're decomposing. - // Technically the getter could have more than 1 argument - // (e.g., if they're defaulted arguments), but we don't care - // about them because we just need to check whether the - // first argument is a DecompositionDecl. - auto const *Arg = CE->getArg(0); - if (!Arg) - return false; - - Init = Arg; - } - auto const *RefExpr = llvm::dyn_cast_or_null<DeclRefExpr>(Init->IgnoreUnlessSpelledInSource()); if (!RefExpr) diff --git a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp index 237846d6006c6e..f80bd6b29305b5 100644 --- a/clang/test/CodeGenCXX/debug-info-structured-binding.cpp +++ b/clang/test/CodeGenCXX/debug-info-structured-binding.cpp @@ -1,4 +1,4 @@ -// 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(), @@ -11,6 +11,10 @@ // 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: ![[VAR_0]] = !DILocalVariable(name: "a" // CHECK: ![[VAR_1]] = !DILocalVariable(name: "x1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) // CHECK: ![[VAR_2]] = !DILocalVariable(name: "y1", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) @@ -21,6 +25,10 @@ // 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; @@ -35,6 +43,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 { @@ -45,6 +69,16 @@ 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 @@ -72,5 +106,7 @@ int f() { auto &[x2, y2] = a; auto [z1, z2] = B{1, 2}; auto [k, v, w] = std::triple{3, 4, 5}; - return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w; + auto [m, n] = C{2, 3}; + auto [s, p] = D{2, 3}; + return x1 + y1 + x2 + y2 + z1 + z2 + k + v + w + m + n + s + p; } >From 7c10a37c001ff0ba2254a2e371b49d7dc3fa1e6d Mon Sep 17 00:00:00 2001 From: Michael Buch <michaelbuc...@gmail.com> Date: Mon, 13 Jan 2025 14:59:19 +0000 Subject: [PATCH 5/5] fixup! revert NFC change --- clang/lib/CodeGen/CGDebugInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 9f5f0c187bfcf9..d7e5e95b7873a0 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -90,7 +90,7 @@ static bool IsDecomposedVarDecl(VarDecl const *VD) { if (!RefExpr) return false; - return llvm::isa_and_nonnull<DecompositionDecl>(RefExpr->getDecl()); + return llvm::dyn_cast_or_null<DecompositionDecl>(RefExpr->getDecl()); } /// Returns true if \ref VD is a compiler-generated variable _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits