Skip to content

Commit f72054a

Browse files
authored
[clang] Correct FixIt ranges for unused capture warnings (#141148)
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.
1 parent 6be4670 commit f72054a

File tree

5 files changed

+568
-16
lines changed

5 files changed

+568
-16
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,8 @@ Improvements to Clang's diagnostics
612612
diagnostic group ``-Wimplicit-int-comparison-on-negation``, grouped under
613613
``-Wimplicit-int-conversion``, so user can turn it off independently.
614614

615+
- Improved the FixIts for unused lambda captures.
616+
615617
Improvements to Clang's time-trace
616618
----------------------------------
617619

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,13 @@ class Sema final : public SemaBase {
972972
/// Calls \c Lexer::getLocForEndOfToken()
973973
SourceLocation getLocForEndOfToken(SourceLocation Loc, unsigned Offset = 0);
974974

975+
/// Calls \c Lexer::findNextToken() to find the next token, and if the
976+
/// locations of both ends of the token can be resolved it return that
977+
/// range; Otherwise it returns an invalid SourceRange.
978+
SourceRange getRangeForNextToken(
979+
SourceLocation Loc, bool IncludeMacros, bool IncludeComments,
980+
std::optional<tok::TokenKind> ExpectedToken = std::nullopt);
981+
975982
/// Retrieve the module loader associated with the preprocessor.
976983
ModuleLoader &getModuleLoader() const;
977984

@@ -9134,6 +9141,7 @@ class Sema final : public SemaBase {
91349141
/// Diagnose if an explicit lambda capture is unused. Returns true if a
91359142
/// diagnostic is emitted.
91369143
bool DiagnoseUnusedLambdaCapture(SourceRange CaptureRange,
9144+
SourceRange FixItRange,
91379145
const sema::Capture &From);
91389146

91399147
/// Build a FieldDecl suitable to hold the given capture.

clang/lib/Sema/Sema.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@ SourceLocation Sema::getLocForEndOfToken(SourceLocation Loc, unsigned Offset) {
8484
return Lexer::getLocForEndOfToken(Loc, Offset, SourceMgr, LangOpts);
8585
}
8686

87+
SourceRange
88+
Sema::getRangeForNextToken(SourceLocation Loc, bool IncludeMacros,
89+
bool IncludeComments,
90+
std::optional<tok::TokenKind> ExpectedToken) {
91+
if (!Loc.isValid())
92+
return SourceRange();
93+
std::optional<Token> NextToken =
94+
Lexer::findNextToken(Loc, SourceMgr, LangOpts, IncludeComments);
95+
if (!NextToken)
96+
return SourceRange();
97+
if (ExpectedToken && NextToken->getKind() != *ExpectedToken)
98+
return SourceRange();
99+
SourceLocation TokenStart = NextToken->getLocation();
100+
SourceLocation TokenEnd = NextToken->getLastLoc();
101+
if (!TokenStart.isValid() || !TokenEnd.isValid())
102+
return SourceRange();
103+
if (!IncludeMacros && (TokenStart.isMacroID() || TokenEnd.isMacroID()))
104+
return SourceRange();
105+
106+
return SourceRange(TokenStart, TokenEnd);
107+
}
108+
87109
ModuleLoader &Sema::getModuleLoader() const { return PP.getModuleLoader(); }
88110

89111
DarwinSDKInfo *

clang/lib/Sema/SemaLambda.cpp

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,7 @@ bool Sema::CaptureHasSideEffects(const Capture &From) {
20222022
}
20232023

20242024
bool Sema::DiagnoseUnusedLambdaCapture(SourceRange CaptureRange,
2025+
SourceRange FixItRange,
20252026
const Capture &From) {
20262027
if (CaptureHasSideEffects(From))
20272028
return false;
@@ -2041,7 +2042,12 @@ bool Sema::DiagnoseUnusedLambdaCapture(SourceRange CaptureRange,
20412042
else
20422043
diag << From.getVariable();
20432044
diag << From.isNonODRUsed();
2044-
diag << FixItHint::CreateRemoval(CaptureRange);
2045+
// If we were able to resolve the fixit range we'll create a fixit,
2046+
// otherwise we just use the raw capture range for the diagnostic.
2047+
if (FixItRange.isValid())
2048+
diag << FixItHint::CreateRemoval(FixItRange);
2049+
else
2050+
diag << CaptureRange;
20452051
return true;
20462052
}
20472053

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

2104+
static SourceRange
2105+
ConstructFixItRangeForUnusedCapture(Sema &S, SourceRange CaptureRange,
2106+
SourceLocation PrevCaptureLoc,
2107+
bool CurHasPreviousCapture, bool IsLast) {
2108+
if (!CaptureRange.isValid())
2109+
return SourceRange();
2110+
2111+
auto GetTrailingEndLocation = [&](SourceLocation StartPoint) {
2112+
SourceRange NextToken = S.getRangeForNextToken(
2113+
StartPoint, /*IncludeMacros=*/false, /*IncludeComments=*/true);
2114+
if (!NextToken.isValid())
2115+
return SourceLocation();
2116+
// Return the last location preceding the next token
2117+
return NextToken.getBegin().getLocWithOffset(-1);
2118+
};
2119+
2120+
if (!CurHasPreviousCapture && !IsLast) {
2121+
// If there are no captures preceding this capture, remove the
2122+
// trailing comma and anything up to the next token
2123+
SourceRange CommaRange =
2124+
S.getRangeForNextToken(CaptureRange.getEnd(), /*IncludeMacros=*/false,
2125+
/*IncludeComments=*/false, tok::comma);
2126+
SourceLocation FixItEnd = GetTrailingEndLocation(CommaRange.getBegin());
2127+
return SourceRange(CaptureRange.getBegin(), FixItEnd);
2128+
}
2129+
2130+
// Otherwise, remove the comma since the last used capture, and
2131+
// anything up to the next token
2132+
SourceLocation FixItStart = S.getLocForEndOfToken(PrevCaptureLoc);
2133+
SourceLocation FixItEnd = GetTrailingEndLocation(CaptureRange.getEnd());
2134+
return SourceRange(FixItStart, FixItEnd);
2135+
}
2136+
20982137
ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
20992138
LambdaScopeInfo *LSI) {
21002139
// Collect information from the lambda scope.
@@ -2162,21 +2201,11 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
21622201
IsGenericLambda && From.isNonODRUsed() && From.isInitCapture();
21632202
if (!NonODRUsedInitCapture) {
21642203
bool IsLast = (I + 1) == LSI->NumExplicitCaptures;
2165-
SourceRange FixItRange;
2166-
if (CaptureRange.isValid()) {
2167-
if (!CurHasPreviousCapture && !IsLast) {
2168-
// If there are no captures preceding this capture, remove the
2169-
// following comma.
2170-
FixItRange = SourceRange(CaptureRange.getBegin(),
2171-
getLocForEndOfToken(CaptureRange.getEnd()));
2172-
} else {
2173-
// Otherwise, remove the comma since the last used capture.
2174-
FixItRange = SourceRange(getLocForEndOfToken(PrevCaptureLoc),
2175-
CaptureRange.getEnd());
2176-
}
2177-
}
2178-
2179-
IsCaptureUsed = !DiagnoseUnusedLambdaCapture(FixItRange, From);
2204+
SourceRange FixItRange = ConstructFixItRangeForUnusedCapture(
2205+
*this, CaptureRange, PrevCaptureLoc, CurHasPreviousCapture,
2206+
IsLast);
2207+
IsCaptureUsed =
2208+
!DiagnoseUnusedLambdaCapture(CaptureRange, FixItRange, From);
21802209
}
21812210
}
21822211

0 commit comments

Comments
 (0)