[clang] d8c2607 - [clang][Sema] Fix false positive -Wshadow with structured binding captures (#157667)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 15 02:56:03 PDT 2025
Author: Ivan Murashko
Date: 2025-09-15T10:55:59+01:00
New Revision: d8c2607fb1f4094db18e7716764738f9bc8489df
URL: https://github.com/llvm/llvm-project/commit/d8c2607fb1f4094db18e7716764738f9bc8489df
DIFF: https://github.com/llvm/llvm-project/commit/d8c2607fb1f4094db18e7716764738f9bc8489df.diff
LOG: [clang][Sema] Fix false positive -Wshadow with structured binding captures (#157667)
Previously, lambda init captures of structured bindings were incorrectly
classified as regular shadow warnings (shown with `-Wshadow`), while
regular parameter captures were correctly classified as
`uncaptured-local` warnings (shown only with `-Wshadow-all`). This
created inconsistent behavior:
```cpp
void foo1(std::pair<int, int> val) {
[val = std::move(val)](){}(); // No warning with -Wshadow (correct)
}
void foo2(std::pair<int, int> val) {
auto [a, b] = val;
[a = std::move(a)](){}(); // Warning with -Wshadow (incorrect)
}
```
The fix extends the existing lambda capture classification logic in
`CheckShadow()` to handle `BindingDecl` consistently with `VarDecl`,
ensuring both cases show no warnings with `-Wshadow` and
`uncaptured-local` warnings with `-Wshadow-all`.
Fixes #68605.
---------
Co-authored-by: Mariya Podchishchaeva <mariya.podchishchaeva at intel.com>
Added:
clang/test/SemaCXX/PR68605.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/lib/Sema/SemaDecl.cpp
clang/test/SemaCXX/warn-shadow-in-lambdas.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 41bec2666f939..bdf8334f78cea 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -316,6 +316,11 @@ Bug Fixes in This Version
- Builtin elementwise operators now accept vector arguments that have
diff erent
qualifiers on their elements. For example, vector of 4 ``const float`` values
and vector of 4 ``float`` values. (#GH155405)
+- Fixed inconsistent shadow warnings for lambda capture of structured bindings.
+ Previously, ``[val = val]`` (regular parameter) produced no warnings with ``-Wshadow``
+ while ``[a = a]`` (where ``a`` is from ``auto [a, b] = std::make_pair(1, 2)``)
+ incorrectly produced warnings. Both cases now consistently show no warnings with
+ ``-Wshadow`` and show uncaptured-local warnings with ``-Wshadow-all``. (#GH68605)
- Fixed a failed assertion with a negative limit parameter value inside of
``__has_embed``. (#GH157842)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2b0ddb584c37e..45cfb66996ce6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8395,7 +8395,7 @@ static ShadowedDeclKind computeShadowedDeclKind(const NamedDecl *ShadowedDecl,
/// Return the location of the capture if the given lambda captures the given
/// variable \p VD, or an invalid source location otherwise.
static SourceLocation getCaptureLocation(const LambdaScopeInfo *LSI,
- const VarDecl *VD) {
+ const ValueDecl *VD) {
for (const Capture &Capture : LSI->Captures) {
if (Capture.isVariableCapture() && Capture.getVariable() == VD)
return Capture.getLocation();
@@ -8492,7 +8492,9 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
if (isa<VarDecl>(D) && NewDC && isa<CXXMethodDecl>(NewDC)) {
if (const auto *RD = dyn_cast<CXXRecordDecl>(NewDC->getParent())) {
if (RD->isLambda() && OldDC->Encloses(NewDC->getLexicalParent())) {
- if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
+ // Handle both VarDecl and BindingDecl in lambda contexts
+ if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
+ const auto *VD = cast<ValueDecl>(ShadowedDecl);
const auto *LSI = cast<LambdaScopeInfo>(getCurFunction());
if (RD->getLambdaCaptureDefault() == LCD_None) {
// Try to avoid warnings for lambdas with an explicit capture
@@ -8521,18 +8523,27 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
return;
}
}
- if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl);
- VD && VD->hasLocalStorage()) {
- // A variable can't shadow a local variable in an enclosing scope, if
- // they are separated by a non-capturing declaration context.
- for (DeclContext *ParentDC = NewDC;
- ParentDC && !ParentDC->Equals(OldDC);
- ParentDC = getLambdaAwareParentOfDeclContext(ParentDC)) {
- // Only block literals, captured statements, and lambda expressions
- // can capture; other scopes don't.
- if (!isa<BlockDecl>(ParentDC) && !isa<CapturedDecl>(ParentDC) &&
- !isLambdaCallOperator(ParentDC)) {
- return;
+ // Apply scoping logic to both VarDecl and BindingDecl with local storage
+ if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
+ bool HasLocalStorage = false;
+ if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl))
+ HasLocalStorage = VD->hasLocalStorage();
+ else if (const auto *BD = dyn_cast<BindingDecl>(ShadowedDecl))
+ HasLocalStorage =
+ cast<VarDecl>(BD->getDecomposedDecl())->hasLocalStorage();
+
+ if (HasLocalStorage) {
+ // A variable can't shadow a local variable or binding in an enclosing
+ // scope, if they are separated by a non-capturing declaration
+ // context.
+ for (DeclContext *ParentDC = NewDC;
+ ParentDC && !ParentDC->Equals(OldDC);
+ ParentDC = getLambdaAwareParentOfDeclContext(ParentDC)) {
+ // Only block literals, captured statements, and lambda expressions
+ // can capture; other scopes don't.
+ if (!isa<BlockDecl>(ParentDC) && !isa<CapturedDecl>(ParentDC) &&
+ !isLambdaCallOperator(ParentDC))
+ return;
}
}
}
@@ -8579,7 +8590,8 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) {
const NamedDecl *ShadowedDecl = Shadow.ShadowedDecl;
// Try to avoid the warning when the shadowed decl isn't captured.
const DeclContext *OldDC = ShadowedDecl->getDeclContext();
- if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
+ if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
+ const auto *VD = cast<ValueDecl>(ShadowedDecl);
SourceLocation CaptureLoc = getCaptureLocation(LSI, VD);
Diag(Shadow.VD->getLocation(),
CaptureLoc.isInvalid() ? diag::warn_decl_shadow_uncaptured_local
diff --git a/clang/test/SemaCXX/PR68605.cpp b/clang/test/SemaCXX/PR68605.cpp
new file mode 100644
index 0000000000000..97eb858b77246
--- /dev/null
+++ b/clang/test/SemaCXX/PR68605.cpp
@@ -0,0 +1,72 @@
+// RUN: %clang_cc1 -verify -fsyntax-only -std=c++20 -Wshadow %s
+// RUN: %clang_cc1 -verify=all -fsyntax-only -std=c++20 -Wshadow-all %s
+
+// Test for issue #68605: Inconsistent shadow warnings for lambda capture of structured bindings.
+//
+// The issue was that structured binding lambda captures were incorrectly classified
+// as regular shadow warnings (shown with -Wshadow) while regular parameter captures
+// were classified as uncaptured-local warnings (shown only with -Wshadow-all).
+//
+// This test validates that both VarDecl and BindingDecl lambda captures now
+// behave consistently: no warnings with -Wshadow, but uncaptured-local warnings
+// with -Wshadow-all.
+
+namespace std {
+ template<typename T> T&& move(T&& t) { return static_cast<T&&>(t); }
+}
+
+namespace issue_68605 {
+
+// Simple pair-like struct for testing
+struct Pair {
+ int first;
+ int second;
+ Pair(int f, int s) : first(f), second(s) {}
+};
+
+// Test case 1: Regular parameter - consistent behavior
+void foo1(Pair val) { // all-note {{previous declaration is here}}
+ [val = std::move(val)](){}(); // all-warning {{declaration shadows a local variable}}
+}
+
+// Test case 2: Structured binding - now consistent with regular parameter
+void foo2(Pair val) {
+ auto [a,b] = val; // all-note {{previous declaration is here}}
+ [a = std::move(a)](){}(); // all-warning {{declaration shadows a structured binding}}
+}
+
+// Test case 3: Multiple captures showing consistent behavior
+void foo3() {
+ Pair data{42, 100};
+ auto [id, value] = data; // all-note 2{{previous declaration is here}}
+
+ // Both show consistent uncaptured-local warnings with -Wshadow-all
+ auto lambda1 = [id = id](){ return id; }; // all-warning {{declaration shadows a structured binding}}
+ auto lambda2 = [value = value](){ return value; }; // all-warning {{declaration shadows a structured binding}}
+}
+
+// Test case 4: Mixed scenario showing consistent behavior
+void foo4() {
+ int regular_var = 10; // all-note {{previous declaration is here}}
+ Pair pair_data{1, 2};
+ auto [x, y] = pair_data; // all-note 2{{previous declaration is here}}
+
+ // All captures now show consistent uncaptured-local warnings with -Wshadow-all
+ auto lambda1 = [regular_var = regular_var](){}; // all-warning {{declaration shadows a local variable}}
+ auto lambda2 = [x = x](){}; // all-warning {{declaration shadows a structured binding}}
+ auto lambda3 = [y = y](){}; // all-warning {{declaration shadows a structured binding}}
+}
+
+// Test case 5: Ensure we don't break existing shadow detection for actual shadowing
+void foo5() {
+ int outer = 5; // expected-note {{previous declaration is here}} all-note {{previous declaration is here}}
+ auto [a, b] = Pair{1, 2}; // expected-note {{previous declaration is here}} all-note {{previous declaration is here}}
+
+ // This SHOULD still warn - it's actual shadowing within the lambda body
+ auto lambda = [outer, a](){ // expected-note {{variable 'outer' is explicitly captured here}} all-note {{variable 'outer' is explicitly captured here}} expected-note {{variable 'a' is explicitly captured here}} all-note {{variable 'a' is explicitly captured here}}
+ int outer = 10; // expected-warning {{declaration shadows a local variable}} all-warning {{declaration shadows a local variable}}
+ int a = 20; // expected-warning {{declaration shadows a structured binding}} all-warning {{declaration shadows a structured binding}}
+ };
+}
+
+} // namespace issue_68605
diff --git a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp
index d54b394df4eb8..2388c5f16e4ca 100644
--- a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp
+++ b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp
@@ -258,10 +258,15 @@ struct S {
};
int foo() {
- auto [a] = S{0}; // expected-note {{previous}} \
- // cxx14-warning {{decomposition declarations are a C++17 extension}}
+#ifdef AVOID
+ auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}}
+ [a = a] () { // No warning with basic -Wshadow due to uncaptured-local classification
+ }();
+#else
+ auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}} expected-note {{previous declaration is here}}
[a = a] () { // expected-warning {{declaration shadows a structured binding}}
}();
+#endif
}
}
More information about the cfe-commits
mailing list