clang-tools 23.0.0git
UseTrailingReturnTypeCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 <optional>
18
19namespace clang::tidy {
20
21template <>
23 modernize::UseTrailingReturnTypeCheck::TransformLambda> {
24 static llvm::ArrayRef<std::pair<
27 static constexpr std::pair<
29 Mapping[] = {
31 "all"},
32 {modernize::UseTrailingReturnTypeCheck::TransformLambda::
33 AllExceptAuto,
34 "all_except_auto"},
36 "none"}};
37 return Mapping;
38 }
39};
40
41} // namespace clang::tidy
42
43using namespace clang::ast_matchers;
44
45namespace clang::tidy::modernize {
46namespace {
47struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
48public:
49 UnqualNameVisitor(const FunctionDecl &F) : F(F) {}
50
51 bool Collision = false;
52
53 bool shouldWalkTypesOfTypeLocs() const { return false; }
54
55 bool visitUnqualName(StringRef UnqualName) {
56 // Check for collisions with function arguments.
57 Collision = llvm::any_of(F.parameters(), [&](const ParmVarDecl *Param) {
58 if (const IdentifierInfo *Ident = Param->getIdentifier())
59 return Ident->getName() == UnqualName;
60 return false;
61 });
62 return Collision;
63 }
64
65 bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) {
66 if (TL.isNull())
67 return true;
68
69 switch (TL.getTypeLocClass()) {
70 case TypeLoc::InjectedClassName:
71 case TypeLoc::Record:
72 case TypeLoc::Enum: {
73 auto TTL = TL.getAs<TagTypeLoc>();
74 const auto *T = TTL.getTypePtr();
75 if (T->getKeyword() != ElaboratedTypeKeyword::None ||
76 TTL.getQualifierLoc())
77 break;
78 if (visitUnqualName(T->getDecl()->getName()))
79 return false;
80 break;
81 }
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())
87 break;
88 if (visitUnqualName(T->getTemplateName().getAsTemplateDecl()->getName()))
89 return false;
90 break;
91 }
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())
97 break;
98 if (visitUnqualName(T->getDecl()->getName()))
99 return false;
100 break;
101 }
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())
107 break;
108 if (visitUnqualName(T->getDecl()->getName()))
109 return false;
110 break;
111 }
112 default:
113 break;
114 }
115
116 return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(
117 TL, TraverseQualifier);
118 }
119
120 // Replace the base method in order to call our own
121 // TraverseTypeLoc().
122 bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL, bool TraverseQualifier) {
123 return TraverseTypeLoc(TL.getUnqualifiedLoc(), TraverseQualifier);
124 }
125
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());
130 }
131
132private:
133 const FunctionDecl &F;
134};
135
136AST_MATCHER(LambdaExpr, hasExplicitResultType) {
137 return Node.hasExplicitResultType();
138}
139
140} // namespace
141
142constexpr StringRef ErrorMessageOnFunction =
143 "use a trailing return type for this function";
144constexpr StringRef ErrorMessageOnLambda =
145 "use a trailing return type for this lambda";
146
147static SourceLocation expandIfMacroId(SourceLocation Loc,
148 const SourceManager &SM) {
149 if (Loc.isMacroID())
150 Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
151 assert(!Loc.isMacroID() &&
152 "SourceLocation must not be a macro ID after recursive expansion");
153 return Loc;
154}
155
157 const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
158 const SourceManager &SM, const LangOptions &LangOpts) {
159 // We start with the location of the closing parenthesis.
160 const SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
161 if (ExceptionSpecRange.isValid())
162 return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
163 LangOpts);
164
165 // If the function argument list ends inside of a macro, it is dangerous to
166 // start lexing from here - bail out.
167 const SourceLocation ClosingParen = FTL.getRParenLoc();
168 if (ClosingParen.isMacroID())
169 return {};
170
171 SourceLocation Result =
172 Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
173
174 // Skip subsequent CV and ref qualifiers.
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());
180 Token T;
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());
187 }
188
189 if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
190 tok::kw_restrict)) {
191 Result = T.getEndLoc();
192 continue;
193 }
194 break;
195 }
196 return Result;
197}
198
199static bool isCvr(Token T) {
200 return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
201}
202
203static bool isSpecifier(Token T) {
204 return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
205 tok::kw_static, tok::kw_friend, tok::kw_virtual);
206}
207
208namespace {
209
210struct ClassifiedToken {
211 Token T;
212 bool IsQualifier;
213 bool IsSpecifier;
214};
215
216} // namespace
217
218static std::optional<ClassifiedToken>
219classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
220 ClassifiedToken CT;
221 CT.T = Tok;
222 CT.IsQualifier = true;
223 CT.IsSpecifier = true;
224 bool ContainsQualifiers = false;
225 bool ContainsSpecifiers = false;
226 bool ContainsSomethingElse = false;
227
228 Token End;
229 End.startToken();
230 End.setKind(tok::eof);
231 const std::array<Token, 2> Stream{Tok, End};
232
233 // FIXME: do not report these token to Preprocessor.TokenWatcher.
234 PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
235 while (true) {
236 Token T;
237 PP.Lex(T);
238 if (T.is(tok::eof))
239 break;
240
241 const bool Qual = isCvr(T);
242 const bool Spec = isSpecifier(T);
243 CT.IsQualifier &= Qual;
244 CT.IsSpecifier &= Spec;
245 ContainsQualifiers |= Qual;
246 ContainsSpecifiers |= Spec;
247 ContainsSomethingElse |= !Qual && !Spec;
248 }
249
250 // If the Token/Macro contains more than one type of tokens, we would need
251 // to split the macro in order to move parts to the trailing return type.
252 if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
253 return std::nullopt;
254
255 return CT;
256}
257
258static std::optional<SmallVector<ClassifiedToken, 8>>
259classifyTokensBeforeFunctionName(const FunctionDecl &F, const ASTContext &Ctx,
260 const SourceManager &SM,
261 const LangOptions &LangOpts,
262 Preprocessor *PP) {
263 const SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
264 const SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
265
266 // Create tokens for everything before the name of the function.
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());
272 Token T;
273 SmallVector<ClassifiedToken, 8> ClassifiedTokens;
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()));
279
280 if (Info.hasMacroDefinition()) {
281 const MacroInfo *MI = PP->getMacroInfo(&Info);
282 if (!MI || MI->isFunctionLike() || MI->isBuiltinMacro())
283 return std::nullopt;
284 }
285
286 T.setIdentifierInfo(&Info);
287 T.setKind(Info.getTokenID());
288 }
289
290 if (std::optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
291 ClassifiedTokens.push_back(*CT);
292 else
293 return std::nullopt;
294 }
295
296 return ClassifiedTokens;
297}
298
299static bool hasAnyNestedLocalQualifiers(QualType Type) {
300 bool Result = Type.hasLocalQualifiers();
301 if (Type->isPointerType())
302 Result = Result || hasAnyNestedLocalQualifiers(
303 Type->castAs<PointerType>()->getPointeeType());
304 if (Type->isReferenceType())
305 Result = Result || hasAnyNestedLocalQualifiers(
306 Type->castAs<ReferenceType>()->getPointeeType());
307 return Result;
308}
309
310static SourceRange
311findReturnTypeAndCVSourceRange(const FunctionDecl &F, const TypeLoc &ReturnLoc,
312 const ASTContext &Ctx, const SourceManager &SM,
313 const LangOptions &LangOpts, Preprocessor *PP) {
314 // We start with the range of the return type and expand to neighboring
315 // qualifiers (const, volatile and restrict).
316 SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
317 if (ReturnTypeRange.isInvalid()) {
318 // Happens if e.g. clang cannot resolve all includes and the return type is
319 // unknown.
320 return {};
321 }
322
323 // If the return type has no local qualifiers, it's source range is accurate.
324 if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
325 return ReturnTypeRange;
326
327 // Include qualifiers to the left and right of the return type.
328 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
329 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts, PP);
330 if (!MaybeTokens)
331 return {};
332 const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
333
334 ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
335 ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
336
337 bool ExtendedLeft = false;
338 for (size_t I = 0; I < Tokens.size(); I++) {
339 // If we found the beginning of the return type, include left qualifiers.
340 if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
341 ReturnTypeRange.getBegin()) &&
342 !ExtendedLeft) {
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;
346 J--)
347 ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
348 ExtendedLeft = true;
349 }
350 // If we found the end of the return type, include right qualifiers.
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());
355 break;
356 }
357 }
358
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;
364}
365
367 const CXXMethodDecl *Method, const SourceManager &SM,
368 const LangOptions &LangOpts, const ASTContext &Ctx) {
369 // 'requires' keyword is present in lambda declaration
370 if (Method->getTrailingRequiresClause()) {
371 SourceLocation ParamEndLoc;
372 if (Method->param_empty())
373 ParamEndLoc = Method->getBeginLoc();
374 else
375 ParamEndLoc = Method->getParametersSourceRange().getEnd();
376
377 const std::pair<FileID, unsigned> ParamEndLocInfo =
378 SM.getDecomposedLoc(ParamEndLoc);
379 const StringRef Buffer = SM.getBufferData(ParamEndLocInfo.first);
380
381 Lexer Lexer(SM.getLocForStartOfFile(ParamEndLocInfo.first), LangOpts,
382 Buffer.begin(), Buffer.data() + ParamEndLocInfo.second,
383 Buffer.end());
384
385 Token Token;
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());
392 }
393
394 if (Token.is(tok::kw_requires))
395 return Token.getLocation().getLocWithOffset(-1);
396 }
397
398 return {};
399 }
400
401 // If no requires clause, insert before the body
402 if (const Stmt *Body = Method->getBody())
403 return Body->getBeginLoc().getLocWithOffset(-1);
404
405 return {};
406}
407
408static void keepSpecifiers(std::string &ReturnType, std::string &Auto,
409 SourceRange ReturnTypeCVRange, const FunctionDecl &F,
410 const FriendDecl *Fr, const ASTContext &Ctx,
411 const SourceManager &SM, const LangOptions &LangOpts,
412 Preprocessor *PP) {
413 // Check if there are specifiers inside the return type. E.g. unsigned
414 // inline int.
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()))
419 return;
420
421 // Tokenize return type. If it contains macros which contain a mix of
422 // qualifiers, specifiers and types, give up.
423 std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
424 classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts, PP);
425 if (!MaybeTokens)
426 return;
427
428 // Find specifiers, remove them from the return type, add them to 'auto'.
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(),
437 CT.T.getLocation()))
438 continue;
439 if (!CT.IsSpecifier)
440 continue;
441
442 // Add the token to 'auto' and remove it from the return type, including
443 // any whitespace following the token.
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]))
452 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;
459 }
460}
461
463 StringRef Name, ClangTidyContext *Context)
464 : ClangTidyCheck(Name, Context),
465 TransformFunctions(Options.get("TransformFunctions", true)),
466 TransformLambdas(Options.get("TransformLambdas", TransformLambda::All)) {
467 if (TransformFunctions == false && TransformLambdas == TransformLambda::None)
468 this->configurationDiag(
469 "The check 'modernize-use-trailing-return-type' will not perform any "
470 "analysis because 'TransformFunctions' and 'TransformLambdas' are "
471 "disabled.");
472}
473
476 Options.store(Opts, "TransformFunctions", TransformFunctions);
477 Options.store(Opts, "TransformLambdas", TransformLambdas);
478}
479
481 auto F =
482 functionDecl(
483 unless(anyOf(
484 hasTrailingReturn(), returns(voidType()), cxxConversionDecl(),
485 cxxMethodDecl(
486 anyOf(isImplicit(),
487 hasParent(cxxRecordDecl(hasParent(lambdaExpr()))))))))
488 .bind("Func");
489
490 if (TransformFunctions) {
491 Finder->addMatcher(F, this);
492 Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
493 }
494
495 if (TransformLambdas != TransformLambda::None)
496 Finder->addMatcher(
497 lambdaExpr(unless(hasExplicitResultType())).bind("Lambda"), this);
498}
499
501 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
502 this->PP = PP;
503}
504
505void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
506 assert(PP && "Expected registerPPCallbacks() to have been called before so "
507 "preprocessor is available");
508
509 if (const auto *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("Lambda")) {
510 diagOnLambda(Lambda, Result);
511 return;
512 }
513
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");
517
518 // Three-way comparison operator<=> is syntactic sugar and generates implicit
519 // nodes for all other operators.
520 if (F->getLocation().isInvalid() || F->isImplicit())
521 return;
522
523 // Skip functions which return 'auto' and defaulted operators.
524 const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
525 if (AT != nullptr &&
526 ((!AT->isConstrained() && AT->getKeyword() == AutoTypeKeyword::Auto &&
527 !hasAnyNestedLocalQualifiers(F->getDeclaredReturnType())) ||
528 F->isDefaulted()))
529 return;
530
531 // TODO: implement those
532 if (F->getDeclaredReturnType()->isFunctionPointerType() ||
533 F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
534 F->getDeclaredReturnType()->isMemberPointerType()) {
535 diag(F->getLocation(), ErrorMessageOnFunction);
536 return;
537 }
538
539 const ASTContext &Ctx = *Result.Context;
540 const SourceManager &SM = *Result.SourceManager;
541 const LangOptions &LangOpts = getLangOpts();
542
543 const TypeSourceInfo *TSI = F->getTypeSourceInfo();
544 if (!TSI)
545 return;
546
547 auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
548 if (!FTL) {
549 // FIXME: This may happen if we have __attribute__((...)) on the function.
550 // We abort for now. Remove this when the function type location gets
551 // available in clang.
552 diag(F->getLocation(), ErrorMessageOnFunction);
553 return;
554 }
555
556 const SourceLocation InsertionLoc =
557 findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
558 if (InsertionLoc.isInvalid()) {
559 diag(F->getLocation(), ErrorMessageOnFunction);
560 return;
561 }
562
563 // Using the declared return type via F->getDeclaredReturnType().getAsString()
564 // discards user formatting and order of const, volatile, type, whitespace,
565 // space before & ... .
566 const SourceRange ReturnTypeCVRange = findReturnTypeAndCVSourceRange(
567 *F, FTL.getReturnLoc(), Ctx, SM, LangOpts, PP);
568 if (ReturnTypeCVRange.isInvalid()) {
569 diag(F->getLocation(), ErrorMessageOnFunction);
570 return;
571 }
572
573 // Check if unqualified names in the return type conflict with other entities
574 // after the rewrite.
575 // FIXME: this could be done better, by performing a lookup of all
576 // unqualified names in the return type in the scope of the function. If the
577 // lookup finds a different entity than the original entity identified by the
578 // name, then we can either not perform a rewrite or explicitly qualify the
579 // entity. Such entities could be function parameter names, (inherited) class
580 // members, template parameters, etc.
581 UnqualNameVisitor UNV{*F};
582 UNV.TraverseTypeLoc(FTL.getReturnLoc());
583 if (UNV.Collision) {
584 diag(F->getLocation(), ErrorMessageOnFunction);
585 return;
586 }
587
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)),
593 SM, LangOpts);
594 const bool NeedSpaceAfterAuto =
595 CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
596
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,
601 PP);
602
603 diag(F->getLocation(), ErrorMessageOnFunction)
604 << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
605 << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
606}
607
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())
613 return;
614
615 const ASTContext *Ctx = Result.Context;
616 const QualType ReturnType = Method->getReturnType();
617
618 // We can't write 'auto' in C++11 mode, try to write generic msg and bail out.
619 if (ReturnType->isDependentType() &&
620 Ctx->getLangOpts().LangStd == LangStandard::lang_cxx11) {
621 if (TransformLambdas == TransformLambda::All)
622 diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
623 return;
624 }
625
626 if (ReturnType->isUndeducedAutoType() &&
627 TransformLambdas == TransformLambda::AllExceptAuto)
628 return;
629
630 const SourceLocation TrailingReturnInsertLoc =
631 findLambdaTrailingReturnInsertLoc(Method, *Result.SourceManager,
632 getLangOpts(), *Result.Context);
633
634 if (TrailingReturnInsertLoc.isValid())
635 diag(Lambda->getBeginLoc(), "use a trailing return type for this lambda")
636 << FixItHint::CreateInsertion(
637 TrailingReturnInsertLoc,
638 " -> " +
639 ReturnType.getAsString(Result.Context->getPrintingPolicy()));
640 else
641 diag(Lambda->getBeginLoc(), ErrorMessageOnLambda);
642}
643
644} // namespace clang::tidy::modernize
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)
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 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)
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< modernize::UseTrailingReturnTypeCheck::TransformLambda, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...