[Lldb-commits] [clang] [lldb] [clang][Expr] Teach IgnoreUnlessSpelledInSource about implicit calls to std::get free function (PR #122265)
Michael Buch via lldb-commits
lldb-commits at lists.llvm.org
Mon Jan 13 08:16:31 PST 2025
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 <michaelbuch12 at gmail.com>
Date: Thu, 9 Jan 2025 10:01:31 +0000
Subject: [PATCH 1/7] [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 <michaelbuch12 at gmail.com>
Date: Thu, 9 Jan 2025 12:37:56 +0000
Subject: [PATCH 2/7] 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 <michaelbuch12 at gmail.com>
Date: Thu, 9 Jan 2025 18:53:43 +0000
Subject: [PATCH 3/7] 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 <michaelbuch12 at gmail.com>
Date: Mon, 13 Jan 2025 11:33:43 +0000
Subject: [PATCH 4/7] 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 <michaelbuch12 at gmail.com>
Date: Mon, 13 Jan 2025 14:59:19 +0000
Subject: [PATCH 5/7] 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
>From 66250572fc6a76ea7470028ad3529af8b8aef0b4 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 13 Jan 2025 16:07:49 +0000
Subject: [PATCH 6/7] fixup! add AST unit-test
---
clang/unittests/AST/ASTTraverserTest.cpp | 67 ++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/clang/unittests/AST/ASTTraverserTest.cpp b/clang/unittests/AST/ASTTraverserTest.cpp
index 8b6e3e90c0ea67..f3128d4a75f976 100644
--- a/clang/unittests/AST/ASTTraverserTest.cpp
+++ b/clang/unittests/AST/ASTTraverserTest.cpp
@@ -1170,6 +1170,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
{
@@ -1184,6 +1190,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
{
@@ -1195,12 +1207,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",
@@ -1582,6 +1599,56 @@ 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");
}
}
>From 8532aca58f6af1881147da4ee609c93ba0fec4fc Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 13 Jan 2025 16:16:16 +0000
Subject: [PATCH 7/7] fixup! clang-format
---
clang/unittests/AST/ASTTraverserTest.cpp | 26 +++++++++++++++---------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/clang/unittests/AST/ASTTraverserTest.cpp b/clang/unittests/AST/ASTTraverserTest.cpp
index f3128d4a75f976..9444d6ba5a1e75 100644
--- a/clang/unittests/AST/ASTTraverserTest.cpp
+++ b/clang/unittests/AST/ASTTraverserTest.cpp
@@ -1604,14 +1604,16 @@ DecompositionDecl ''
{
auto FN = ast_matchers::match(
- functionDecl(hasName("decompTuple"), hasDescendant(callExpr(hasAncestor(varDecl(hasName("a"),
- hasAncestor(bindingDecl())))).bind("decomp_call"))),
+ 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(
+ EXPECT_EQ(dumpASTString(TK_AsIs, FN[0].getNodeAs<CallExpr>("decomp_call")),
+ R"cpp(
CallExpr
|-ImplicitCastExpr
| `-DeclRefExpr 'get'
@@ -1628,8 +1630,11 @@ DeclRefExpr ''
{
auto FN = ast_matchers::match(
- functionDecl(hasName("decompTuple"), hasDescendant(callExpr(hasAncestor(varDecl(hasName("c"),
- hasAncestor(bindingDecl())))).bind("decomp_call_with_default"))),
+ functionDecl(hasName("decompTuple"),
+ hasDescendant(callExpr(hasAncestor(varDecl(
+ hasName("c"),
+ hasAncestor(bindingDecl()))))
+ .bind("decomp_call_with_default"))),
AST2->getASTContext());
EXPECT_EQ(FN.size(), 1u);
@@ -1645,9 +1650,10 @@ CallExpr
`-IntegerLiteral
)cpp");
- EXPECT_EQ(dumpASTString(TK_IgnoreUnlessSpelledInSource,
- FN[0].getNodeAs<CallExpr>("decomp_call_with_default")),
- R"cpp(
+ EXPECT_EQ(
+ dumpASTString(TK_IgnoreUnlessSpelledInSource,
+ FN[0].getNodeAs<CallExpr>("decomp_call_with_default")),
+ R"cpp(
DeclRefExpr ''
)cpp");
}
More information about the lldb-commits
mailing list