clang-tools 19.0.0git
UseTrailingReturnTypeCheck.cpp
Go to the documentation of this file.
1//===--- UseTrailingReturnTypeCheck.cpp - clang-tidy-----------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/RecursiveASTVisitor.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Preprocessor.h"
14#include "clang/Tooling/FixIt.h"
15#include "llvm/ADT/StringExtras.h"
16
17#include <cctype>
18#include <optional>
19
20using namespace clang::ast_matchers;
21
22namespace clang::tidy::modernize {
23namespace {
24struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
25public:
26 UnqualNameVisitor(const FunctionDecl &F) : F(F) {}
27
28 bool Collision = false;
29
30 bool shouldWalkTypesOfTypeLocs() const { return false; }
31
32 bool visitUnqualName(StringRef UnqualName) {
33 // Check for collisions with function arguments.
34 for (ParmVarDecl *Param : F.parameters())
35 if (const IdentifierInfo *Ident = Param->getIdentifier())
36 if (Ident->getName() == UnqualName) {
37 Collision = true;
38 return true;
39 }
40 return false;
41 }
42
43 bool TraverseTypeLoc(TypeLoc TL, bool Elaborated = false) {
44 if (TL.isNull())
45 return true;
46
47 if (!Elaborated) {
48 switch (TL.getTypeLocClass()) {
49 case TypeLoc::Record:
50 if (visitUnqualName(
51 TL.getAs<RecordTypeLoc>().getTypePtr()->getDecl()->getName()))
52 return false;
53 break;
54 case TypeLoc::Enum:
55 if (visitUnqualName(
56 TL.getAs<EnumTypeLoc>().getTypePtr()->getDecl()->getName()))
57 return false;
58 break;
59 case TypeLoc::TemplateSpecialization:
60 if (visitUnqualName(TL.getAs<TemplateSpecializationTypeLoc>()
61 .getTypePtr()
62 ->getTemplateName()
63 .getAsTemplateDecl()
64 ->getName()))
65 return false;
66 break;
67 case TypeLoc::Typedef:
68 if (visitUnqualName(
69 TL.getAs<TypedefTypeLoc>().getTypePtr()->getDecl()->getName()))
70 return false;
71 break;
72 case TypeLoc::Using:
73 if (visitUnqualName(TL.getAs<UsingTypeLoc>()
74 .getTypePtr()
75 ->getFoundDecl()
76 ->getName()))
77 return false;
78 break;
79 default:
80 break;
81 }
82 }
83
85 }
86
87 // Replace the base method in order to call our own
88 // TraverseTypeLoc().
89 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) {
90 return TraverseTypeLoc(TL.getUnqualifiedLoc());
91 }
92
93 // Replace the base version to inform TraverseTypeLoc that the type is
94 // elaborated.
95 bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) {
96 if (TL.getQualifierLoc() &&
97 !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc()))
98 return false;
99 const auto *T = TL.getTypePtr();
100 return TraverseTypeLoc(TL.getNamedTypeLoc(),
101 T->getKeyword() != ElaboratedTypeKeyword::None ||
102 T->getQualifier());
103 }
104
105 bool VisitDeclRefExpr(DeclRefExpr *S) {
106 DeclarationName Name = S->getNameInfo().getName();
107 return S->getQualifierLoc() || Name.isEmpty() || !Name.isIdentifier() ||
108 !visitUnqualName(Name.getAsIdentifierInfo()->getName());
109 }
110
111private:
112 const FunctionDecl &F;
113};
114} // namespace
115
116constexpr llvm::StringLiteral Message =
117 "use a trailing return type for this function";
118
119static SourceLocation expandIfMacroId(SourceLocation Loc,
120 const SourceManager &SM) {
121 if (Loc.isMacroID())
122 Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
123 assert(!Loc.isMacroID() &&
124 "SourceLocation must not be a macro ID after recursive expansion");
125 return Loc;
126}
127
128SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
129 const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
130 const SourceManager &SM, const LangOptions &LangOpts) {
131 // We start with the location of the closing parenthesis.
132 SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
133 if (ExceptionSpecRange.isValid())
134 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
135 LangOpts);
136
137 // If the function argument list ends inside of a macro, it is dangerous to
138 // start lexing from here - bail out.
139 SourceLocation ClosingParen = FTL.getRParenLoc();
140 if (ClosingParen.isMacroID())
141 return {};
142
143 SourceLocation Result =
144 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
145
146 // Skip subsequent CV and ref qualifiers.
147 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
148 StringRef File = SM.getBufferData(Loc.first);
149 const char *TokenBegin = File.data() + Loc.second;
150 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
151 TokenBegin, File.end());
152 Token T;
153 while (!Lexer.LexFromRawLexer(T)) {
154 if (T.is(tok::raw_identifier)) {
155 IdentifierInfo &Info = Ctx.Idents.get(
156 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
157 T.setIdentifierInfo(&Info);
158 T.setKind(Info.getTokenID());
159 }
160
161 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
162 tok::kw_restrict)) {
163 Result = T.getEndLoc();
164 continue;
165 }
166 break;
167 }
168 return Result;
169}
170
171static bool isCvr(Token T) {
172 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
173}
174
175static bool isSpecifier(Token T) {
176 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
177 tok::kw_static, tok::kw_friend, tok::kw_virtual);
178}
179
180static std::optional<ClassifiedToken>
181classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
183 CT.T = Tok;
184 CT.IsQualifier = true;
185 CT.IsSpecifier = true;
186 bool ContainsQualifiers = false;
187 bool ContainsSpecifiers = false;
188 bool ContainsSomethingElse = false;
189
190 Token End;
191 End.startToken();
192 End.setKind(tok::eof);
193 SmallVector<Token, 2> Stream{Tok, End};
194
195 // FIXME: do not report these token to Preprocessor.TokenWatcher.
196 PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
197 while (true) {
198 Token T;
199 PP.Lex(T);
200 if (T.is(tok::eof))
201 break;
202
203 bool Qual = isCvr(T);
204 bool Spec = isSpecifier(T);
205 CT.IsQualifier &= Qual;
206 CT.IsSpecifier &= Spec;
207 ContainsQualifiers |= Qual;
208 ContainsSpecifiers |= Spec;
209 ContainsSomethingElse |= !Qual && !Spec;
210 }
211
212 // If the Token/Macro contains more than one type of tokens, we would need
213 // to split the macro in order to move parts to the trailing return type.
214 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
215 return std::nullopt;
216
217 return CT;
218}
219
220std::optional<SmallVector<ClassifiedToken, 8>>
221UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
222 const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
223 const LangOptions &LangOpts) {
224 SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
225 SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
226
227 // Create tokens for everything before the name of the function.
228 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
229 StringRef File = SM.getBufferData(Loc.first);
230 const char *TokenBegin = File.data() + Loc.second;
231 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
232 TokenBegin, File.end());
233 Token T;
234 SmallVector<ClassifiedToken, 8> ClassifiedTokens;
235 while (!Lexer.LexFromRawLexer(T) &&
236 SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
237 if (T.is(tok::raw_identifier)) {
238 IdentifierInfo &Info = Ctx.Idents.get(
239 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
240
241 if (Info.hasMacroDefinition()) {
242 const MacroInfo *MI = PP->getMacroInfo(&Info);
243 if (!MI || MI->isFunctionLike()) {
244 // Cannot handle function style macros.
245 diag(F.getLocation(), Message);
246 return std::nullopt;
247 }
248 }
249
250 T.setIdentifierInfo(&Info);
251 T.setKind(Info.getTokenID());
252 }
253
254 if (std::optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
255 ClassifiedTokens.push_back(*CT);
256 else {
257 diag(F.getLocation(), Message);
258 return std::nullopt;
259 }
260 }
261
262 return ClassifiedTokens;
263}
264
265static bool hasAnyNestedLocalQualifiers(QualType Type) {
266 bool Result = Type.hasLocalQualifiers();
267 if (Type->isPointerType())
268 Result = Result || hasAnyNestedLocalQualifiers(
269 Type->castAs<PointerType>()->getPointeeType());
270 if (Type->isReferenceType())
271 Result = Result || hasAnyNestedLocalQualifiers(
272 Type->castAs<ReferenceType>()->getPointeeType());
273 return Result;
274}
275
276SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
277 const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx,
278 const SourceManager &SM, const LangOptions &LangOpts) {
279
280 // We start with the range of the return type and expand to neighboring
281 // qualifiers (const, volatile and restrict).
282 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
283 if (ReturnTypeRange.isInvalid()) {
284 // Happens if e.g. clang cannot resolve all includes and the return type is
285 // unknown.
286 diag(F.getLocation(), Message);
287 return {};
288 }
289
290
291 // If the return type has no local qualifiers, it's source range is accurate.
292 if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
293 return ReturnTypeRange;
294
295 // Include qualifiers to the left and right of the return type.
296 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
297 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
298 if (!MaybeTokens)
299 return {};
300 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
301
302 ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
303 ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
304
305 bool ExtendedLeft = false;
306 for (size_t I = 0; I < Tokens.size(); I++) {
307 // If we found the beginning of the return type, include left qualifiers.
308 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
309 ReturnTypeRange.getBegin()) &&
310 !ExtendedLeft) {
311 assert(I <= size_t(std::numeric_limits<int>::max()) &&
312 "Integer overflow detected");
313 for (int J = static_cast<int>(I) - 1; J >= 0 && Tokens[J].IsQualifier;
314 J--)
315 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
316 ExtendedLeft = true;
317 }
318 // If we found the end of the return type, include right qualifiers.
319 if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
320 Tokens[I].T.getLocation())) {
321 for (size_t J = I; J < Tokens.size() && Tokens[J].IsQualifier; J++)
322 ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
323 break;
324 }
325 }
326
327 assert(!ReturnTypeRange.getBegin().isMacroID() &&
328 "Return type source range begin must not be a macro");
329 assert(!ReturnTypeRange.getEnd().isMacroID() &&
330 "Return type source range end must not be a macro");
331 return ReturnTypeRange;
332}
333
334void UseTrailingReturnTypeCheck::keepSpecifiers(
335 std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
336 const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
337 const SourceManager &SM, const LangOptions &LangOpts) {
338 // Check if there are specifiers inside the return type. E.g. unsigned
339 // inline int.
340 const auto *M = dyn_cast<CXXMethodDecl>(&F);
341 if (!F.isConstexpr() && !F.isInlineSpecified() &&
342 F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
343 !Fr && !(M && M->isVirtualAsWritten()))
344 return;
345
346 // Tokenize return type. If it contains macros which contain a mix of
347 // qualifiers, specifiers and types, give up.
348 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
349 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
350 if (!MaybeTokens)
351 return;
352
353 // Find specifiers, remove them from the return type, add them to 'auto'.
354 unsigned int ReturnTypeBeginOffset =
355 SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
356 size_t InitialAutoLength = Auto.size();
357 unsigned int DeletedChars = 0;
358 for (ClassifiedToken CT : *MaybeTokens) {
359 if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
360 ReturnTypeCVRange.getBegin()) ||
361 SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
362 CT.T.getLocation()))
363 continue;
364 if (!CT.IsSpecifier)
365 continue;
366
367 // Add the token to 'auto' and remove it from the return type, including
368 // any whitespace following the token.
369 unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
370 assert(TOffset >= ReturnTypeBeginOffset &&
371 "Token location must be after the beginning of the return type");
372 unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars;
373 unsigned int TLengthWithWS = CT.T.getLength();
374 while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
375 llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
376 TLengthWithWS++;
377 std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
378 if (!llvm::isSpace(Specifier.back()))
379 Specifier.push_back(' ');
380 Auto.insert(Auto.size() - InitialAutoLength, Specifier);
381 ReturnType.erase(TOffsetInRT, TLengthWithWS);
382 DeletedChars += TLengthWithWS;
383 }
384}
385
387 auto F = functionDecl(
388 unless(anyOf(hasTrailingReturn(), returns(voidType()),
389 cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
390 .bind("Func");
391
392 Finder->addMatcher(F, this);
393 Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
394}
395
397 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
398 this->PP = PP;
399}
400
401void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
402 assert(PP && "Expected registerPPCallbacks() to have been called before so "
403 "preprocessor is available");
404
405 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
406 const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
407 assert(F && "Matcher is expected to find only FunctionDecls");
408
409 // Three-way comparison operator<=> is syntactic sugar and generates implicit
410 // nodes for all other operators.
411 if (F->getLocation().isInvalid() || F->isImplicit())
412 return;
413
414 // Skip functions which return 'auto' and defaulted operators.
415 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
416 if (AT != nullptr &&
417 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
418 !hasAnyNestedLocalQualifiers(F->getDeclaredReturnType())) ||
419 F->isDefaulted()))
420 return;
421
422 // TODO: implement those
423 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
424 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
425 F->getDeclaredReturnType()->isMemberPointerType()) {
426 diag(F->getLocation(), Message);
427 return;
428 }
429
430 const ASTContext &Ctx = *Result.Context;
431 const SourceManager &SM = *Result.SourceManager;
432 const LangOptions &LangOpts = getLangOpts();
433
434 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
435 if (!TSI)
436 return;
437
438 auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
439 if (!FTL) {
440 // FIXME: This may happen if we have __attribute__((...)) on the function.
441 // We abort for now. Remove this when the function type location gets
442 // available in clang.
443 diag(F->getLocation(), Message);
444 return;
445 }
446
447 SourceLocation InsertionLoc =
448 findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
449 if (InsertionLoc.isInvalid()) {
450 diag(F->getLocation(), Message);
451 return;
452 }
453
454 // Using the declared return type via F->getDeclaredReturnType().getAsString()
455 // discards user formatting and order of const, volatile, type, whitespace,
456 // space before & ... .
457 SourceRange ReturnTypeCVRange =
458 findReturnTypeAndCVSourceRange(*F, FTL.getReturnLoc(), Ctx, SM, LangOpts);
459 if (ReturnTypeCVRange.isInvalid())
460 return;
461
462 // Check if unqualified names in the return type conflict with other entities
463 // after the rewrite.
464 // FIXME: this could be done better, by performing a lookup of all
465 // unqualified names in the return type in the scope of the function. If the
466 // lookup finds a different entity than the original entity identified by the
467 // name, then we can either not perform a rewrite or explicitly qualify the
468 // entity. Such entities could be function parameter names, (inherited) class
469 // members, template parameters, etc.
470 UnqualNameVisitor UNV{*F};
471 UNV.TraverseTypeLoc(FTL.getReturnLoc());
472 if (UNV.Collision) {
473 diag(F->getLocation(), Message);
474 return;
475 }
476
477 SourceLocation ReturnTypeEnd =
478 Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
479 StringRef CharAfterReturnType = Lexer::getSourceText(
480 CharSourceRange::getCharRange(ReturnTypeEnd,
481 ReturnTypeEnd.getLocWithOffset(1)),
482 SM, LangOpts);
483 bool NeedSpaceAfterAuto =
484 CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
485
486 std::string Auto = NeedSpaceAfterAuto ? "auto " : "auto";
487 std::string ReturnType =
488 std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
489 keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
490 LangOpts);
491
492 diag(F->getLocation(), Message)
493 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
494 << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
495}
496
497} // namespace clang::tidy::modernize
llvm::SmallString< 256U > Name
std::string ReturnType
FunctionInfo Info
NodeType Type
SourceLocation Loc
const google::protobuf::Message & M
Definition: Server.cpp:309
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
@ Auto
Diagnostics must not be generated for this snapshot.
static std::optional< ClassifiedToken > classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok)
static bool hasAnyNestedLocalQualifiers(QualType Type)
static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM)
constexpr llvm::StringLiteral Message