https://github.com/ivanmurashko updated https://github.com/llvm/llvm-project/pull/157667
>From 6aa3e6424865a5fc53489612ecb7f656f7165b9c Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Tue, 9 Sep 2025 13:42:46 +0100 Subject: [PATCH 1/8] [clang][Sema] Fix false positive -Wshadow with structured binding captures Previously, lambda init captures that captured structured bindings would incorrectly emit shadow warnings, even though regular parameter captures don't emit such warnings. This created inconsistent behavior: ```cpp void foo1(std::pair<int, int> val) { [val = val](){}(); // No warning (correct) } void foo2(std::pair<int, int> val) { auto [a, b] = val; [a = a](){}(); // Warning (incorrect) } ``` The fix modifies getShadowedDeclaration() for VarDecl to return nullptr when a lambda init capture would shadow a BindingDecl, ensuring consistent behavior between regular captures and structured binding captures. --- clang/lib/Sema/SemaDecl.cpp | 6 ++ clang/test/SemaCXX/PR68605.cpp | 71 +++++++++++++++++++ clang/test/SemaCXX/warn-shadow-in-lambdas.cpp | 5 +- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/PR68605.cpp diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 365ebb63b1559..311105f31e3e3 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8410,6 +8410,12 @@ NamedDecl *Sema::getShadowedDeclaration(const VarDecl *D, return nullptr; NamedDecl *ShadowedDecl = R.getFoundDecl(); + + // Don't warn when lambda captures shadow structured bindings. + // This ensures consistency with regular parameter captures. + if (isa<BindingDecl>(ShadowedDecl) && D->isInitCapture()) + return nullptr; + return isa<VarDecl, FieldDecl, BindingDecl>(ShadowedDecl) ? ShadowedDecl : nullptr; } diff --git a/clang/test/SemaCXX/PR68605.cpp b/clang/test/SemaCXX/PR68605.cpp new file mode 100644 index 0000000000000..90e4876b3e394 --- /dev/null +++ b/clang/test/SemaCXX/PR68605.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -verify -fsyntax-only -std=c++20 -Wshadow %s + +// Test for issue #68605: False positive warning with `-Wshadow` when using +// structured binding and lambda capture. +// +// The issue is that structured bindings should behave consistently with +// regular variables when used in lambda captures - no shadow warning should +// be emitted when a lambda capture variable has the same name as the captured +// structured binding, just like with regular parameters. + +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 - should NOT produce warning (baseline) +void foo1(Pair val) { + [val = std::move(val)](){}(); // No warning expected +} + +// Test case 2: Structured binding - should NOT produce warning +void foo2(Pair val) { + auto [a,b] = val; + [a = std::move(a)](){}(); // No warning - consistent with regular parameter behavior +} + +// Test case 3: More complex example with multiple captures +void foo3() { + Pair data{42, 100}; + auto [id, value] = data; + + // Both of these should NOT produce warnings + auto lambda1 = [id = id](){ return id; }; // No warning + auto lambda2 = [value = value](){ return value; }; // No warning +} + +// Test case 4: Mixed scenario with regular var and structured binding +void foo4() { + int regular_var = 10; + Pair pair_data{1, 2}; + auto [x, y] = pair_data; + + // Regular variable capture - no warning expected (current behavior) + auto lambda1 = [regular_var = regular_var](){}; + + // Structured binding captures - should be consistent + auto lambda2 = [x = x](){}; // No warning - consistent behavior + auto lambda3 = [y = y](){}; // No warning - consistent behavior +} + +// 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}} + auto [a, b] = Pair{1, 2}; // expected-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}} + int outer = 10; // expected-warning {{declaration shadows a local variable}} + int a = 20; // expected-warning {{declaration shadows a structured binding}} + }; +} + +} // namespace issue_68605 \ No newline at end of file diff --git a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp index d54b394df4eb8..2221c3f6d1049 100644 --- a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp +++ b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp @@ -258,9 +258,8 @@ struct S { }; int foo() { - auto [a] = S{0}; // expected-note {{previous}} \ - // cxx14-warning {{decomposition declarations are a C++17 extension}} - [a = a] () { // expected-warning {{declaration shadows a structured binding}} + auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}} + [a = a] () { // No warning - consistent with regular parameter captures }(); } >From c0723fefc075729f5f1526115c81e42a34c9c51b Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Wed, 10 Sep 2025 10:48:52 +0100 Subject: [PATCH 2/8] [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. --- clang/lib/Sema/SemaDecl.cpp | 42 ++++++++++++++++++----- clang/test/SemaCXX/PR68605.cpp | 63 +++++++++++++++++----------------- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 311105f31e3e3..ce2b3136e5ff4 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8411,11 +8411,6 @@ NamedDecl *Sema::getShadowedDeclaration(const VarDecl *D, NamedDecl *ShadowedDecl = R.getFoundDecl(); - // Don't warn when lambda captures shadow structured bindings. - // This ensures consistency with regular parameter captures. - if (isa<BindingDecl>(ShadowedDecl) && D->isInitCapture()) - return nullptr; - return isa<VarDecl, FieldDecl, BindingDecl>(ShadowedDecl) ? ShadowedDecl : nullptr; } @@ -8485,6 +8480,7 @@ 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())) { + // Handle lambda capture logic for both VarDecl and BindingDecl if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) { const auto *LSI = cast<LambdaScopeInfo>(getCurFunction()); if (RD->getLambdaCaptureDefault() == LCD_None) { @@ -8502,6 +8498,21 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, ->ShadowingDecls.push_back({D, VD}); return; } + } else if (isa<BindingDecl>(ShadowedDecl)) { + // Apply lambda capture logic only when D is actually a lambda capture + if (isa<VarDecl>(D) && cast<VarDecl>(D)->isInitCapture()) { + if (RD->getLambdaCaptureDefault() == LCD_None) { + // BindingDecls cannot be explicitly captured, so always treat as + // uncaptured + WarningDiag = diag::warn_decl_shadow_uncaptured_local; + } else { + // Same deferred handling as VarDecl + cast<LambdaScopeInfo>(getCurFunction()) + ->ShadowingDecls.push_back({D, ShadowedDecl}); + return; + } + } + // For non-init-capture cases, fall through to regular shadow logic } if (isa<FieldDecl>(ShadowedDecl)) { // If lambda can capture this, then emit default shadowing warning, @@ -8514,10 +8525,17 @@ 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. + // Apply scoping logic to both VarDecl and BindingDecl + bool shouldApplyScopingLogic = false; + if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) { + shouldApplyScopingLogic = VD->hasLocalStorage(); + } else if (isa<BindingDecl>(ShadowedDecl)) { + shouldApplyScopingLogic = true; + } + + if (shouldApplyScopingLogic) { + // 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)) { @@ -8583,6 +8601,12 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) { Diag(CaptureLoc, diag::note_var_explicitly_captured_here) << Shadow.VD->getDeclName() << /*explicitly*/ 0; Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration); + } else if (isa<BindingDecl>(ShadowedDecl)) { + // BindingDecls cannot be explicitly captured, so always uncaptured-local + Diag(Shadow.VD->getLocation(), diag::warn_decl_shadow_uncaptured_local) + << Shadow.VD->getDeclName() + << computeShadowedDeclKind(ShadowedDecl, OldDC) << OldDC; + Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration); } else if (isa<FieldDecl>(ShadowedDecl)) { Diag(Shadow.VD->getLocation(), LSI->isCXXThisCaptured() ? diag::warn_decl_shadow diff --git a/clang/test/SemaCXX/PR68605.cpp b/clang/test/SemaCXX/PR68605.cpp index 90e4876b3e394..b981f6c4eecce 100644 --- a/clang/test/SemaCXX/PR68605.cpp +++ b/clang/test/SemaCXX/PR68605.cpp @@ -1,12 +1,15 @@ // 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: False positive warning with `-Wshadow` when using -// structured binding and lambda capture. +// Test for issue #68605: Inconsistent shadow warnings for lambda capture of structured bindings. // -// The issue is that structured bindings should behave consistently with -// regular variables when used in lambda captures - no shadow warning should -// be emitted when a lambda capture variable has the same name as the captured -// structured binding, just like with regular parameters. +// 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); } @@ -21,50 +24,48 @@ struct Pair { Pair(int f, int s) : first(f), second(s) {} }; -// Test case 1: Regular parameter - should NOT produce warning (baseline) -void foo1(Pair val) { - [val = std::move(val)](){}(); // No warning expected +// 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 - should NOT produce warning +// Test case 2: Structured binding - now consistent with regular parameter void foo2(Pair val) { - auto [a,b] = val; - [a = std::move(a)](){}(); // No warning - consistent with regular parameter behavior + auto [a,b] = val; // all-note {{previous declaration is here}} + [a = std::move(a)](){}(); // all-warning {{declaration shadows a structured binding}} } -// Test case 3: More complex example with multiple captures +// Test case 3: Multiple captures showing consistent behavior void foo3() { Pair data{42, 100}; - auto [id, value] = data; + auto [id, value] = data; // all-note 2{{previous declaration is here}} - // Both of these should NOT produce warnings - auto lambda1 = [id = id](){ return id; }; // No warning - auto lambda2 = [value = value](){ return value; }; // No warning + // 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 with regular var and structured binding +// Test case 4: Mixed scenario showing consistent behavior void foo4() { - int regular_var = 10; + int regular_var = 10; // all-note {{previous declaration is here}} Pair pair_data{1, 2}; - auto [x, y] = pair_data; + auto [x, y] = pair_data; // all-note 2{{previous declaration is here}} - // Regular variable capture - no warning expected (current behavior) - auto lambda1 = [regular_var = regular_var](){}; - - // Structured binding captures - should be consistent - auto lambda2 = [x = x](){}; // No warning - consistent behavior - auto lambda3 = [y = y](){}; // No warning - consistent behavior + // 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}} - auto [a, b] = Pair{1, 2}; // expected-note {{previous declaration is here}} + 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}} - int outer = 10; // expected-warning {{declaration shadows a local variable}} - int a = 20; // expected-warning {{declaration shadows a structured binding}} + auto lambda = [outer, a](){ // expected-note {{variable 'outer' is explicitly captured here}} all-note {{variable 'outer' 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}} }; } >From 74a2ff78c5bf82666696d8cf08c07f6e932ee07b Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Wed, 10 Sep 2025 13:03:04 +0100 Subject: [PATCH 3/8] Fix warn-shadow-in-lambdas test for structured bindings --- clang/test/SemaCXX/warn-shadow-in-lambdas.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp index 2221c3f6d1049..2388c5f16e4ca 100644 --- a/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp +++ b/clang/test/SemaCXX/warn-shadow-in-lambdas.cpp @@ -258,9 +258,15 @@ struct S { }; int foo() { +#ifdef AVOID auto [a] = S{0}; // cxx14-warning {{decomposition declarations are a C++17 extension}} - [a = a] () { // No warning - consistent with regular parameter captures + [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 } } >From 27fc9b7095e6ab993782e121e1ff1a3776b2ab0a Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Wed, 10 Sep 2025 15:43:36 +0100 Subject: [PATCH 4/8] [Sema] Consolidate VarDecl and BindingDecl shadow detection using ValueDecl Consolidate the handling of VarDecl and BindingDecl in shadow detection by using their common base class ValueDecl. --- clang/lib/Sema/SemaDecl.cpp | 79 +++++++++++++++------------------- clang/test/SemaCXX/PR68605.cpp | 2 +- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index ce2b3136e5ff4..193db2726273a 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8382,7 +8382,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(); @@ -8410,7 +8410,6 @@ NamedDecl *Sema::getShadowedDeclaration(const VarDecl *D, return nullptr; NamedDecl *ShadowedDecl = R.getFoundDecl(); - return isa<VarDecl, FieldDecl, BindingDecl>(ShadowedDecl) ? ShadowedDecl : nullptr; } @@ -8480,9 +8479,11 @@ 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())) { - // Handle lambda capture logic for both VarDecl and BindingDecl - 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 // list. Warn only when the lambda captures the shadowed decl @@ -8498,21 +8499,6 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, ->ShadowingDecls.push_back({D, VD}); return; } - } else if (isa<BindingDecl>(ShadowedDecl)) { - // Apply lambda capture logic only when D is actually a lambda capture - if (isa<VarDecl>(D) && cast<VarDecl>(D)->isInitCapture()) { - if (RD->getLambdaCaptureDefault() == LCD_None) { - // BindingDecls cannot be explicitly captured, so always treat as - // uncaptured - WarningDiag = diag::warn_decl_shadow_uncaptured_local; - } else { - // Same deferred handling as VarDecl - cast<LambdaScopeInfo>(getCurFunction()) - ->ShadowingDecls.push_back({D, ShadowedDecl}); - return; - } - } - // For non-init-capture cases, fall through to regular shadow logic } if (isa<FieldDecl>(ShadowedDecl)) { // If lambda can capture this, then emit default shadowing warning, @@ -8525,25 +8511,32 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, return; } } - // Apply scoping logic to both VarDecl and BindingDecl - bool shouldApplyScopingLogic = false; - if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) { - shouldApplyScopingLogic = VD->hasLocalStorage(); - } else if (isa<BindingDecl>(ShadowedDecl)) { - shouldApplyScopingLogic = true; - } - - if (shouldApplyScopingLogic) { - // 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; + // 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 { + // For BindingDecl, apply the same logic as + // VarDecl::hasLocalStorage(): local storage means not at file context + hasLocalStorage = !ShadowedDecl->getLexicalDeclContext() + ->getRedeclContext() + ->isFileContext(); + } + + 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; + } } } } @@ -8590,8 +8583,10 @@ 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 : diag::warn_decl_shadow) @@ -8601,12 +8596,6 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) { Diag(CaptureLoc, diag::note_var_explicitly_captured_here) << Shadow.VD->getDeclName() << /*explicitly*/ 0; Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration); - } else if (isa<BindingDecl>(ShadowedDecl)) { - // BindingDecls cannot be explicitly captured, so always uncaptured-local - Diag(Shadow.VD->getLocation(), diag::warn_decl_shadow_uncaptured_local) - << Shadow.VD->getDeclName() - << computeShadowedDeclKind(ShadowedDecl, OldDC) << OldDC; - Diag(ShadowedDecl->getLocation(), diag::note_previous_declaration); } else if (isa<FieldDecl>(ShadowedDecl)) { Diag(Shadow.VD->getLocation(), LSI->isCXXThisCaptured() ? diag::warn_decl_shadow diff --git a/clang/test/SemaCXX/PR68605.cpp b/clang/test/SemaCXX/PR68605.cpp index b981f6c4eecce..12e8216d8eaa2 100644 --- a/clang/test/SemaCXX/PR68605.cpp +++ b/clang/test/SemaCXX/PR68605.cpp @@ -63,7 +63,7 @@ void foo5() { 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}} + 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}} }; >From 578e61b7aebbfa9213a28f3dabd29708112ce2a5 Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Wed, 10 Sep 2025 18:39:24 +0100 Subject: [PATCH 5/8] [Clang] Simplify BindingDecl scoping logic using getDecomposedDecl()->hasLocalStorage() --- clang/lib/Sema/SemaDecl.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 193db2726273a..256e57cea3b45 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8513,18 +8513,15 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, } // Apply scoping logic to both VarDecl and BindingDecl with local storage if (isa<VarDecl, BindingDecl>(ShadowedDecl)) { - bool hasLocalStorage = false; + bool HasLocalStorage = false; if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) { - hasLocalStorage = VD->hasLocalStorage(); - } else { - // For BindingDecl, apply the same logic as - // VarDecl::hasLocalStorage(): local storage means not at file context - hasLocalStorage = !ShadowedDecl->getLexicalDeclContext() - ->getRedeclContext() - ->isFileContext(); + HasLocalStorage = VD->hasLocalStorage(); + } else if (const auto *BD = dyn_cast<BindingDecl>(ShadowedDecl)) { + HasLocalStorage = + cast<VarDecl>(BD->getDecomposedDecl())->hasLocalStorage(); } - if (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. >From 13a6db586a6187803dd3a066154c565ff05df3e6 Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Wed, 10 Sep 2025 21:55:52 +0100 Subject: [PATCH 6/8] [Clang] Add release note for consistent structured binding shadow warnings --- clang/docs/ReleaseNotes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index a20b1ab298f9c..dbfdb79193377 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -308,6 +308,11 @@ Bug Fixes in This Version - Builtin elementwise operators now accept vector arguments that have different 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) Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >From e1c4dc206ee22006682d9e5a0f3f6da168b92f40 Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Thu, 11 Sep 2025 14:33:29 +0100 Subject: [PATCH 7/8] Apply suggestions from code review Code style fixes applied Co-authored-by: Mariya Podchishchaeva <mariya.podchishcha...@intel.com> --- clang/lib/Sema/SemaDecl.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 256e57cea3b45..441f7699ae49d 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -8483,7 +8483,6 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, 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 // list. Warn only when the lambda captures the shadowed decl @@ -8514,12 +8513,11 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, // 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)) { + if (const auto *VD = dyn_cast<VarDecl>(ShadowedDecl)) HasLocalStorage = VD->hasLocalStorage(); - } else if (const auto *BD = dyn_cast<BindingDecl>(ShadowedDecl)) { + 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 @@ -8531,9 +8529,8 @@ void Sema::CheckShadow(NamedDecl *D, NamedDecl *ShadowedDecl, // Only block literals, captured statements, and lambda expressions // can capture; other scopes don't. if (!isa<BlockDecl>(ParentDC) && !isa<CapturedDecl>(ParentDC) && - !isLambdaCallOperator(ParentDC)) { + !isLambdaCallOperator(ParentDC)) return; - } } } } @@ -8583,7 +8580,6 @@ void Sema::DiagnoseShadowingLambdaDecls(const LambdaScopeInfo *LSI) { 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 : diag::warn_decl_shadow) >From ef25bd24d475fa75c038a743ff64b4c36d7f7241 Mon Sep 17 00:00:00 2001 From: Ivan Murashko <ivan.muras...@gmail.com> Date: Thu, 11 Sep 2025 15:58:03 +0100 Subject: [PATCH 8/8] [Clang] Add missing newline at end of PR68605.cpp test file --- clang/test/SemaCXX/PR68605.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/SemaCXX/PR68605.cpp b/clang/test/SemaCXX/PR68605.cpp index 12e8216d8eaa2..97eb858b77246 100644 --- a/clang/test/SemaCXX/PR68605.cpp +++ b/clang/test/SemaCXX/PR68605.cpp @@ -69,4 +69,4 @@ void foo5() { }; } -} // namespace issue_68605 \ No newline at end of file +} // namespace issue_68605 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits