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() != ElaboratedTypeKeyword::None ||
105 bool VisitDeclRefExpr(DeclRefExpr *S) {
106 DeclarationName
Name = S->getNameInfo().getName();
107 return S->getQualifierLoc() ||
Name.isEmpty() || !
Name.isIdentifier() ||
108 !visitUnqualName(
Name.getAsIdentifierInfo()->getName());
112 const FunctionDecl &F;
117 "use a trailing return type for this function";
120 const SourceManager &SM) {
123 assert(!
Loc.isMacroID() &&
124 "SourceLocation must not be a macro ID after recursive expansion");
128SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
129 const FunctionDecl &F,
const FunctionTypeLoc &FTL,
const ASTContext &Ctx,
130 const SourceManager &SM,
const LangOptions &LangOpts) {
132 SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
133 if (ExceptionSpecRange.isValid())
134 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
139 SourceLocation ClosingParen = FTL.getRParenLoc();
140 if (ClosingParen.isMacroID())
143 SourceLocation Result =
144 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
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());
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());
161 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
163 Result = T.getEndLoc();
172 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
176 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
177 tok::kw_static, tok::kw_friend, tok::kw_virtual);
180static std::optional<ClassifiedToken>
186 bool ContainsQualifiers =
false;
187 bool ContainsSpecifiers =
false;
188 bool ContainsSomethingElse =
false;
192 End.setKind(tok::eof);
193 SmallVector<Token, 2> Stream{Tok, End};
196 PP.EnterTokenStream(Stream,
false,
false);
203 bool Qual =
isCvr(T);
207 ContainsQualifiers |= Qual;
208 ContainsSpecifiers |= Spec;
209 ContainsSomethingElse |= !Qual && !Spec;
214 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
220std::optional<SmallVector<ClassifiedToken, 8>>
221UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
222 const FunctionDecl &F,
const ASTContext &Ctx,
const SourceManager &SM,
223 const LangOptions &LangOpts) {
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());
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()));
241 if (
Info.hasMacroDefinition()) {
242 const MacroInfo *MI = PP->getMacroInfo(&
Info);
243 if (!MI || MI->isFunctionLike()) {
250 T.setIdentifierInfo(&
Info);
251 T.setKind(
Info.getTokenID());
254 if (std::optional<ClassifiedToken> CT =
classifyToken(F, *PP, T))
255 ClassifiedTokens.push_back(*CT);
262 return ClassifiedTokens;
266 bool Result =
Type.hasLocalQualifiers();
267 if (
Type->isPointerType())
269 Type->castAs<PointerType>()->getPointeeType());
270 if (
Type->isReferenceType())
272 Type->castAs<ReferenceType>()->getPointeeType());
276SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
277 const FunctionDecl &F,
const TypeLoc &ReturnLoc,
const ASTContext &Ctx,
278 const SourceManager &SM,
const LangOptions &LangOpts) {
282 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
283 if (ReturnTypeRange.isInvalid()) {
293 return ReturnTypeRange;
296 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
297 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
300 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
302 ReturnTypeRange.setBegin(
expandIfMacroId(ReturnTypeRange.getBegin(), SM));
305 bool ExtendedLeft =
false;
306 for (
size_t I = 0; I < Tokens.size(); I++) {
308 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
309 ReturnTypeRange.getBegin()) &&
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;
315 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
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());
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;
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) {
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()))
348 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
349 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
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(),
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]))
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);
382 DeletedChars += TLengthWithWS;
387 auto F = functionDecl(
388 unless(anyOf(hasTrailingReturn(), returns(voidType()),
389 cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
392 Finder->addMatcher(F,
this);
393 Finder->addMatcher(friendDecl(hasDescendant(F)).bind(
"Friend"),
this);
397 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
402 assert(PP &&
"Expected registerPPCallbacks() to have been called before so "
403 "preprocessor is available");
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");
411 if (F->getLocation().isInvalid() || F->isImplicit())
415 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
417 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
423 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
424 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
425 F->getDeclaredReturnType()->isMemberPointerType()) {
430 const ASTContext &Ctx = *Result.Context;
431 const SourceManager &SM = *Result.SourceManager;
434 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
438 auto FTL = 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);
llvm::SmallString< 256U > Name
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)