Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
15 changes: 15 additions & 0 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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

if (!NextToken)
return SourceRange();
SourceLocation TokenStart = NextToken->getLocation();
SourceLocation TokenEnd = NextToken->getLastLoc();
if (!TokenStart.isValid() || !TokenEnd.isValid())
return SourceRange();
Comment on lines +101 to +102
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

return SourceRange(TokenStart, TokenEnd);
}

ModuleLoader &Sema::getModuleLoader() const { return PP.getModuleLoader(); }

DarwinSDKInfo *
Expand Down
26 changes: 20 additions & 6 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
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.

FixItRange = SourceRange(FixItStart, FixItEnd);
}
}

Expand Down
250 changes: 250 additions & 0 deletions clang/test/FixIt/fixit-unused-lambda-capture-trailing-tokens.cpp
Original file line number Diff line number Diff line change
@@ -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++;
};
}