Skip to content

[clang] Correct FixIt ranges for unused capture warnings #141148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented May 22, 2025

Fixes #106445 by using the lexer to find the correct range for the removal FixIts. Previously the ranges that were generated assuming no unsurprising formatting, which for the most part works. Being correct in all cases requires using the lexer to find the bounding tokens for the region to remove.

As part of this it adds Sema::getRangeForNextToken to wrap Lexer::findNextToken.

Fixes llvm#106445 by using the lexer to find the correct range
for the removal FixIts. Previously the ranges that were
generated assuming no unsurprising formatting, which for
the most part works. Being correct in all cases requires
using the lexer to find the bounding tokens for the region
to remove.

As part of this it adds Sema::getRangeForNextToken to wrap
Lexer::findNextToken.
@ojhunt ojhunt requested a review from cor3ntin May 22, 2025 22:10
@ojhunt ojhunt self-assigned this May 22, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 22, 2025
@llvmbot
Copy link
Member

llvmbot commented May 22, 2025

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

Fixes #106445 by using the lexer to find the correct range for the removal FixIts. Previously the ranges that were generated assuming no unsurprising formatting, which for the most part works. Being correct in all cases requires using the lexer to find the bounding tokens for the region to remove.

As part of this it adds Sema::getRangeForNextToken to wrap Lexer::findNextToken.


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

