Skip to content

Commit c0723fe

Browse files
committed
[Sema] Fix inconsistent shadow warnings for lambda capture of structured bindings
Lambda captures that shadow structured bindings 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 created inconsistent behavior between semantically equivalent code patterns. This change extends the existing lambda capture classification logic to handle BindingDecl consistently with VarDecl: - Lambda init captures of structured bindings now show as uncaptured-local - Regular variable declarations inside lambda bodies still show as shadow - All existing shadow warning functionality is preserved The fix ensures consistent behavior between: void func(std::pair<int,int> val) { [val = val](){}; } // no -Wshadow, warns with -Wshadow-all void func(std::pair<int,int> val) { auto [a,b] = val; [a = a](){}; } // no -Wshadow, warns with -Wshadow-all Previously the structured binding case incorrectly produced warnings with basic -Wshadow, preventing structured bindings from being used in lambda captures consistently with regular parameters.
1 parent 6aa3e64 commit c0723fe

File tree

2 files changed

+65
-40
lines changed

2 files changed

+65
-40
lines changed

clang/lib/Sema/SemaDecl.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8411,11 +8411,6 @@ NamedDecl *Sema::getShadowedDeclaration(const VarDecl *D,
84118411

84128412
NamedDecl *ShadowedDecl = R.getFoundDecl();
84138413

8414-
// Don't warn when lambda captures shadow structured bindings.
8415-
// This ensures consistency with regular parameter captures.
8416-
if (isa<BindingDecl>(ShadowedDecl) && D->isInitCapture())
8417-
return nullptr;
8418-
84198414
return isa<VarDecl, FieldDecl, BindingDecl>(ShadowedDecl) ? ShadowedDecl
84208415
: nullptr;
84218416
}
@@ -8485,6 +8480,7 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
84858480
if (isa<VarDecl>(D) && NewDC && isa<CXXMethodDecl>(NewDC)) {
84868481
if (const auto *RD = dyn_cast<CXXRecordDecl>(NewDC->getParent())) {
84878482
if (RD->isLambda() && OldDC->Encloses(NewDC->getLexicalParent())) {
8483+
// Handle lambda capture logic for both VarDecl and BindingDecl
84888484
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
84898485
const auto *LSI = cast<LambdaScopeInfo>(getCurFunction());
84908486
if (RD->getLambdaCaptureDefault() == LCD_None) {
@@ -8502,6 +8498,21 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
85028498
->ShadowingDecls.push_back({D, VD});
85038499
return;
85048500
}
8501+
} else if (isa<BindingDecl>(ShadowedDecl)) {
8502+
// Apply lambda capture logic only when D is actually a lambda capture
8503+
if (isa<VarDecl>(D) && cast<VarDecl>(D)->isInitCapture()) {
8504+
if (RD->getLambdaCaptureDefault() == LCD_None) {
8505+
// BindingDecls cannot be explicitly captured, so always treat as
8506+
// uncaptured
8507+
WarningDiag = diag::warn_decl_shadow_uncaptured_local;
8508+
} else {
8509+
// Same deferred handling as VarDecl
8510+
cast<LambdaScopeInfo>(getCurFunction())
8511+
->ShadowingDecls.push_back({D, ShadowedDecl});
8512+
return;
8513+
}
8514+
}
8515+
// For non-init-capture cases, fall through to regular shadow logic
85058516
}
85068517
if (isa<FieldDecl>(ShadowedDecl)) {
85078518
// If lambda can capture this, then emit default shadowing warning,
@@ -8514,10 +8525,17 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl,
85148525
return;
85158526
}
85168527
}
8517-
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl);
8518-
VD && VD->hasLocalStorage()) {
8519-
// A variable can't shadow a local variable in an enclosing scope, if
8520-
// they are separated by a non-capturing declaration context.
8528+
// Apply scoping logic to both VarDecl and BindingDecl
8529+
bool shouldApplyScopingLogic = false;
8530+
if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) {
8531+
shouldApplyScopingLogic = VD->hasLocalStorage();
8532+
} else if (isa<BindingDecl>(ShadowedDecl)) {
8533+
shouldApplyScopingLogic = true;
8534+
}
8535+
8536+
if (shouldApplyScopingLogic) {
8537+
// A variable can't shadow a local variable or binding in an enclosing
8538+
// scope, if they are separated by a non-capturing declaration context.
85218539
for (DeclContext *ParentDC = NewDC;
85228540
ParentDC && !ParentDC->Equals(OldDC);
85238541
ParentDC = getLambdaAwareParentOfDeclContext(ParentDC)) {
@@ -8583,6 +8601,12 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) {
85838601
Diag(CaptureLoc, diag::note_var_explicitly_captured_here)
85848602
<< Shadow.VD->getDeclName() << /*explicitly*/ 0;
85858603
Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration);
8604+
} else if (isa<BindingDecl>(ShadowedDecl)) {
8605+
// BindingDecls cannot be explicitly captured, so always uncaptured-local
8606+
Diag(Shadow.VD->getLocation(), diag::warn_decl_shadow_uncaptured_local)
8607+
<< Shadow.VD->getDeclName()
8608+
<< computeShadowedDeclKind(ShadowedDecl, OldDC) << OldDC;
8609+
Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration);
85868610
} else if (isa<FieldDecl>(ShadowedDecl)) {
85878611
Diag(Shadow.VD->getLocation(),
85888612
LSI->isCXXThisCaptured() ? diag::warn_decl_shadow

clang/test/SemaCXX/PR68605.cpp

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// 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
23

3-
// Test for issue #68605: False positive warning with `-Wshadow` when using
4-
// structured binding and lambda capture.
4+
// Test for issue #68605: Inconsistent shadow warnings for lambda capture of structured bindings.
55
//
6-
// The issue is that structured bindings should behave consistently with
7-
// regular variables when used in lambda captures - no shadow warning should
8-
// be emitted when a lambda capture variable has the same name as the captured
9-
// structured binding, just like with regular parameters.
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.
1013

1114
namespace std {
1215
template<typename T> T&& move(T&& t) { return static_cast<T&&>(t); }
@@ -21,50 +24,48 @@ struct Pair {
2124
Pair(int f, int s) : first(f), second(s) {}
2225
};
2326

24-
// Test case 1: Regular parameter - should NOT produce warning (baseline)
25-
void foo1(Pair val) {
26-
[val = std::move(val)](){}(); // No warning expected
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}}
2730
}
2831

29-
// Test case 2: Structured binding - should NOT produce warning
32+
// Test case 2: Structured binding - now consistent with regular parameter
3033
void foo2(Pair val) {
31-
auto [a,b] = val;
32-
[a = std::move(a)](){}(); // No warning - consistent with regular parameter behavior
34+
auto [a,b] = val; // all-note {{previous declaration is here}}
35+
[a = std::move(a)](){}(); // all-warning {{declaration shadows a structured binding}}
3336
}
3437

35-
// Test case 3: More complex example with multiple captures
38+
// Test case 3: Multiple captures showing consistent behavior
3639
void foo3() {
3740
Pair data{42, 100};
38-
auto [id, value] = data;
41+
auto [id, value] = data; // all-note 2{{previous declaration is here}}
3942

40-
// Both of these should NOT produce warnings
41-
auto lambda1 = [id = id](){ return id; }; // No warning
42-
auto lambda2 = [value = value](){ return value; }; // No warning
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}}
4346
}
4447

