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"
26 UnqualNameVisitor(
const FunctionDecl &F) : F(F) {}
30 bool shouldWalkTypesOfTypeLocs()
const {
return false; }
32 bool visitUnqualName(StringRef UnqualName) {
34 for (ParmVarDecl *Param : F.parameters())
35 if (
const IdentifierInfo *Ident = Param->getIdentifier())
36 if (Ident->getName() == UnqualName) {
43 bool TraverseTypeLoc(TypeLoc TL,
bool Elaborated =
false) {
48 switch (TL.getTypeLocClass()) {
51 TL.getAs<RecordTypeLoc>().getTypePtr()->getDecl()->getName()))
56 TL.getAs<EnumTypeLoc>().getTypePtr()->getDecl()->getName()))
59 case TypeLoc::TemplateSpecialization:
60 if (visitUnqualName(TL.getAs<TemplateSpecializationTypeLoc>()
67 case TypeLoc::Typedef:
69 TL.getAs<TypedefTypeLoc>().getTypePtr()->getDecl()->getName()))
73 if (visitUnqualName(TL.getAs<UsingTypeLoc>()
89 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) {
90 return TraverseTypeLoc(TL.getUnqualifiedLoc());
95 bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) {
96 if (TL.getQualifierLoc() &&
97 !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc()))
99 const auto *T = TL.getTypePtr();
100 return TraverseTypeLoc(TL.getNamedTypeLoc(),
101 T->getKeyword() != ETK_None || T->getQualifier());
104 bool VisitDeclRefExpr(DeclRefExpr *S) {
105 DeclarationName
Name = S->getNameInfo().getName();
106 return S->getQualifierLoc() || !
Name.isIdentifier() ||
107 !visitUnqualName(
Name.getAsIdentifierInfo()->getName());
111 const FunctionDecl &F;
116 "use a trailing return type for this function";
119 const SourceManager &SM) {
122 assert(!
Loc.isMacroID() &&
123 "SourceLocation must not be a macro ID after recursive expansion");
127SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
128 const FunctionDecl &F,
const FunctionTypeLoc &FTL,
const ASTContext &Ctx,
129 const SourceManager &SM,
const LangOptions &LangOpts) {
131 SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
132 if (ExceptionSpecRange.isValid())
133 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
138 SourceLocation ClosingParen = FTL.getRParenLoc();
139 if (ClosingParen.isMacroID())
142 SourceLocation Result =
143 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
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());
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());
160 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
162 Result = T.getEndLoc();
171 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
175 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
176 tok::kw_static, tok::kw_friend, tok::kw_virtual);
179static std::optional<ClassifiedToken>
185 bool ContainsQualifiers =
false;
186 bool ContainsSpecifiers =
false;
187 bool ContainsSomethingElse =
false;
191 End.setKind(tok::eof);
192 SmallVector<Token, 2> Stream{Tok, End};
195 PP.EnterTokenStream(Stream,
false,
false);
202 bool Qual =
isCvr(T);
206 ContainsQualifiers |= Qual;
207 ContainsSpecifiers |= Spec;
208 ContainsSomethingElse |= !Qual && !Spec;
213 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
219std::optional<SmallVector<ClassifiedToken, 8>>
220UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
221 const FunctionDecl &F,
const ASTContext &Ctx,
const SourceManager &SM,
222 const LangOptions &LangOpts) {
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());
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()));
240 if (
Info.hasMacroDefinition()) {
241 const MacroInfo *MI = PP->getMacroInfo(&
Info);
242 if (!MI || MI->isFunctionLike()) {
249 T.setIdentifierInfo(&
Info);
250 T.setKind(
Info.getTokenID());
253 if (std::optional<ClassifiedToken> CT =
classifyToken(F, *PP, T))
254 ClassifiedTokens.push_back(*CT);
261 return ClassifiedTokens;
265 bool Result =
Type.hasLocalQualifiers();
266 if (
Type->isPointerType())
268 Type->castAs<PointerType>()->getPointeeType());
269 if (
Type->isReferenceType())
271 Type->castAs<ReferenceType>()->getPointeeType());
275SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
276 const FunctionDecl &F,
const TypeLoc &ReturnLoc,
const ASTContext &Ctx,
277 const SourceManager &SM,
const LangOptions &LangOpts) {
281 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
282 if (ReturnTypeRange.isInvalid()) {
292 return ReturnTypeRange;
295 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
296 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
299 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
301 ReturnTypeRange.setBegin(
expandIfMacroId(ReturnTypeRange.getBegin(), SM));
304 bool ExtendedLeft =
false;
305 for (
size_t I = 0; I < Tokens.size(); I++) {
307 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
308 ReturnTypeRange.getBegin()) &&
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;
314 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
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());
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;
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) {
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()))
347 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
348 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
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(),
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]))
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);
381 DeletedChars += TLengthWithWS;
386 auto F = functionDecl(
387 unless(anyOf(hasTrailingReturn(), returns(voidType()),
388 cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
391 Finder->addMatcher(F,
this);
392 Finder->addMatcher(friendDecl(hasDescendant(F)).bind(
"Friend"),
this);
396 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
401 assert(PP &&
"Expected registerPPCallbacks() to have been called before so "
402 "preprocessor is available");
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");
410 if (F->getLocation().isInvalid() || F->isImplicit())
414 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
416 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
422 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
423 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
424 F->getDeclaredReturnType()->isMemberPointerType()) {
429 const ASTContext &Ctx = *Result.Context;
430 const SourceManager &SM = *Result.SourceManager;
433 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
437 FunctionTypeLoc FTL =
438 TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
447 SourceLocation InsertionLoc =
448 findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
449 if (InsertionLoc.isInvalid()) {
457 SourceRange ReturnTypeCVRange =
458 findReturnTypeAndCVSourceRange(*F, FTL.getReturnLoc(), Ctx, SM, LangOpts);
459 if (ReturnTypeCVRange.isInvalid())
470 UnqualNameVisitor UNV{*F};
471 UNV.TraverseTypeLoc(FTL.getReturnLoc());
477 SourceLocation ReturnTypeEnd =
478 Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
479 StringRef CharAfterReturnType = Lexer::getSourceText(
480 CharSourceRange::getCharRange(ReturnTypeEnd,
481 ReturnTypeEnd.getLocWithOffset(1)),
483 bool NeedSpaceAfterAuto =
484 CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
486 std::string Auto = NeedSpaceAfterAuto ?
"auto " :
"auto";
488 std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
489 keepSpecifiers(
ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
493 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
494 << FixItHint::CreateInsertion(InsertionLoc,
" -> " +
ReturnType);
const google::protobuf::Message & M
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)
static bool isSpecifier(Token T)
constexpr llvm::StringLiteral Message
static bool isCvr(Token T)