Skip to content

Commit d8c2607

Browse files
[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@intel.com>
1 parent 44061d1 commit d8c2607

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ Bug Fixes in This Version
316316
- Builtin elementwise operators now accept vector arguments that have different
317317
qualifiers on their elements. For example, vector of 4 ``const float`` values
318318
and vector of 4 ``float`` values. (#GH155405)
319+
- Fixed inconsistent shadow warnings for lambda capture of structured bindings.
320+
Previously, ``[val = val]`` (regular parameter) produced no warnings with ``-Wshadow``
321+
while ``[a = a]`` (where ``a`` is from ``auto [a, b] = std::make_pair(1, 2)``)
322+
incorrectly produced warnings. Both cases now consistently show no warnings with
323+
``-Wshadow`` and show uncaptured-local warnings with ``-Wshadow-all``. (#GH68605)
319324
- Fixed a failed assertion with a negative limit parameter value inside of
320325
``__has_embed``. (#GH157842)
321326

clang/lib/Sema/SemaDecl.cpp

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8395,7 +8395,7 @@ static ShadowedDeclKind computeShadowedDeclKind(const NamedDecl *ShadowedDecl,
83958395
/// Return the location of the capture if the given lambda captures the given
83968396
/// variable \p VD, or an invalid source location otherwise.
83978397
static SourceLocation getCaptureLocation(const LambdaScopeInfo *LSI,
8398-
const VarDecl *VD) {
8398+
const ValueDecl *VD) {
83998399
for (const Capture &Capture : LSI->Captures) {
84008400
if (Capture.isVariableCapture() && Capture.getVariable() == VD)
84018401
return Capture.getLocation();
@@ -8492,7 +8492,9 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
84928492
if (isa<VarDecl>(D) && NewDC && isa<CXXMethodDecl>(NewDC)) {
84938493
if (const auto *RD = dyn_cast<CXXRecordDecl>(NewDC->getParent())) {
84948494
if (RD->isLambda() && OldDC->Encloses(NewDC->getLexicalParent())) {
8495-
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
8495+
// Handle both VarDecl and BindingDecl in lambda contexts
8496+
if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
8497+
const auto *VD = cast<ValueDecl>(ShadowedDecl);
84968498
const auto *LSI = cast<LambdaScopeInfo>(getCurFunction());
84978499
if (RD->getLambdaCaptureDefault() == LCD_None) {
84988500
// Try to avoid warnings for lambdas with an explicit capture
@@ -8521,18 +8523,27 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
85218523
return;
85228524
}
85238525
}
8524-
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl);
8525-
VD && VD->hasLocalStorage()) {
8526-
// A variable can't shadow a local variable in an enclosing scope, if
8527-
// they are separated by a non-capturing declaration context.
8528-
for (DeclContext *ParentDC = NewDC;
8529-
ParentDC && !ParentDC->Equals(OldDC);
8530-
ParentDC = getLambdaAwareParentOfDeclContext(ParentDC)) {
8531-
// Only block literals, captured statements, and lambda expressions
8532-
// can capture; other scopes don't.
8533-
if (!isa<BlockDecl>(ParentDC) && !isa<CapturedDecl>(ParentDC) &&
8534-
!isLambdaCallOperator(ParentDC)) {
8535-
return;
8526+
// Apply scoping logic to both VarDecl and BindingDecl with local storage
8527+
if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
8528+
bool HasLocalStorage = false;
8529+
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl))
8530+
HasLocalStorage = VD->hasLocalStorage();
8531+
else if (const auto *BD = dyn_cast<BindingDecl>(ShadowedDecl))
8532+
HasLocalStorage =
8533+
cast<VarDecl>(BD->getDecomposedDecl())->hasLocalStorage();
8534+
8535+
if (HasLocalStorage) {
8536+
// A variable can't shadow a local variable or binding in an enclosing
8537+
// scope, if they are separated by a non-capturing declaration
8538+
// context.
8539+
for (DeclContext *ParentDC = NewDC;
8540+
ParentDC && !ParentDC->Equals(OldDC);
8541+
ParentDC = getLambdaAwareParentOfDeclContext(ParentDC)) {
8542+
// Only block literals, captured statements, and lambda expressions
8543+
// can capture; other scopes don't.
8544+
if (!isa<BlockDecl>(ParentDC) && !isa<CapturedDecl>(ParentDC) &&
8545+
!isLambdaCallOperator(ParentDC))
8546+
return;
85368547
}
85378548
}
85388549
}
@@ -8579,7 +8590,8 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) {
85798590
const NamedDecl *ShadowedDecl = Shadow.ShadowedDecl;
85808591
// Try to avoid the warning when the shadowed decl isn't captured.
85818592
const DeclContext *OldDC = ShadowedDecl->getDeclContext();
8582-
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
8593+
if (isa<VarDecl, BindingDecl>(ShadowedDecl)) {
8594+
const auto *VD = cast<ValueDecl>(ShadowedDecl);
85838595
SourceLocation CaptureLoc = getCaptureLocation(LSI, VD);
85848596
Diag(Shadow.VD->getLocation(),
85858597
CaptureLoc.isInvalid() ? diag::warn_decl_shadow_uncaptured_local

clang/test/SemaCXX/PR68605.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %clang_cc1 -verify -fsyntax-only -std=c++20 -Wshadow %s
2+
// RUN: %clang_cc1 -verify=all -fsyntax-only -std=c++20 -Wshadow-all %s
3+
4+
// Test for issue #68605: Inconsistent shadow warnings for lambda capture of structured bindings.
5+
//
6+
// The issue was that structured binding lambda captures were incorrectly classified
7+
// as regular shadow warnings (shown with -Wshadow) while regular parameter captures
8+
// were classified as uncaptured-local warnings (shown only with -Wshadow-all).
9+
//
10+
// This test validates that both VarDecl and BindingDecl lambda captures now
11+
// behave consistently: no warnings with -Wshadow, but uncaptured-local warnings
12+
// with -Wshadow-all.
13+
14+
namespace std {
15+
template<typename T> T&& move(T&& t) { return static_cast<T&&>(t); }
16+
}
17+
18+
namespace issue_68605 {
19+
20+
// Simple pair-like struct for testing
21+
struct Pair {
22+
int first;
23+
int second;
24+
Pair(int f, int s) : first(f), second(s) {}
25+
};
26+
27+
// Test case 1: Regular parameter - consistent behavior
28+
void foo1(Pair val) { // all-note {{previous declaration is here}}
29+
[val = std::move(val)](){}(); // all-warning {{declaration shadows a local variable}}
30+
}
31+
32+
// Test case 2: Structured binding - now consistent with regular parameter
33+
void foo2(Pair val) {
34+
auto [a,b] = val; // all-note {{previous declaration is here}}
35+
[a = std::move(a)](){}(); // all-warning {{declaration shadows a structured binding}}
36+
}
37+
38+
// Test case 3: Multiple captures showing consistent behavior
39+
void foo3() {
40+
Pair data{42, 100};
41+
auto [id, value] = data; // all-note 2{{previous declaration is here}}
42+
43+
// Both show consistent uncaptured-local warnings with -Wshadow-all
44+
auto lambda1 = [id = id](){ return id; }; // all-warning {{declaration shadows a structured binding}}
45+
auto lambda2 = [value = value](){ return value; }; // all-warning {{declaration shadows a structured binding}}
46+
}
47+
48+
// Test case 4: Mixed scenario showing consistent behavior
49+
void foo4() {
50+
int regular_var = 10; // all-note {{previous declaration is here}}
51+
Pair pair_data{1, 2};
52+
auto [x, y] = pair_data; // all-note 2{{previous declaration is here}}
53+
54+
// All captures now show consistent uncaptured-local warnings with -Wshadow-all
55+
auto lambda1 = [regular_var = regular_var](){}; // all-warning {{declaration shadows a local variable}}
56+
auto lambda2 = [x = x](){}; // all-warning {{declaration shadows a structured binding}}
57+
auto lambda3 = [y = y](){}; // all-warning {{declaration shadows a structured binding}}
58+
}
59+
60+
// Test case 5: Ensure we don't break existing shadow detection for actual shadowing
61+
void foo5() {
62+
int outer = 5; // expected-note {{previous declaration is here}} all-note {{previous declaration is here}}
63+
auto [a, b] = Pair{1, 2}; // expected-note {{previous declaration is here}} all-note {{previous declaration is here}}
64+
65+
// This SHOULD still warn - it's actual shadowing within the lambda body
66+
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}}
67+
int outer = 10; // expected-warning {{declaration shadows a local variable}} all-warning {{declaration shadows a local variable}}
68+
int a = 20; // expected-warning {{declaration shadows a structured binding}} all-warning {{declaration shadows a structured binding}}
69+
};
70+
}
71+
72+
} // namespace issue_68605

clang/test/SemaCXX/warn-shadow-in-lambdas.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,15 @@ struct S {
258258
};
259259

260260
int foo() {
261-
auto [a] = S{0}; // expected-note {{previous}} \
262-
// cxx14-warning {{decomposition declarations are a C++17 extension}}
261+
#ifdef AVOID
262+
auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}}
263+
[a = a] () { // No warning with basic -Wshadow due to uncaptured-local classification
264+
}();
265+
#else
266+
auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}} expected-note {{previous declaration is here}}
263267
[a = a] () { // expected-warning {{declaration shadows a structured binding}}
264268
}();
269+
#endif
265270
}
266271

267272
}

0 commit comments

Comments
 (0)