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"
23 modernize::UseTrailingReturnTypeCheck::TransformLambda> {
24 static llvm::ArrayRef<std::pair<
27 static constexpr std::pair<
32 {modernize::UseTrailingReturnTypeCheck::TransformLambda::
49 UnqualNameVisitor(
const FunctionDecl &F) : F(F) {}
51 bool Collision =
false;
53 bool shouldWalkTypesOfTypeLocs()
const {
return false; }
55 bool visitUnqualName(StringRef UnqualName) {
57 Collision = llvm::any_of(F.parameters(), [&](
const ParmVarDecl *Param) {
58 if (const IdentifierInfo *Ident = Param->getIdentifier())
59 return Ident->getName() == UnqualName;
65 bool TraverseTypeLoc(TypeLoc TL,
bool TraverseQualifier =
true) {
69 switch (TL.getTypeLocClass()) {
70 case TypeLoc::InjectedClassName:
73 auto TTL = TL.getAs<TagTypeLoc>();
74 const auto *
T = TTL.getTypePtr();
75 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
76 TTL.getQualifierLoc())
78 if (visitUnqualName(
T->getDecl()->getName()))
82 case TypeLoc::TemplateSpecialization: {
83 auto TTL = TL.getAs<TemplateSpecializationTypeLoc>();
84 const auto *
T = TTL.getTypePtr();
85 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
86 TTL.getQualifierLoc())
88 if (visitUnqualName(
T->getTemplateName().getAsTemplateDecl()->getName()))
92 case TypeLoc::Typedef: {
93 auto TTL = TL.getAs<TypedefTypeLoc>();
94 const auto *
T = TTL.getTypePtr();
95 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
96 TTL.getQualifierLoc())
98 if (visitUnqualName(
T->getDecl()->getName()))
102 case TypeLoc::Using: {
103 auto TTL = TL.getAs<UsingTypeLoc>();
104 const auto *
T = TTL.getTypePtr();
105 if (
T->getKeyword() != ElaboratedTypeKeyword::None ||
106 TTL.getQualifierLoc())
108 if (visitUnqualName(
T->getDecl()->getName()))
116 return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(
117 TL, TraverseQualifier);
122 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL,
bool TraverseQualifier) {
123 return TraverseTypeLoc(TL.getUnqualifiedLoc(), TraverseQualifier);
126 bool VisitDeclRefExpr(DeclRefExpr *S) {
127 const DeclarationName Name = S->getNameInfo().getName();
128 return S->getQualifierLoc() || Name.isEmpty() || !Name.isIdentifier() ||
129 !visitUnqualName(Name.getAsIdentifierInfo()->getName());
133 const FunctionDecl &F;
137 return Node.hasExplicitResultType();
143 "use a trailing return type for this function";
145 "use a trailing return type for this lambda";
148 const SourceManager &SM) {
150 Loc =
expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
151 assert(!Loc.isMacroID() &&
152 "SourceLocation must not be a macro ID after recursive expansion");
157 const FunctionDecl &F,
const FunctionTypeLoc &FTL,
const ASTContext &Ctx,
158 const SourceManager &SM,
const LangOptions &LangOpts) {
160 const SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
161 if (ExceptionSpecRange.isValid())
162 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
167 const SourceLocation ClosingParen = FTL.getRParenLoc();
168 if (ClosingParen.isMacroID())
171 SourceLocation Result =
172 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
175 const std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
176 const StringRef File = SM.getBufferData(Loc.first);
177 const char *TokenBegin = File.data() + Loc.second;
178 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
179 TokenBegin, File.end());
181 while (!Lexer.LexFromRawLexer(T)) {
182 if (T.is(tok::raw_identifier)) {
183 IdentifierInfo &Info = Ctx.Idents.get(
184 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
185 T.setIdentifierInfo(&Info);
186 T.setKind(Info.getTokenID());
189 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
191 Result = T.getEndLoc();
200 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
204 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
205 tok::kw_static, tok::kw_friend, tok::kw_virtual);
210struct ClassifiedToken {
218static std::optional<ClassifiedToken>
222 CT.IsQualifier =
true;
223 CT.IsSpecifier =
true;
224 bool ContainsQualifiers =
false;
225 bool ContainsSpecifiers =
false;
226 bool ContainsSomethingElse =
false;
230 End.setKind(tok::eof);
231 const std::array<Token, 2> Stream{Tok, End};
234 PP.EnterTokenStream(Stream,
false,
false);
241 const bool Qual =
isCvr(T);
243 CT.IsQualifier &= Qual;
244 CT.IsSpecifier &= Spec;
245 ContainsQualifiers |= Qual;
246 ContainsSpecifiers |= Spec;
247 ContainsSomethingElse |= !Qual && !Spec;
252 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
258static std::optional<SmallVector<ClassifiedToken, 8>>
260 const SourceManager &SM,
261 const LangOptions &LangOpts,
264 const SourceLocation BeginNameF =
expandIfMacroId(F.getLocation(), SM);
267 const std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
268 const StringRef File = SM.getBufferData(Loc.first);
269 const char *TokenBegin = File.data() + Loc.second;
270 Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
271 TokenBegin, File.end());
274 while (!Lexer.LexFromRawLexer(T) &&
275 SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
276 if (T.is(tok::raw_identifier)) {
277 IdentifierInfo &Info = Ctx.Idents.get(
278 StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
280 if (Info.hasMacroDefinition()) {
281 const MacroInfo *MI = PP->getMacroInfo(&Info);
282 if (!MI || MI->isFunctionLike() || MI->isBuiltinMacro())
286 T.setIdentifierInfo(&Info);
287 T.setKind(Info.getTokenID());
290 if (std::optional<ClassifiedToken> CT =
classifyToken(F, *PP, T))
291 ClassifiedTokens.push_back(*CT);
296 return ClassifiedTokens;
300 bool Result = Type.hasLocalQualifiers();
301 if (Type->isPointerType())
303 Type->castAs<PointerType>()->getPointeeType());
304 if (Type->isReferenceType())
306 Type->castAs<ReferenceType>()->getPointeeType());
312 const ASTContext &Ctx,
const SourceManager &SM,
313 const LangOptions &LangOpts, Preprocessor *PP) {
316 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
317 if (ReturnTypeRange.isInvalid()) {
325 return ReturnTypeRange;
328 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
334 ReturnTypeRange.setBegin(
expandIfMacroId(ReturnTypeRange.getBegin(), SM));
337 bool ExtendedLeft =
false;
338 for (
size_t I = 0; I < Tokens.size(); I++) {
340 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
341 ReturnTypeRange.getBegin()) &&
343 assert(I <=
size_t(std::numeric_limits<int>::max()) &&
344 "Integer overflow detected");
345 for (
int J =
static_cast<int>(I) - 1; J >= 0 && Tokens[J].IsQualifier;
347 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
351 if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
352 Tokens[I].T.getLocation())) {
353 for (
size_t J = I; J < Tokens.size() && Tokens[J].IsQualifier; J++)
354 ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
359 assert(!ReturnTypeRange.getBegin().isMacroID() &&
360 "Return type source range begin must not be a macro");
361 assert(!ReturnTypeRange.getEnd().isMacroID() &&
362 "Return type source range end must not be a macro");
363 return ReturnTypeRange;
367 const CXXMethodDecl *Method,
const SourceManager &SM,
368 const LangOptions &LangOpts,
const ASTContext &Ctx) {
370 if (Method->getTrailingRequiresClause()) {
371 SourceLocation ParamEndLoc;
372 if (Method->param_empty())
373 ParamEndLoc = Method->getBeginLoc();
375 ParamEndLoc = Method->getParametersSourceRange().getEnd();
377 const std::pair<FileID, unsigned> ParamEndLocInfo =
378 SM.getDecomposedLoc(ParamEndLoc);
379 const StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);
381 Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
382 Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
386 while (!Lexer.LexFromRawLexer(Token)) {
387 if (Token.is(tok::raw_identifier)) {
388 IdentifierInfo &Info = Ctx.Idents.get(StringRef(
389 SM.getCharacterData(Token.getLocation()), Token.getLength()));
390 Token.setIdentifierInfo(&Info);
391 Token.setKind(Info.getTokenID());
394 if (Token.is(tok::kw_requires))
395 return Token.getLocation().getLocWithOffset(-1);
402 if (
const Stmt *Body = Method->getBody())
403 return Body->getBeginLoc().getLocWithOffset(-1);
409 SourceRange ReturnTypeCVRange,
const FunctionDecl &F,
410 const FriendDecl *Fr,
const ASTContext &Ctx,
411 const SourceManager &SM,
const LangOptions &LangOpts,
415 const auto *M = dyn_cast<CXXMethodDecl>(&F);
416 if (!F.isConstexpr() && !F.isInlineSpecified() &&
417 F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
418 !Fr && !(M && M->isVirtualAsWritten()))
423 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
429 const unsigned int ReturnTypeBeginOffset =
430 SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
431 const size_t InitialAutoLength = Auto.size();
432 unsigned int DeletedChars = 0;
433 for (
const ClassifiedToken CT : *MaybeTokens) {
434 if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
435 ReturnTypeCVRange.getBegin()) ||
436 SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
444 const unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
445 assert(TOffset >= ReturnTypeBeginOffset &&
446 "Token location must be after the beginning of the return type");
447 const unsigned int TOffsetInRT =
448 TOffset - ReturnTypeBeginOffset - DeletedChars;
449 unsigned int TLengthWithWS = CT.T.getLength();
450 while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
451 llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
453 std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
454 if (!llvm::isSpace(Specifier.back()))
455 Specifier.push_back(
' ');
456 Auto.insert(Auto.size() - InitialAutoLength, Specifier);
457 ReturnType.erase(TOffsetInRT, TLengthWithWS);
458 DeletedChars += TLengthWithWS;
465 TransformFunctions(Options.get(
"TransformFunctions", true)),
468 this->configurationDiag(
469 "The check 'modernize-use-trailing-return-type' will not perform any "
470 "analysis because 'TransformFunctions' and 'TransformLambdas' are "
476 Options.store(Opts,
"TransformFunctions", TransformFunctions);
477 Options.store(Opts,
"TransformLambdas", TransformLambdas);
484 hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
487 hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
490 if (TransformFunctions) {
491 Finder->addMatcher(F,
this);
492 Finder->addMatcher(friendDecl(hasDescendant(F)).bind(
"Friend"),
this);
497 lambdaExpr(unless(hasExplicitResultType())).bind(
"Lambda"),
this);
501 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
506 assert(PP &&
"Expected registerPPCallbacks() to have been called before so "
507 "preprocessor is available");
509 if (
const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>(
"Lambda")) {
510 diagOnLambda(Lambda, Result);
514 const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>(
"Friend");
515 const auto *F = Result.Nodes.getNodeAs<FunctionDecl>(
"Func");
516 assert(F &&
"Matcher is expected to find only FunctionDecls");
520 if (F->getLocation().isInvalid() || F->isImplicit())
524 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
526 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
532 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
533 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
534 F->getDeclaredReturnType()->isMemberPointerType()) {
539 const ASTContext &Ctx = *Result.Context;
540 const SourceManager &SM = *Result.SourceManager;
541 const LangOptions &LangOpts = getLangOpts();
543 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
547 auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
556 const SourceLocation InsertionLoc =
558 if (InsertionLoc.isInvalid()) {
567 *F, FTL.getReturnLoc(), Ctx, SM, LangOpts, PP);
568 if (ReturnTypeCVRange.isInvalid()) {
581 UnqualNameVisitor UNV{*F};
582 UNV.TraverseTypeLoc(FTL.getReturnLoc());
588 const SourceLocation ReturnTypeEnd =
589 Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
590 const StringRef CharAfterReturnType = Lexer::getSourceText(
591 CharSourceRange::getCharRange(ReturnTypeEnd,
592 ReturnTypeEnd.getLocWithOffset(1)),
594 const bool NeedSpaceAfterAuto =
595 CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
597 std::string Auto = NeedSpaceAfterAuto ?
"auto " :
"auto";
598 std::string ReturnType =
599 std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
600 keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM, LangOpts,
604 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
605 << FixItHint::CreateInsertion(InsertionLoc,
" -> " + ReturnType);
608void UseTrailingReturnTypeCheck::diagOnLambda(
609 const LambdaExpr *Lambda,
610 const ast_matchers::MatchFinder::MatchResult &Result) {
611 const CXXMethodDecl *Method = Lambda->getCallOperator();
612 if (!Method || Lambda->hasExplicitResultType())
615 const ASTContext *Ctx = Result.Context;
616 const QualType ReturnType = Method->getReturnType();
619 if (ReturnType->isDependentType() &&
620 Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) {
626 if (ReturnType->isUndeducedAutoType() &&
630 const SourceLocation TrailingReturnInsertLoc =
632 getLangOpts(), *Result.Context);
634 if (TrailingReturnInsertLoc.isValid())
635 diag(Lambda->getBeginLoc(),
"use a trailing return type for this lambda")
636 << FixItHint::CreateInsertion(
637 TrailingReturnInsertLoc,
639 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...