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"
24 modernize::UseTrailingReturnTypeCheck::TransformLambda> {
25 static llvm::ArrayRef<std::pair<
28 static constexpr std::pair<
33 {modernize::UseTrailingReturnTypeCheck::TransformLambda::
50 UnqualNameVisitor(
const FunctionDecl &F) : F(F) {}
52 bool Collision =
false;
54 bool shouldWalkTypesOfTypeLocs()
const {
return false; }
56 bool visitUnqualName(StringRef UnqualName) {
58 Collision = llvm::any_of(F.parameters(), [&](
const ParmVarDecl *Param) {
59 if (const IdentifierInfo *Ident = Param->getIdentifier())
60 return Ident->getName() == UnqualName;
66 bool TraverseTypeLoc(TypeLoc TL,
bool TraverseQualifier =
true) {
70 switch (TL.getTypeLocClass()) {
71 case TypeLoc::InjectedClassName:
74 auto TTL = TL.getAs<TagTypeLoc>();
75 const auto *
T = TTL.getTypePtr();
76 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
77 TTL.getQualifierLoc())
79 if (visitUnqualName(
T->getDecl()->getName()))
83 case TypeLoc::TemplateSpecialization: {
84 auto TTL = TL.getAs<TemplateSpecializationTypeLoc>();
85 const auto *
T = TTL.getTypePtr();
86 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
87 TTL.getQualifierLoc())
89 if (visitUnqualName(
T->getTemplateName().getAsTemplateDecl()->getName()))
93 case TypeLoc::Typedef: {
94 auto TTL = TL.getAs<TypedefTypeLoc>();
95 const auto *
T = TTL.getTypePtr();
96 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
97 TTL.getQualifierLoc())
99 if (visitUnqualName(
T->getDecl()->getName()))
103 case TypeLoc::Using: {
104 auto TTL = TL.getAs<UsingTypeLoc>();
105 const auto *
T = TTL.getTypePtr();
106 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
107 TTL.getQualifierLoc())
109 if (visitUnqualName(
T->getDecl()->getName()))
117 return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(
118 TL, TraverseQualifier);
123 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL,
bool TraverseQualifier) {
124 return TraverseTypeLoc(TL.getUnqualifiedLoc(), TraverseQualifier);
127 bool VisitDeclRefExpr(DeclRefExpr *S) {
128 const DeclarationName Name = S->getNameInfo().getName();
129 return S->getQualifierLoc() || Name.isEmpty() || !Name.isIdentifier() ||
130 !visitUnqualName(Name.getAsIdentifierInfo()->getName());
134 const FunctionDecl &F;
138 return Node.hasExplicitResultType();
144 "use a trailing return type for this function";
146 "use a trailing return type for this lambda";
149 const SourceManager &SM) {
151 Loc =
expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
152 assert(!Loc.isMacroID() &&
153 "SourceLocation must not be a macro ID after recursive expansion");
158 const FunctionDecl &F,
const FunctionTypeLoc &FTL,
const ASTContext &Ctx,
159 const SourceManager &SM,
const LangOptions &LangOpts) {
161 const SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
162 if (ExceptionSpecRange.isValid())
163 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
168 const SourceLocation ClosingParen = FTL.getRParenLoc();
169 if (ClosingParen.isMacroID())
172 SourceLocation Result =
173 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
176 const std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
177 const StringRef File = SM.getBufferData(Loc.first);
178 const char *TokenBegin = File.data() + Loc.second;
179 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
180 TokenBegin, File.end());
182 while (!Lexer.LexFromRawLexer(T)) {
183 if (T.is(tok::raw_identifier)) {
184 IdentifierInfo &Info = Ctx.Idents.get(
185 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
186 T.setIdentifierInfo(&Info);
187 T.setKind(Info.getTokenID());
190 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
192 Result = T.getEndLoc();
201 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
205 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
206 tok::kw_static, tok::kw_friend, tok::kw_virtual);
209static std::optional<ClassifiedToken>
215 bool ContainsQualifiers =
false;
216 bool ContainsSpecifiers =
false;
217 bool ContainsSomethingElse =
false;
221 End.setKind(tok::eof);
222 const SmallVector<Token, 2> Stream{Tok, End};
225 PP.EnterTokenStream(Stream,
false,
false);
232 const bool Qual =
isCvr(T);
236 ContainsQualifiers |= Qual;
237 ContainsSpecifiers |= Spec;
238 ContainsSomethingElse |= !Qual && !Spec;
243 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
249static std::optional<SmallVector<ClassifiedToken, 8>>
251 const SourceManager &SM,
252 const LangOptions &LangOpts,
255 const SourceLocation BeginNameF =
expandIfMacroId(F.getLocation(), SM);
258 const std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
259 const StringRef File = SM.getBufferData(Loc.first);
260 const char *TokenBegin = File.data() + Loc.second;
261 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
262 TokenBegin, File.end());
264 SmallVector<ClassifiedToken, 8> ClassifiedTokens;
265 while (!Lexer.LexFromRawLexer(T) &&
266 SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
267 if (T.is(tok::raw_identifier)) {
268 IdentifierInfo &Info = Ctx.Idents.get(
269 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
271 if (Info.hasMacroDefinition()) {
272 const MacroInfo *MI = PP->getMacroInfo(&Info);
273 if (!MI || MI->isFunctionLike()) {
279 T.setIdentifierInfo(&Info);
280 T.setKind(Info.getTokenID());
283 if (std::optional<ClassifiedToken> CT =
classifyToken(F, *PP, T))
284 ClassifiedTokens.push_back(*CT);
289 return ClassifiedTokens;
293 bool Result = Type.hasLocalQualifiers();
294 if (Type->isPointerType())
296 Type->castAs<PointerType>()->getPointeeType());
297 if (Type->isReferenceType())
299 Type->castAs<ReferenceType>()->getPointeeType());
305 const ASTContext &Ctx,
const SourceManager &SM,
306 const LangOptions &LangOpts, Preprocessor *PP) {
309 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
310 if (ReturnTypeRange.isInvalid()) {
318 return ReturnTypeRange;
321 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
325 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
327 ReturnTypeRange.setBegin(
expandIfMacroId(ReturnTypeRange.getBegin(), SM));
330 bool ExtendedLeft =
false;
331 for (
size_t I = 0; I < Tokens.size(); I++) {
333 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
334 ReturnTypeRange.getBegin()) &&
336 assert(I <=
size_t(std::numeric_limits<int>::max()) &&
337 "Integer overflow detected");
338 for (
int J =
static_cast<int>(I) - 1; J >= 0 && Tokens[J].IsQualifier;
340 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
344 if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
345 Tokens[I].T.getLocation())) {
346 for (
size_t J = I; J < Tokens.size() && Tokens[J].IsQualifier; J++)
347 ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
352 assert(!ReturnTypeRange.getBegin().isMacroID() &&
353 "Return type source range begin must not be a macro");
354 assert(!ReturnTypeRange.getEnd().isMacroID() &&
355 "Return type source range end must not be a macro");
356 return ReturnTypeRange;
360 const CXXMethodDecl *Method,
const SourceManager &SM,
361 const LangOptions &LangOpts,
const ASTContext &Ctx) {
363 if (Method->getTrailingRequiresClause()) {
364 SourceLocation ParamEndLoc;
365 if (Method->param_empty())
366 ParamEndLoc = Method->getBeginLoc();
368 ParamEndLoc = Method->getParametersSourceRange().getEnd();
370 const std::pair<FileID, unsigned> ParamEndLocInfo =
371 SM.getDecomposedLoc(ParamEndLoc);
372 const StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);
374 Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
375 Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
379 while (!Lexer.LexFromRawLexer(Token)) {
380 if (Token.is(tok::raw_identifier)) {
381 IdentifierInfo &Info = Ctx.Idents.get(StringRef(
382 SM.getCharacterData(Token.getLocation()), Token.getLength()));
383 Token.setIdentifierInfo(&Info);
384 Token.setKind(Info.getTokenID());
387 if (Token.is(tok::kw_requires))
388 return Token.getLocation().getLocWithOffset(-1);
395 if (
const Stmt *Body = Method->getBody())
396 return Body->getBeginLoc().getLocWithOffset(-1);
402 SourceRange ReturnTypeCVRange,
const FunctionDecl &F,
403 const FriendDecl *Fr,
const ASTContext &Ctx,
404 const SourceManager &SM,
const LangOptions &LangOpts,
408 const auto *M = dyn_cast<CXXMethodDecl>(&F);
409 if (!F.isConstexpr() && !F.isInlineSpecified() &&
410 F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
411 !Fr && !(M && M->isVirtualAsWritten()))
416 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
422 const unsigned int ReturnTypeBeginOffset =
423 SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
424 const size_t InitialAutoLength = Auto.size();
425 unsigned int DeletedChars = 0;
427 if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
428 ReturnTypeCVRange.getBegin()) ||
429 SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
437 const unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
438 assert(TOffset >= ReturnTypeBeginOffset &&
439 "Token location must be after the beginning of the return type");
440 const unsigned int TOffsetInRT =
441 TOffset - ReturnTypeBeginOffset - DeletedChars;
442 unsigned int TLengthWithWS = CT.T.getLength();
443 while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
444 llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
446 std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
447 if (!llvm::isSpace(Specifier.back()))
448 Specifier.push_back(
' ');
449 Auto.insert(Auto.size() - InitialAutoLength, Specifier);
450 ReturnType.erase(TOffsetInRT, TLengthWithWS);
451 DeletedChars += TLengthWithWS;
458 TransformFunctions(Options.get(
"TransformFunctions", true)),
461 this->configurationDiag(
462 "The check 'modernize-use-trailing-return-type' will not perform any "
463 "analysis because 'TransformFunctions' and 'TransformLambdas' are "
469 Options.store(Opts,
"TransformFunctions", TransformFunctions);
470 Options.store(Opts,
"TransformLambdas", TransformLambdas);
477 hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
480 hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
483 if (TransformFunctions) {
484 Finder->addMatcher(F,
this);
485 Finder->addMatcher(friendDecl(hasDescendant(F)).bind(
"Friend"),
this);
490 lambdaExpr(unless(hasExplicitResultType())).bind(
"Lambda"),
this);
494 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
499 assert(PP &&
"Expected registerPPCallbacks() to have been called before so "
500 "preprocessor is available");
502 if (
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>(
"Lambda")) {
503 diagOnLambda(Lambda, Result);
507 const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>(
"Friend");
508 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>(
"Func");
509 assert(F &&
"Matcher is expected to find only FunctionDecls");
513 if (F->getLocation().isInvalid() || F->isImplicit())
517 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
519 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
525 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
526 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
527 F->getDeclaredReturnType()->isMemberPointerType()) {
532 const ASTContext &Ctx = *Result.Context;
533 const SourceManager &SM = *Result.SourceManager;
534 const LangOptions &LangOpts = getLangOpts();
536 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
540 auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
549 const SourceLocation InsertionLoc =
551 if (InsertionLoc.isInvalid()) {
560 *F, FTL.getReturnLoc(), Ctx, SM, LangOpts, PP);
561 if (ReturnTypeCVRange.isInvalid()) {
574 UnqualNameVisitor UNV{*F};
575 UNV.TraverseTypeLoc(FTL.getReturnLoc());
581 const SourceLocation ReturnTypeEnd =
582 Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
583 const StringRef CharAfterReturnType = Lexer::getSourceText(
584 CharSourceRange::getCharRange(ReturnTypeEnd,
585 ReturnTypeEnd.getLocWithOffset(1)),
587 const bool NeedSpaceAfterAuto =
588 CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
590 std::string Auto = NeedSpaceAfterAuto ?
"auto " :
"auto";
591 std::string ReturnType =
592 std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
593 keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM, LangOpts,
597 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
598 << FixItHint::CreateInsertion(InsertionLoc,
" -> " + ReturnType);
601void UseTrailingReturnTypeCheck::diagOnLambda(
602 const LambdaExpr *Lambda,
603 const ast_matchers::MatchFinder::MatchResult &Result) {
604 const CXXMethodDecl *Method = Lambda->getCallOperator();
605 if (!Method || Lambda->hasExplicitResultType())
608 const ASTContext *Ctx = Result.Context;
609 const QualType ReturnType = Method->getReturnType();
612 if (ReturnType->isDependentType() &&
613 Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) {
619 if (ReturnType->isUndeducedAutoType() &&
623 const SourceLocation TrailingReturnInsertLoc =
625 getLangOpts(), *Result.Context);
627 if (TrailingReturnInsertLoc.isValid())
628 diag(Lambda->getBeginLoc(),
"use a trailing return type for this lambda")
629 << FixItHint::CreateInsertion(
630 TrailingReturnInsertLoc,
632 ReturnType.getAsString(Result.Context->getPrintingPolicy()));
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
UseTrailingReturnTypeCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_MATCHER(BinaryOperator, isRelationalOperator)
static std::optional< ClassifiedToken > classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok)
constexpr StringRef ErrorMessageOnFunction
static bool hasAnyNestedLocalQualifiers(QualType Type)
static void keepSpecifiers(std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange, const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx, const SourceManager &SM, const LangOptions &LangOpts, Preprocessor *PP)
static SourceLocation findLambdaTrailingReturnInsertLoc(const CXXMethodDecl *Method, const SourceManager &SM, const LangOptions &LangOpts, const ASTContext &Ctx)
static SourceLocation expandIfMacroId(SourceLocation Loc, const SourceManager &SM)
static std::optional< SmallVector< ClassifiedToken, 8 > > classifyTokensBeforeFunctionName(const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM, const LangOptions &LangOpts, Preprocessor *PP)
static bool isSpecifier(Token T)
static SourceRange findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx, const SourceManager &SM, const LangOptions &LangOpts, Preprocessor *PP)
static SourceLocation findTrailingReturnTypeSourceLocation(const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx, const SourceManager &SM, const LangOptions &LangOpts)
constexpr StringRef ErrorMessageOnLambda
static bool isCvr(Token T)
llvm::StringMap< ClangTidyValue > OptionMap
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...