clang-tools 17.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() != ETK_None || T->getQualifier());
102 }
103
104 bool VisitDeclRefExpr(DeclRefExpr *S) {
105 DeclarationName Name = S->getNameInfo().getName();
106 return S->getQualifierLoc() || !Name.isIdentifier() ||
107 !visitUnqualName(Name.getAsIdentifierInfo()->getName());
108 }
109
110private:
111 const FunctionDecl &F;
112};
113} // namespace
114
115constexpr llvm::StringLiteral Message =
116 "use a trailing return type for this function";
117
118static SourceLocation expandIfMacroId(SourceLocation Loc,
119 const SourceManager &SM) {
120 if (Loc.isMacroID())
121 Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
122 assert(!Loc.isMacroID() &&
123 "SourceLocation must not be a macro ID after recursive expansion");
124 return Loc;
125}
126
127SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
128 const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
129 const SourceManager &SM, const LangOptions &LangOpts) {
130 // We start with the location of the closing parenthesis.
131 SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
132 if (ExceptionSpecRange.isValid())
133 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
134 LangOpts);
135
136 // If the function argument list ends inside of a macro, it is dangerous to
137 // start lexing from here - bail out.
138 SourceLocation ClosingParen = FTL.getRParenLoc();
139 if (ClosingParen.isMacroID())
140 return {};
141
142 SourceLocation Result =
143 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
144
145 // Skip subsequent CV and ref qualifiers.
146 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
147 StringRef File = SM.getBufferData(Loc.first);
148 const char *TokenBegin = File.data() + Loc.second;
149 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
150 TokenBegin, File.end());
151 Token T;
152 while (!Lexer.LexFromRawLexer(T)) {
153 if (T.is(tok::raw_identifier)) {
154 IdentifierInfo &Info = Ctx.Idents.get(
155 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
156 T.setIdentifierInfo(&Info);
157 T.setKind(Info.getTokenID());
158 }
159
160 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
161 tok::kw_restrict)) {
162 Result = T.getEndLoc();
163 continue;
164 }
165 break;
166 }
167 return Result;
168}
169
170static bool isCvr(Token T) {
171 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
172}
173
174static bool isSpecifier(Token T) {
175 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
176 tok::kw_static, tok::kw_friend, tok::kw_virtual);
177}
178
179static std::optional<ClassifiedToken>
180classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
182 CT.T = Tok;
183 CT.IsQualifier = true;
184 CT.IsSpecifier = true;
185 bool ContainsQualifiers = false;
186 bool ContainsSpecifiers = false;
187 bool ContainsSomethingElse = false;
188
189 Token End;
190 End.startToken();
191 End.setKind(tok::eof);
192 SmallVector<Token, 2> Stream{Tok, End};
193
194 // FIXME: do not report these token to Preprocessor.TokenWatcher.
195 PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
196 while (true) {
197 Token T;
198 PP.Lex(T);
199 if (T.is(tok::eof))
200 break;
201
202 bool Qual = isCvr(T);
203 bool Spec = isSpecifier(T);
204 CT.IsQualifier &= Qual;
205 CT.IsSpecifier &= Spec;
206 ContainsQualifiers |= Qual;
207 ContainsSpecifiers |= Spec;
208 ContainsSomethingElse |= !Qual && !Spec;
209 }
210
211 // If the Token/Macro contains more than one type of tokens, we would need
212 // to split the macro in order to move parts to the trailing return type.
213 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
214 return std::nullopt;
215
216 return CT;
217}
218
219std::optional<SmallVector<ClassifiedToken, 8>>
220UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
221 const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
222 const LangOptions &LangOpts) {
223 SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
224 SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
225
226 // Create tokens for everything before the name of the function.
227 std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
228 StringRef File = SM.getBufferData(Loc.first);
229 const char *TokenBegin = File.data() + Loc.second;
230 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
231 TokenBegin, File.end());
232 Token T;
233 SmallVector<ClassifiedToken, 8> ClassifiedTokens;
234 while (!Lexer.LexFromRawLexer(T) &&
235 SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
236 if (T.is(tok::raw_identifier)) {
237 IdentifierInfo &Info = Ctx.Idents.get(
238 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
239
240 if (Info.hasMacroDefinition()) {
241 const MacroInfo *MI = PP->getMacroInfo(&Info);
242 if (!MI || MI->isFunctionLike()) {
243 // Cannot handle function style macros.
244 diag(F.getLocation(), Message);
245 return std::nullopt;
246 }
247 }
248
249 T.setIdentifierInfo(&Info);
250 T.setKind(Info.getTokenID());
251 }
252
253 if (std::optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
254 ClassifiedTokens.push_back(*CT);
255 else {
256 diag(F.getLocation(), Message);
257 return std::nullopt;
258 }
259 }
260
261 return ClassifiedTokens;
262}
263
264static bool hasAnyNestedLocalQualifiers(QualType Type) {
265 bool Result = Type.hasLocalQualifiers();
266 if (Type->isPointerType())
267 Result = Result || hasAnyNestedLocalQualifiers(
268 Type->castAs<PointerType>()->getPointeeType());
269 if (Type->isReferenceType())
270 Result = Result || hasAnyNestedLocalQualifiers(
271 Type->castAs<ReferenceType>()->getPointeeType());
272 return Result;
273}
274
275SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
276 const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx,
277 const SourceManager &SM, const LangOptions &LangOpts) {
278
279 // We start with the range of the return type and expand to neighboring
280 // qualifiers (const, volatile and restrict).
281 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
282 if (ReturnTypeRange.isInvalid()) {
283 // Happens if e.g. clang cannot resolve all includes and the return type is
284 // unknown.
285 diag(F.getLocation(), Message);
286 return {};
287 }
288
289
290 // If the return type has no local qualifiers, it's source range is accurate.
291 if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
292 return ReturnTypeRange;
293
294 // Include qualifiers to the left and right of the return type.
295 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
296 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
297 if (!MaybeTokens)
298 return {};
299 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
300
301 ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
302 ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
303
304 bool ExtendedLeft = false;
305 for (size_t I = 0; I < Tokens.size(); I++) {
306 // If we found the beginning of the return type, include left qualifiers.
307 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
308 ReturnTypeRange.getBegin()) &&
309 !ExtendedLeft) {
310 assert(I <= size_t(std::numeric_limits<int>::max()) &&
311 "Integer overflow detected");
312 for (int J = static_cast<int>(I) - 1; J >= 0 && Tokens[J].IsQualifier;
313 J--)
314 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
315 ExtendedLeft = true;
316 }
317 // If we found the end of the return type, include right qualifiers.
318 if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
319 Tokens[I].T.getLocation())) {
320 for (size_t J = I; J < Tokens.size() && Tokens[J].IsQualifier; J++)
321 ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
322 break;
323 }
324 }
325
326 assert(!ReturnTypeRange.getBegin().isMacroID() &&
327 "Return type source range begin must not be a macro");
328 assert(!ReturnTypeRange.getEnd().isMacroID() &&
329 "Return type source range end must not be a macro");
330 return ReturnTypeRange;
331}
332
333void UseTrailingReturnTypeCheck::keepSpecifiers(
334 std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
335 const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
336 const SourceManager &SM, const LangOptions &LangOpts) {
337 // Check if there are specifiers inside the return type. E.g. unsigned
338 // inline int.
339 const auto *M = dyn_cast<CXXMethodDecl>(&F);
340 if (!F.isConstexpr() && !F.isInlineSpecified() &&
341 F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
342 !Fr && !(M && M->isVirtualAsWritten()))
343 return;
344
345 // Tokenize return type. If it contains macros which contain a mix of
346 // qualifiers, specifiers and types, give up.
347 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
348 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
349 if (!MaybeTokens)
350 return;
351
352 // Find specifiers, remove them from the return type, add them to 'auto'.
353 unsigned int ReturnTypeBeginOffset =
354 SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
355 size_t InitialAutoLength = Auto.size();
356 unsigned int DeletedChars = 0;
357 for (ClassifiedToken CT : *MaybeTokens) {
358 if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
359 ReturnTypeCVRange.getBegin()) ||
360 SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
361 CT.T.getLocation()))
362 continue;
363 if (!CT.IsSpecifier)
364 continue;
365
366 // Add the token to 'auto' and remove it from the return type, including
367 // any whitespace following the token.
368 unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
369 assert(TOffset >= ReturnTypeBeginOffset &&
370 "Token location must be after the beginning of the return type");
371 unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars;
372 unsigned int TLengthWithWS = CT.T.getLength();
373 while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
374 llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
375 TLengthWithWS++;
376 std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
377 if (!llvm::isSpace(Specifier.back()))
378 Specifier.push_back(' ');
379 Auto.insert(Auto.size() - InitialAutoLength, Specifier);
380 ReturnType.erase(TOffsetInRT, TLengthWithWS);
381 DeletedChars += TLengthWithWS;
382 }
383}
384
386 auto F = functionDecl(
387 unless(anyOf(hasTrailingReturn(), returns(voidType()),
388 cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
389 .bind("Func");
390
391 Finder->addMatcher(F, this);
392 Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
393}
394
396 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
397 this->PP = PP;
398}
399
400void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
401 assert(PP && "Expected registerPPCallbacks() to have been called before so "
402 "preprocessor is available");
403
404 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
405 const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
406 assert(F && "Matcher is expected to find only FunctionDecls");
407
408 // Three-way comparison operator<=> is syntactic sugar and generates implicit
409 // nodes for all other operators.
410 if (F->getLocation().isInvalid() || F->isImplicit())
411 return;
412
413 // Skip functions which return 'auto' and defaulted operators.
414 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
415 if (AT != nullptr &&
416 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
417 !hasAnyNestedLocalQualifiers(F->getDeclaredReturnType())) ||
418 F->isDefaulted()))
419 return;
420
421 // TODO: implement those
422 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
423 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
424 F->getDeclaredReturnType()->isMemberPointerType()) {
425 diag(F->getLocation(), Message);
426 return;
427 }
428
429 const ASTContext &Ctx = *Result.Context;
430 const SourceManager &SM = *Result.SourceManager;
431 const LangOptions &LangOpts = getLangOpts();
432
433 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
434 if (!TSI)
435 return;
436
437 FunctionTypeLoc FTL =
438 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
std::string ReturnType
FunctionInfo Info
NodeType Type
SourceLocation Loc
Token Name
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