4 Files Affected:

  • (modified) clang/include/clang/Sema/Sema.h (+6)
  • (modified) clang/lib/Sema/Sema.cpp (+15)
  • (modified) clang/lib/Sema/SemaLambda.cpp (+20-6)
  • (added) clang/test/FixIt/fixit-unused-lambda-capture-trailing-tokens.cpp (+250)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a994b845e11fc..f83047d63adab 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -972,6 +972,12 @@ class Sema final : public SemaBase {
   /// Calls \c Lexer::getLocForEndOfToken()
   SourceLocation getLocForEndOfToken(SourceLocation Loc, unsigned Offset = 0);
 
+  /// Calls \c Lexer::findNextToken() to find the next token, and if the
+  /// locations of both ends of the token can be resolved it return that
+  /// range; Otherwise it returns an invalid SourceRange.
+  SourceRange getRangeForNextToken(SourceLocation Loc,
+                                   bool IncludeComments = false);
+
   /// Retrieve the module loader associated with the preprocessor.
   ModuleLoader &getModuleLoader() const;
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 1901d19b14dfc..9f706579b1b79 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -84,6 +84,21 @@ SourceLocation Sema::getLocForEndOfToken(SourceLocation Loc, unsigned Offset) {
   return Lexer::getLocForEndOfToken(Loc, Offset, SourceMgr, LangOpts);
 }
 
+SourceRange Sema::getRangeForNextToken(SourceLocation Loc,
+                                       bool IncludeComments) {
+  if (!Loc.isValid())
+    return SourceRange();
+  std::optional<Token> NextToken =
+      Lexer::findNextToken(Loc, SourceMgr, LangOpts, IncludeComments);
+  if (!NextToken)
+    return SourceRange();
+  SourceLocation TokenStart = NextToken->getLocation();
+  SourceLocation TokenEnd = NextToken->getLastLoc();
+  if (!TokenStart.isValid() || !TokenEnd.isValid())
+    return SourceRange();
+  return SourceRange(TokenStart, TokenEnd);
+}
+
 ModuleLoader &Sema::getModuleLoader() const { return PP.getModuleLoader(); }
 
 DarwinSDKInfo *
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index aad16290422f5..d55dbf82ffdc0 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -2164,15 +2164,29 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
           bool IsLast = (I + 1) == LSI->NumExplicitCaptures;
           SourceRange FixItRange;
           if (CaptureRange.isValid()) {
+            auto GetTrailingEndLocation = [&](SourceLocation StartPoint) {
+              SourceRange NextToken =
+                  getRangeForNextToken(StartPoint, /*IncludeComments=*/true);
+              if (!NextToken.isValid())
+                return SourceLocation();
+              // Return the last location preceding the next token
+              return NextToken.getBegin().getLocWithOffset(-1);
+            };
             if (!CurHasPreviousCapture && !IsLast) {
               // If there are no captures preceding this capture, remove the
-              // following comma.
-              FixItRange = SourceRange(CaptureRange.getBegin(),
-                                       getLocForEndOfToken(CaptureRange.getEnd()));
+              // trailing comma and anything up to the next token
+              SourceRange CommaRange =
+                  getRangeForNextToken(CaptureRange.getEnd());
+              SourceLocation FixItEnd =
+                  GetTrailingEndLocation(CommaRange.getBegin());
+              FixItRange = SourceRange(CaptureRange.getBegin(), FixItEnd);
             } else {
-              // Otherwise, remove the comma since the last used capture.
-              FixItRange = SourceRange(getLocForEndOfToken(PrevCaptureLoc),
-                                       CaptureRange.getEnd());
+              // Otherwise, remove the comma since the last used capture, and
+              // anything up to the next token
+              SourceLocation FixItStart = getLocForEndOfToken(PrevCaptureLoc);
+              SourceLocation FixItEnd =
+                  GetTrailingEndLocation(CaptureRange.getEnd());
+              FixItRange = SourceRange(FixItStart, FixItEnd);
             }
           }
 
diff --git a/clang/test/FixIt/fixit-unused-lambda-capture-trailing-tokens.cpp b/clang/test/FixIt/fixit-unused-lambda-capture-trailing-tokens.cpp
new file mode 100644
index 0000000000000..e2c2cdb6a2d92
--- /dev/null
+++ b/clang/test/FixIt/fixit-unused-lambda-capture-trailing-tokens.cpp
@@ -0,0 +1,250 @@
+// RUN: cp %s %t
+// RUN: %clang_cc1 -x c++ -Wunused-lambda-capture -Wno-unused-value -std=c++1z -fixit %t
+// RUN: grep -v CHECK %t | FileCheck %s
+
+
+#define MACRO_CAPTURE(...) __VA_ARGS__
+int main() {
+    int a = 0, b = 0, c = 0;
+    auto F0 = [a, &b]() mutable {
+    // CHECK: auto F0 = [a]()
+        a++;
+    };
+
+    auto F1 = [&a, &b]() {
+    // CHECK: auto F1 = []() {
+    };
+
+    auto F2 = [&a, b]() {
+    // CHECK: auto F2 = []() {
+    };
+
+    auto F3 = [&a,
+         &b]() {
+    // CHECK: auto F3 = []() {
+    };
+
+    auto F4 = [&a
+        , &b]() {
+    // CHECK: auto F4 = []() {
+    };
+    auto F5 = [&a ,&b]()  {
+    // CHECK: auto F5 = []() {
+    };
+
+    auto F0a = [a, &b]() mutable {
+    // CHECK: auto F0a = [a]() mutable {
+        a++;
+    };
+
+    auto F1a = [&a, &b]() {
+    // CHECK: auto F1a = [&a]() {
+        a++;
+    };
+
+    auto F2a = [&a, b]() {
+    // CHECK: auto F2a = [&a]() {
+        a++;
+    };
+
+    auto F3a = [&a,
+         &b]() {
+    // CHECK: auto F3a = [&a]() {
+        a++;
+    };
+
+    auto F4a = [&a
+        , &b]() {
+    // CHECK: auto F4a = [&a]() {
+        a++;
+    };
+
+    auto F5a = [&a ,&b]() {
+    // CHECK: auto F5a = [&a]() {
+        a++;
+    };
+    auto F0b = [a, &b]() mutable {
+    // CHECK: auto F0b = [ &b]() mutable
+        b++;
+    };
+
+    auto F1b = [&a, &b]() {
+    // CHECK: auto F1b = [ &b]() {
+        b++;
+    };
+
+    auto F2b = [&a, b]() mutable {
+    // CHECK: auto F2b = [ b]() mutable {
+        b++;
+    };
+
+    auto F3b = [&a,
+         &b]() {
+    // CHECK: auto F3b = [ &b]() {
+        b++;
+    };
+
+    auto F4b = [&a
+        , &b]() {
+    // CHECK: auto F4b = [ &b]() {
+        b++;
+    };
+    auto F5b = [&a ,&b]() {
+    // CHECK: auto F5b = [&b]() {
+        b++;
+    };
+
+    auto F6 = [&a, &b, &c]() {
+    // CHECK: auto F6 = [&a, &b]() {
+        a++;
+        b++;
+    };
+    auto F7 = [&a, &b, &c]() {
+    // CHECK: auto F7 = [&a, &c]() {
+        a++;
+        c++;
+    };
+    auto F8 = [&a, &b, &c]() {
+    // CHECK: auto F8 = [ &b, &c]() {
+        b++;
+        c++;
+    };
+    auto F9 = [&a, &b    , &c]() {
+    // CHECK: auto F9 = [&a   , &c]() {
+        a++;
+        c++;
+    };
+    auto F10 = [&a,
+         &b, &c]() {
+    // CHECK: auto F10 = [&a, &c]() {
+        a++;
+        c++;
+    };
+    auto F11 = [&a,  &b  ,
+         &c]() {
+    // CHECK: auto F11 = [&a ,
+    // CHECK-NEXT:      &c]() {
+        a++;
+        c++;
+    };
+    auto F12 = [a = 0,  b  ,
+         c]() mutable {
+    // CHECK: auto F12 = [ b  ,
+    // CHECK-NEXT:     c]() mutable {
+        b++;
+        c++;
+    };
+    auto F13 = [a,  b = 0 ,
+         c]() mutable {
+    // CHECK: auto F13 = [a ,
+    // CHECK-NEXT:     c]() mutable {
+        a++;
+        c++;
+    };
+    auto F14 = [a,  b ,
+         c
+        = 0]() mutable {
+    // CHECK: auto F14 = [a,  b]() mutable {
+        a++;
+        b++;
+    };
+
+    // We want to remove everything including the comment
+    // as well as the comma following the capture of `a`
+    auto F15 = [&a /* comment */, &b]() {
+    // CHECK: auto F15 = [ &b]() {
+        b++;
+    };
+
+    // The comment preceding the first capture remains. This is more
+    // by design of the fixit logic than anything else, but we should
+    // consider the preceding comment might actually be a comment for
+    // the entire capture set, so maybe we do want it to hang around
+    auto F16 = [/* comment */ &a , &b]() {
+    // CHECK: auto F16 = [/* comment */ &b]() {
+        b++;
+    };
+
+    auto F16b = [&a ,    /* comment */ &b]() {
+    // CHECK: auto F16b = [ /* comment */ &b]() {
+        b++;
+    };
+
+    auto F17 = [&a /* comment */, &b]() {
+    // CHECK: auto F17 = [&a]() {
+        a++;
+    };
+
+    auto F18 = [&a , /* comment */ &b]() {
+    // CHECK: auto F18 = [&a]() {
+        a++;
+    };
+    
+    auto F19 = [&a /* comment */, &b /* comment */]() {
+    // CHECK: auto F19 = [&a /* comment */]() {
+        a++;
+    };
+
+    auto F20 = [MACRO_CAPTURE(&a, &b)]() {
+    // CHECK: auto F20 = [MACRO_CAPTURE(&a, &b)]() {
+    };
+
+    auto F21 = [MACRO_CAPTURE(&a), &b]() {
+    // CHECK: auto F21 = []() {
+    };
+
+    auto F22 = [MACRO_CAPTURE(&a,) &b]() {
+    // CHECK: auto F22 = [MACRO_CAPTURE(&a,) &b]() {
+    };
+    auto F23 = [&a MACRO_CAPTURE(, &b)]() {
+    // CHECK: auto F23 = []() {
+    };
+    auto F24 = [&a, MACRO_CAPTURE(&b)]() {
+    // CHECK: auto F24 = []() {
+    };
+
+    auto F20a = [MACRO_CAPTURE(&a, &b)]() {
+    // CHECK: auto F20a = [MACRO_CAPTURE(&a, &b)]() {
+      a++;
+    };
+
+    auto F21a = [MACRO_CAPTURE(&a), &b]() {
+    // CHECK: auto F21a = [MACRO_CAPTURE(&a)]() {
+      a++;
+    };
+
+    auto F22a = [MACRO_CAPTURE(&a,) &b]() {
+    // Cauto F22a = [MACRO_CAPTURE(&a,) &b]() {
+      a++;
+    };
+    auto F23a = [&a MACRO_CAPTURE(, &b)]() {
+    // CHECK: auto F23a = [&a]() {
+      a++;
+    };
+    auto F24a = [&a, MACRO_CAPTURE(&b)]() {
+    // CHECK: auto F24a = [&a]() {
+      a++;
+    };
+    auto F20b = [MACRO_CAPTURE(&a, &b)]() {
+    // CHECK: auto F20b = [MACRO_CAPTURE(&a, &b)]() {
+      b++;
+    };
+
+    auto F21b = [MACRO_CAPTURE(&a), &b]() {
+    // CHECK: auto F21b = [ &b]() {
+      b++;
+    };
+
+    auto F22b = [MACRO_CAPTURE(&a,) &b]() {
+    // CHECK: auto F22b = [MACRO_CAPTURE(&a,) &b]() {
+      b++;
+    };
+    auto F23b = [&a MACRO_CAPTURE(, &b)]() {
+    // CHECK: auto F23b = [(, &b)]() {
+      b++;
+    };
+    auto F24b = [&a, MACRO_CAPTURE(&b)]() {
+    // CHECK: auto F24b = [ MACRO_CAPTURE(&b)]() {
+      b++;
+    };
+}

if (!Loc.isValid())
return SourceRange();
std::optional<Token> NextToken =
Lexer::findNextToken(Loc, SourceMgr, LangOpts, IncludeComments);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use findLocationAfterToken here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm using this one function for two purposes:

  • Finding the (presumed) comma and stepping over it
  • Finding the start of the next token

It seemed like a reasonable approach, and given we're producing a fixit diagnostic I don't think the perf matters.

Basically what I'm trying to do is say given an unused capture:

auto foo = [a, unused,           b] ... // just a pile of white space for illustrative purposes
auto bar = [a, unused, /* ... */ b] ...
auto wibble = [a,  /* ... */  unused, b] ...
auto thingy = [a,  unused /* ... */, b] ...

produce fixits that result in

auto foo = [a, b] ... // just a pile of white space for illustrative purposes
auto bar = [a, /* ... */ b] ...
auto wibble = [a, b] ...
auto thingy = [a, b] ...

and for that what we want to do is find the end of the comma, and the start of the next token, simply using the "findNextToken" to just get those tokens seems reasonable.

Of course as we discussed in discord, that function doesn't handle macros, and handling this correctly for macros would imply the Sema "wrapper" excludes macro wrappers (so would not just be a wrapper) or every user has to check for macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cor3ntin ping for your copious spare time :D

Comment on lines +97 to +98
if (!TokenStart.isValid() || !TokenEnd.isValid())
return SourceRange();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check TokenEnd.isMacroID() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup! But that kind of results in this no longer being just a wrapper so I'm not sure if that's "good" - I might make it a flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cor3ntin thoughts? basically should the Lexer function take a "allow tokens that intersect macros", should the Sema wrapper, or should - because of use case - the Sema wrapper always exclude macros? I don't particularly like the last option as it means the Sema and Lexer versions of the same function name behave differently

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cor3ntin I've added a "include macros" parameter and removed the default parameters because I think that given the lack of existing usages there's no real reason for the default args, and they present a hazard.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks reasonable

Comment on lines 2167 to 2188
auto GetTrailingEndLocation = [&](SourceLocation StartPoint) {
SourceRange NextToken =
getRangeForNextToken(StartPoint, /*IncludeComments=*/true);
if (!NextToken.isValid())
return SourceLocation();
// Return the last location preceding the next token
return NextToken.getBegin().getLocWithOffset(-1);
};
if (!CurHasPreviousCapture && !IsLast) {
// If there are no captures preceding this capture, remove the
// following comma.
FixItRange = SourceRange(CaptureRange.getBegin(),
getLocForEndOfToken(CaptureRange.getEnd()));
// trailing comma and anything up to the next token
SourceRange CommaRange =
getRangeForNextToken(CaptureRange.getEnd());
SourceLocation FixItEnd =
GetTrailingEndLocation(CommaRange.getBegin());
FixItRange = SourceRange(CaptureRange.getBegin(), FixItEnd);
} else {
// Otherwise, remove the comma since the last used capture.
FixItRange = SourceRange(getLocForEndOfToken(PrevCaptureLoc),
CaptureRange.getEnd());
// Otherwise, remove the comma since the last used capture, and
// anything up to the next token
SourceLocation FixItStart = getLocForEndOfToken(PrevCaptureLoc);
SourceLocation FixItEnd =
GetTrailingEndLocation(CaptureRange.getEnd());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we put that in a separate function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, we care about compile times and having an entire additional will make compilation slower. :D :D

(yes, I'll make it a function)

Copy link
Contributor Author

@ojhunt ojhunt May 30, 2025

Choose a reason for hiding this comment

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

  • trying to make a visible task

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted this method, but I'm not super happy with it -- I don't know how to refactor this to be clearer/less-trivially-extracted.

@cor3ntin
Copy link
Contributor

This change needs a release note.
Please add an entry to clang/docs/ReleaseNotes.rst in the section the most adapted to the change, and referencing any Github issue this change fixes. Thanks!

Copy link

github-actions bot commented Jun 3, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

LGTM modulo nitpicks

Comment on lines 103 to 106
if (!IncludeMacros) {
if (TokenStart.isMacroID() || TokenEnd.isMacroID())
return SourceRange();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You casn merge all of that in a single statement

@@ -2095,6 +2101,39 @@ FieldDecl *Sema::BuildCaptureField(RecordDecl *RD,
return Field;
}

static SourceRange
constructFixItRangeForUnusedCapture(Sema &S, SourceRange CaptureRange,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
constructFixItRangeForUnusedCapture(Sema &S, SourceRange CaptureRange,
ConstructFixItRangeForUnusedCapture(Sema &S, SourceRange CaptureRange,

Comment on lines +97 to +98
if (!TokenStart.isValid() || !TokenEnd.isValid())
return SourceRange();
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks reasonable

@@ -1,5 +1,5 @@
// RUN: cp %s %t
// RUN: %clang_cc1 -x c++ -Wunused-lambda-capture -Wno-unused-value -std=c++1z -fixit %t
// RUN: %clang_cc1 -x c++ -Wno-vla-cxx-extension -Wunused-lambda-capture -Wno-unused-value -std=c++1z -fixit %t
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is that change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah no, that was to silence a warning while I was verifying output

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 3, 2025

I don't understand the windows failure (ninja says there was a failure, but I cannot find any failure in the log?)

I'm also confused by the documentation failure: it's not telling me what/where the error is coming from.

@erichkeane
Copy link
Collaborator

I don't understand the windows failure (ninja says there was a failure, but I cannot find any failure in the log?)

I'm also confused by the documentation failure: it's not telling me what/where the error is coming from.

Documentation failure says:

Warning, treated as error:
/home/runner/work/llvm-project/llvm-project/clang-build/tools/clang/docs/DiagnosticsReference.rst:18009:Unexpected indentation.

I couldn't find the windows reason, but maybe that is the reason for that failure too?

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 3, 2025

Documentation failure says:

Warning, treated as error: /home/runner/work/llvm-project/llvm-project/clang-build/tools/clang/docs/DiagnosticsReference.rst:18009:Unexpected indentation.

Right, but I don't know how/what is causing the failure as I haven't changed any diagnostics? :(

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 4, 2025

I can't reproduce this locally so I've ensured my tree is up to date and pushing again to see if it fails again

@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 4, 2025

The docs failure is #142387

@ojhunt ojhunt merged commit f72054a into llvm:main Jun 4, 2025
9 of 11 checks passed
@ojhunt ojhunt deleted the users/ojhunt/Bug-106445 branch June 4, 2025 00:57
rorth pushed a commit to rorth/llvm-project that referenced this pull request Jun 11, 2025
Fixes llvm#106445 by using the lexer to find the correct range for the
removal FixIts. Previously the ranges that were generated assuming no
unsurprising formatting, which for the most part works. Being correct in
all cases requires using the lexer to find the bounding tokens for the
region to remove.

As part of this it adds Sema::getRangeForNextToken to wrap
Lexer::findNextToken.
DhruvSrivastavaX pushed a commit to DhruvSrivastavaX/lldb-for-aix that referenced this pull request Jun 12, 2025
Fixes llvm#106445 by using the lexer to find the correct range for the
removal FixIts. Previously the ranges that were generated assuming no
unsurprising formatting, which for the most part works. Being correct in
all cases requires using the lexer to find the bounding tokens for the
region to remove.

As part of this it adds Sema::getRangeForNextToken to wrap
Lexer::findNextToken.
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.

Unused lambda capture source ranges are slightly off
5 participants