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