Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

ivanmurashko
Copy link
Contributor

@ivanmurashko ivanmurashko commented Sep 9, 2025

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:

  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.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Sep 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 9, 2025

@llvm/pr-subscribers-clang

Author: Ivan Murashko (ivanmurashko)

Changes

Fixes #68605.

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:

void foo1(std::pair&lt;int, int&gt; val) {
  [val = std::move(val)](){}();  // No warning (correct)
}

void foo2(std::pair&lt;int, int&gt; val) {
  auto [a, b] = val;
  [a = std::move(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.


Full diff: https://github.com/llvm/llvm-project/pull/157667.diff

2 Files Affected:

  • (modified) clang/lib/Sema/SemaDecl.cpp (+6)
  • (added) clang/test/SemaCXX/PR68605.cpp (+71)
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

…tures

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.
@ivanmurashko ivanmurashko force-pushed the fix-structured-binding-lambda-shadow-68605 branch from 1e8d884 to 6aa3e64 Compare September 9, 2025 13:34
@zwuis
Copy link
Contributor

zwuis commented Sep 9, 2025

Can you tell why there is false positive with structured bindings only, but without variables?

@zwuis
Copy link
Contributor

zwuis commented Sep 9, 2025

IIUC we shouldn't emit any warning on "lambda captures shadow something". It is a better approach for me that removing emitting this warning and performing code cleanup (there must be some checks preventing false positive with shadowing variables).

@ivanmurashko
Copy link
Contributor Author

ivanmurashko commented Sep 9, 2025

Can you tell why there is false positive with structured bindings only, but without variables?

Structured bindings (auto [a, b] = pair) create BindingDecl AST nodes, while regular variables create VarDecl nodes. Both actually trigger shadow warnings in lambda captures, but they get classified into different warning categories:

  • Regular variables: VarDecl shadowing VarDecl -> classified as -Wshadow-uncaptured-local (only shown with -Wshadow-all)
  • Structured bindings: VarDecl shadowing BindingDecl -> classified as -Wshadow (shown with basic -Wshadow)

This makes regular variables appear to "not warn" in typical usage (since most users use -Wshadow, not -Wshadow-all), while structured bindings always warn with basic -Wshadow. The "false positive" occurs because users expect consistent behavior between these equivalent patterns:

  [val = val]  // No warning with -Wshadow (classified as uncaptured-local)
  [a = a]      // Warning with -Wshadow (classified as regular shadow) 

My fix was focused on the simplest solution to make behavior consistent for the most popular case (basic -Wshadow). A proper fix should probably preserve the uncaptured-local warning classification for structured bindings rather than suppressing all shadow warnings, but that would require more complex changes to the warning classification logic.

@ivanmurashko
Copy link
Contributor Author

ivanmurashko commented Sep 9, 2025

IIUC we shouldn't emit any warning on "lambda captures shadow something". It is a better approach for me that removing emitting this warning and performing code cleanup (there must be some checks preventing false positive with shadowing variables).

@zwuis You're right! That's a much cleaner approach. Lambda init captures like [val = val] and [a = a] are intentional, explicit syntax - not accidental shadowing that needs warnings. I could implement this broader fix by simply removing the BindingDecl condition:

  // Before (targeted fix):
  if (isa<BindingDecl>(ShadowedDecl) && D->isInitCapture())
    return nullptr;

  // After (broader fix):  
  if (D->isInitCapture())
    return nullptr;

This single-line change eliminates all shadow warnings for lambda init captures across all warning flags (-Wshadow, -Wshadow-all, -Wshadow-uncaptured-local), providing completely consistent behavior and removing an entire category of false positives.

The fix is much simpler conceptually - lambda captures are deliberate syntax, so no shadow warnings should apply to them at all.

What do you think about this approach? It addresses both the original issue and provides the code cleanup you mentioned.

@zwuis
Copy link
Contributor

zwuis commented Sep 10, 2025

Oh, I missed "-Wshadow-uncaptured-local". Thanks for pointing out!

IIUC we shouldn't emit any warning on "lambda captures shadow something". It is a better approach for me that removing emitting this warning and performing code cleanup (there must be some checks preventing false positive with shadowing variables).

So this is wrong.

My fix was focused on the simplest solution to make behavior consistent for the most popular case (basic -Wshadow). A proper fix should probably preserve the uncaptured-local warning classification for structured bindings rather than suppressing all shadow warnings, but that would require more complex changes to the warning classification logic.

So current fix will introduce false negative with "-Wshadow-uncaptured-local" and shadowing structured bindings, and, hide the underlying issue. It would be great to find the difference of handling shadowing VarDecl and BindingDecl.

…red 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.
@ivanmurashko
Copy link
Contributor Author

So current fix will introduce false negative with "-Wshadow-uncaptured-local" and shadowing structured bindings, and, hide the underlying issue. It would be great to find the difference of handling shadowing VarDecl and BindingDecl.

The new commit c0723fe addresses this by extending the existing lambda capture classification logic at lines 8501-8515 in clang/lib/Sema/SemaDecl.cpp.

Previously, VarDecl had lambda capture handling (lines 8484-8500) while BindingDecl fell through to regular shadow detection, causing inconsistent warning classification. The fix ensures both VarDecl and BindingDecl follow the same lambda capture code path, making [val = val] and [a = a] (where a is from structured binding) behave identically.

Copy link
Contributor

@zwuis zwuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! It looks better.

Please add a release note entry to "clang/docs/ReleaseNotes.rst".

@@ -8410,6 +8410,7 @@ NamedDecl *Sema::getShadowedDeclaration(const VarDecl *D,
return nullptr;

NamedDecl *ShadowedDecl = R.getFoundDecl();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unrelated.

Suggested change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unrelated.

Restored at 27fc9b7

…ueDecl

Consolidate the handling of VarDecl and BindingDecl in shadow detection
by using their common base class ValueDecl.
@zwuis zwuis requested review from cor3ntin and shafik September 11, 2025 00:53
@zwuis
Copy link
Contributor

zwuis commented Sep 11, 2025

LGTM if PR description is updated.

@ivanmurashko
Copy link
Contributor Author

LGTM if PR description is updated.

Ah, I forgot to update the PR description, that was just fixed

Copy link
Contributor

@Fznamznon Fznamznon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just style nits, otherwise LGTM

};
}

} // namespace issue_68605
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline at the end of the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed at ef25bd2

@ivanmurashko
Copy link
Contributor Author

ivanmurashko commented Sep 13, 2025

Hi everyone, As the PR has been already accepted, I'll wait until Monday (15 Sep) to provide additional time for review. If there are no further comments by then, I'll proceed with merging the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

False positive warning with -Wshadow when using structured binding and lambda capture
4 participants