45-
// Test case 4: Mixed scenario with regular var and structured binding
48+
// Test case 4: Mixed scenario showing consistent behavior
4649
void foo4() {
47-
int regular_var = 10;
50+
int regular_var = 10; // all-note {{previous declaration is here}}
4851
Pair pair_data{1, 2};
49-
auto [x, y] = pair_data;
52+
auto [x, y] = pair_data; // all-note 2{{previous declaration is here}}
5053

51-
// Regular variable capture - no warning expected (current behavior)
52-
auto lambda1 = [regular_var = regular_var](){};
53-
54-
// Structured binding captures - should be consistent
55-
auto lambda2 = [x = x](){}; // No warning - consistent behavior
56-
auto lambda3 = [y = y](){}; // No warning - consistent behavior
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}}
5758
}
5859

5960
// Test case 5: Ensure we don't break existing shadow detection for actual shadowing
6061
void foo5() {
61-
int outer = 5; // expected-note {{previous declaration is here}}
62-
auto [a, b] = Pair{1, 2}; // expected-note {{previous declaration is here}}
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}}
6364

6465
// This SHOULD still warn - it's actual shadowing within the lambda body
65-
auto lambda = [outer, a](){ // expected-note {{variable 'outer' is explicitly captured here}}
66-
int outer = 10; // expected-warning {{declaration shadows a local variable}}
67-
int a = 20; // expected-warning {{declaration shadows a structured binding}}
66+
auto lambda = [outer, a](){ // expected-note {{variable 'outer' is explicitly captured here}} all-note {{variable 'outer' 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}}
6869
};
6970
}
7071

0 commit comments

Comments
 (0)