1 //===--- UseTrailingReturnTypeCheck.cpp - clang-tidy-----------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "UseTrailingReturnTypeCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/RecursiveASTVisitor.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Preprocessor.h"
15 #include "clang/Tooling/FixIt.h"
16 #include "llvm/ADT/StringExtras.h"
17 
18 #include <cctype>
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang {
23 namespace tidy {
24 namespace modernize {
25 namespace {
26 struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
27 public:
UnqualNameVisitorclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor28   UnqualNameVisitor(const FunctionDecl &F) : F(F) {}
29 
30   bool Collision = false;
31 
shouldWalkTypesOfTypeLocsclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor32   bool shouldWalkTypesOfTypeLocs() const { return false; }
33 
visitUnqualNameclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor34   bool visitUnqualName(StringRef UnqualName) {
35     // Check for collisions with function arguments.
36     for (ParmVarDecl *Param : F.parameters())
37       if (const IdentifierInfo *Ident = Param->getIdentifier())
38         if (Ident->getName() == UnqualName) {
39           Collision = true;
40           return true;
41         }
42     return false;
43   }
44 
TraverseTypeLocclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor45   bool TraverseTypeLoc(TypeLoc TL, bool Elaborated = false) {
46     if (TL.isNull())
47       return true;
48 
49     if (!Elaborated) {
50       switch (TL.getTypeLocClass()) {
51       case TypeLoc::Record:
52         if (visitUnqualName(
53                 TL.getAs<RecordTypeLoc>().getTypePtr()->getDecl()->getName()))
54           return false;
55         break;
56       case TypeLoc::Enum:
57         if (visitUnqualName(
58                 TL.getAs<EnumTypeLoc>().getTypePtr()->getDecl()->getName()))
59           return false;
60         break;
61       case TypeLoc::TemplateSpecialization:
62         if (visitUnqualName(TL.getAs<TemplateSpecializationTypeLoc>()
63                                 .getTypePtr()
64                                 ->getTemplateName()
65                                 .getAsTemplateDecl()
66                                 ->getName()))
67           return false;
68         break;
69       case TypeLoc::Typedef:
70         if (visitUnqualName(
71                 TL.getAs<TypedefTypeLoc>().getTypePtr()->getDecl()->getName()))
72           return false;
73         break;
74       default:
75         break;
76       }
77     }
78 
79     return RecursiveASTVisitor<UnqualNameVisitor>::TraverseTypeLoc(TL);
80   }
81 
82   // Replace the base method in order to call ower own
83   // TraverseTypeLoc().
TraverseQualifiedTypeLocclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor84   bool TraverseQualifiedTypeLoc(QualifiedTypeLoc TL) {
85     return TraverseTypeLoc(TL.getUnqualifiedLoc());
86   }
87 
88   // Replace the base version to inform TraverseTypeLoc that the type is
89   // elaborated.
TraverseElaboratedTypeLocclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor90   bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc TL) {
91     if (TL.getQualifierLoc() &&
92         !TraverseNestedNameSpecifierLoc(TL.getQualifierLoc()))
93       return false;
94     return TraverseTypeLoc(TL.getNamedTypeLoc(), true);
95   }
96 
VisitDeclRefExprclang::tidy::modernize::__anon56941aaf0111::UnqualNameVisitor97   bool VisitDeclRefExpr(DeclRefExpr *S) {
98     DeclarationName Name = S->getNameInfo().getName();
99     return S->getQualifierLoc() || !Name.isIdentifier() ||
100            !visitUnqualName(Name.getAsIdentifierInfo()->getName());
101   }
102 
103 private:
104   const FunctionDecl &F;
105 };
106 } // namespace
107 
108 constexpr llvm::StringLiteral Message =
109     "use a trailing return type for this function";
110 
expandIfMacroId(SourceLocation Loc,const SourceManager & SM)111 static SourceLocation expandIfMacroId(SourceLocation Loc,
112                                       const SourceManager &SM) {
113   if (Loc.isMacroID())
114     Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
115   assert(!Loc.isMacroID() &&
116          "SourceLocation must not be a macro ID after recursive expansion");
117   return Loc;
118 }
119 
findTrailingReturnTypeSourceLocation(const FunctionDecl & F,const FunctionTypeLoc & FTL,const ASTContext & Ctx,const SourceManager & SM,const LangOptions & LangOpts)120 SourceLocation UseTrailingReturnTypeCheck::findTrailingReturnTypeSourceLocation(
121     const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
122     const SourceManager &SM, const LangOptions &LangOpts) {
123   // We start with the location of the closing parenthesis.
124   SourceRange ExceptionSpecRange = F.getExceptionSpecSourceRange();
125   if (ExceptionSpecRange.isValid())
126     return Lexer::getLocForEndOfToken(ExceptionSpecRange.getEnd(), 0, SM,
127                                       LangOpts);
128 
129   // If the function argument list ends inside of a macro, it is dangerous to
130   // start lexing from here - bail out.
131   SourceLocation ClosingParen = FTL.getRParenLoc();
132   if (ClosingParen.isMacroID())
133     return {};
134 
135   SourceLocation Result =
136       Lexer::getLocForEndOfToken(ClosingParen, 0, SM, LangOpts);
137 
138   // Skip subsequent CV and ref qualifiers.
139   std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(Result);
140   StringRef File = SM.getBufferData(Loc.first);
141   const char *TokenBegin = File.data() + Loc.second;
142   Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
143               TokenBegin, File.end());
144   Token T;
145   while (!Lexer.LexFromRawLexer(T)) {
146     if (T.is(tok::raw_identifier)) {
147       IdentifierInfo &Info = Ctx.Idents.get(
148           StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
149       T.setIdentifierInfo(&Info);
150       T.setKind(Info.getTokenID());
151     }
152 
153     if (T.isOneOf(tok::amp, tok::ampamp, tok::kw_const, tok::kw_volatile,
154                   tok::kw_restrict)) {
155       Result = T.getEndLoc();
156       continue;
157     }
158     break;
159   }
160   return Result;
161 }
162 
isCvr(Token T)163 static bool isCvr(Token T) {
164   return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
165 }
166 
isSpecifier(Token T)167 static bool isSpecifier(Token T) {
168   return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
169                    tok::kw_static, tok::kw_friend, tok::kw_virtual);
170 }
171 
172 static llvm::Optional<ClassifiedToken>
classifyToken(const FunctionDecl & F,Preprocessor & PP,Token Tok)173 classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
174   ClassifiedToken CT;
175   CT.T = Tok;
176   CT.isQualifier = true;
177   CT.isSpecifier = true;
178   bool ContainsQualifiers = false;
179   bool ContainsSpecifiers = false;
180   bool ContainsSomethingElse = false;
181 
182   Token End;
183   End.startToken();
184   End.setKind(tok::eof);
185   SmallVector<Token, 2> Stream{Tok, End};
186 
187   // FIXME: do not report these token to Preprocessor.TokenWatcher.
188   PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
189   while (true) {
190     Token T;
191     PP.Lex(T);
192     if (T.is(tok::eof))
193       break;
194 
195     bool Qual = isCvr(T);
196     bool Spec = isSpecifier(T);
197     CT.isQualifier &= Qual;
198     CT.isSpecifier &= Spec;
199     ContainsQualifiers |= Qual;
200     ContainsSpecifiers |= Spec;
201     ContainsSomethingElse |= !Qual && !Spec;
202   }
203 
204   // If the Token/Macro contains more than one type of tokens, we would need
205   // to split the macro in order to move parts to the trailing return type.
206   if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
207     return llvm::None;
208 
209   return CT;
210 }
211 
212 llvm::Optional<SmallVector<ClassifiedToken, 8>>
classifyTokensBeforeFunctionName(const FunctionDecl & F,const ASTContext & Ctx,const SourceManager & SM,const LangOptions & LangOpts)213 UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
214     const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
215     const LangOptions &LangOpts) {
216   SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
217   SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
218 
219   // Create tokens for everything before the name of the function.
220   std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
221   StringRef File = SM.getBufferData(Loc.first);
222   const char *TokenBegin = File.data() + Loc.second;
223   Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
224               TokenBegin, File.end());
225   Token T;
226   SmallVector<ClassifiedToken, 8> ClassifiedTokens;
227   while (!Lexer.LexFromRawLexer(T) &&
228          SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
229     if (T.is(tok::raw_identifier)) {
230       IdentifierInfo &Info = Ctx.Idents.get(
231           StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
232 
233       if (Info.hasMacroDefinition()) {
234         const MacroInfo *MI = PP->getMacroInfo(&Info);
235         if (!MI || MI->isFunctionLike()) {
236           // Cannot handle function style macros.
237           diag(F.getLocation(), Message);
238           return llvm::None;
239         }
240       }
241 
242       T.setIdentifierInfo(&Info);
243       T.setKind(Info.getTokenID());
244     }
245 
246     if (llvm::Optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
247       ClassifiedTokens.push_back(*CT);
248     else {
249       diag(F.getLocation(), Message);
250       return llvm::None;
251     }
252   }
253 
254   return ClassifiedTokens;
255 }
256 
hasAnyNestedLocalQualifiers(QualType Type)257 static bool hasAnyNestedLocalQualifiers(QualType Type) {
258   bool Result = Type.hasLocalQualifiers();
259   if (Type->isPointerType())
260     Result = Result || hasAnyNestedLocalQualifiers(
261                            Type->castAs<PointerType>()->getPointeeType());
262   if (Type->isReferenceType())
263     Result = Result || hasAnyNestedLocalQualifiers(
264                            Type->castAs<ReferenceType>()->getPointeeType());
265   return Result;
266 }
267 
findReturnTypeAndCVSourceRange(const FunctionDecl & F,const TypeLoc & ReturnLoc,const ASTContext & Ctx,const SourceManager & SM,const LangOptions & LangOpts)268 SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange(
269     const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx,
270     const SourceManager &SM, const LangOptions &LangOpts) {
271 
272   // We start with the range of the return type and expand to neighboring
273   // qualifiers (const, volatile and restrict).
274   SourceRange ReturnTypeRange = F.getReturnTypeSourceRange();
275   if (ReturnTypeRange.isInvalid()) {
276     // Happens if e.g. clang cannot resolve all includes and the return type is
277     // unknown.
278     diag(F.getLocation(), Message);
279     return {};
280   }
281 
282   // If the return type is a constrained 'auto' or 'decltype(auto)', we need to
283   // include the tokens after the concept. Unfortunately, the source range of an
284   // AutoTypeLoc, if it is constrained, does not include the 'auto' or
285   // 'decltype(auto)'. If the return type is a plain 'decltype(...)', the
286   // source range only contains the first 'decltype' token.
287   auto ATL = ReturnLoc.getAs<AutoTypeLoc>();
288   if ((ATL && (ATL.isConstrained() ||
289                ATL.getAutoKeyword() == AutoTypeKeyword::DecltypeAuto)) ||
290       ReturnLoc.getAs<DecltypeTypeLoc>()) {
291     SourceLocation End =
292         expandIfMacroId(ReturnLoc.getSourceRange().getEnd(), SM);
293     SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
294 
295     // Extend the ReturnTypeRange until the last token before the function
296     // name.
297     std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(End);
298     StringRef File = SM.getBufferData(Loc.first);
299     const char *TokenBegin = File.data() + Loc.second;
300     Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
301                 TokenBegin, File.end());
302     Token T;
303     SourceLocation LastTLoc = End;
304     while (!Lexer.LexFromRawLexer(T) &&
305            SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
306       LastTLoc = T.getLocation();
307     }
308     ReturnTypeRange.setEnd(LastTLoc);
309   }
310 
311   // If the return type has no local qualifiers, it's source range is accurate.
312   if (!hasAnyNestedLocalQualifiers(F.getReturnType()))
313     return ReturnTypeRange;
314 
315   // Include qualifiers to the left and right of the return type.
316   llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
317       classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
318   if (!MaybeTokens)
319     return {};
320   const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
321 
322   ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
323   ReturnTypeRange.setEnd(expandIfMacroId(ReturnTypeRange.getEnd(), SM));
324 
325   bool ExtendedLeft = false;
326   for (size_t I = 0; I < Tokens.size(); I++) {
327     // If we found the beginning of the return type, include left qualifiers.
328     if (!SM.isBeforeInTranslationUnit(Tokens[I].T.getLocation(),
329                                       ReturnTypeRange.getBegin()) &&
330         !ExtendedLeft) {
331       assert(I <= size_t(std::numeric_limits<int>::max()) &&
332              "Integer overflow detected");
333       for (int J = static_cast<int>(I) - 1; J >= 0 && Tokens[J].isQualifier;
334            J--)
335         ReturnTypeRange.setBegin(Tokens[J].T.getLocation());
336       ExtendedLeft = true;
337     }
338     // If we found the end of the return type, include right qualifiers.
339     if (SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
340                                      Tokens[I].T.getLocation())) {
341       for (size_t J = I; J < Tokens.size() && Tokens[J].isQualifier; J++)
342         ReturnTypeRange.setEnd(Tokens[J].T.getLocation());
343       break;
344     }
345   }
346 
347   assert(!ReturnTypeRange.getBegin().isMacroID() &&
348          "Return type source range begin must not be a macro");
349   assert(!ReturnTypeRange.getEnd().isMacroID() &&
350          "Return type source range end must not be a macro");
351   return ReturnTypeRange;
352 }
353 
keepSpecifiers(std::string & ReturnType,std::string & Auto,SourceRange ReturnTypeCVRange,const FunctionDecl & F,const FriendDecl * Fr,const ASTContext & Ctx,const SourceManager & SM,const LangOptions & LangOpts)354 void UseTrailingReturnTypeCheck::keepSpecifiers(
355     std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange,
356     const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx,
357     const SourceManager &SM, const LangOptions &LangOpts) {
358   // Check if there are specifiers inside the return type. E.g. unsigned
359   // inline int.
360   const auto *M = dyn_cast<CXXMethodDecl>(&F);
361   if (!F.isConstexpr() && !F.isInlineSpecified() &&
362       F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static &&
363       !Fr && !(M && M->isVirtualAsWritten()))
364     return;
365 
366   // Tokenize return type. If it contains macros which contain a mix of
367   // qualifiers, specifiers and types, give up.
368   llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
369       classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
370   if (!MaybeTokens)
371     return;
372 
373   // Find specifiers, remove them from the return type, add them to 'auto'.
374   unsigned int ReturnTypeBeginOffset =
375       SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second;
376   size_t InitialAutoLength = Auto.size();
377   unsigned int DeletedChars = 0;
378   for (ClassifiedToken CT : *MaybeTokens) {
379     if (SM.isBeforeInTranslationUnit(CT.T.getLocation(),
380                                      ReturnTypeCVRange.getBegin()) ||
381         SM.isBeforeInTranslationUnit(ReturnTypeCVRange.getEnd(),
382                                      CT.T.getLocation()))
383       continue;
384     if (!CT.isSpecifier)
385       continue;
386 
387     // Add the token to 'auto' and remove it from the return type, including
388     // any whitespace following the token.
389     unsigned int TOffset = SM.getDecomposedLoc(CT.T.getLocation()).second;
390     assert(TOffset >= ReturnTypeBeginOffset &&
391            "Token location must be after the beginning of the return type");
392     unsigned int TOffsetInRT = TOffset - ReturnTypeBeginOffset - DeletedChars;
393     unsigned int TLengthWithWS = CT.T.getLength();
394     while (TOffsetInRT + TLengthWithWS < ReturnType.size() &&
395            llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS]))
396       TLengthWithWS++;
397     std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS);
398     if (!llvm::isSpace(Specifier.back()))
399       Specifier.push_back(' ');
400     Auto.insert(Auto.size() - InitialAutoLength, Specifier);
401     ReturnType.erase(TOffsetInRT, TLengthWithWS);
402     DeletedChars += TLengthWithWS;
403   }
404 }
405 
registerMatchers(MatchFinder * Finder)406 void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
407   auto F = functionDecl(
408                unless(anyOf(hasTrailingReturn(), returns(voidType()),
409                             cxxConversionDecl(), cxxMethodDecl(isImplicit()))))
410                .bind("Func");
411 
412   Finder->addMatcher(F, this);
413   Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this);
414 }
415 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)416 void UseTrailingReturnTypeCheck::registerPPCallbacks(
417     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
418   this->PP = PP;
419 }
420 
check(const MatchFinder::MatchResult & Result)421 void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) {
422   assert(PP && "Expected registerPPCallbacks() to have been called before so "
423                "preprocessor is available");
424 
425   const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func");
426   const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend");
427   assert(F && "Matcher is expected to find only FunctionDecls");
428 
429   if (F->getLocation().isInvalid())
430     return;
431 
432   // Skip functions which return just 'auto'.
433   const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>();
434   if (AT != nullptr && !AT->isConstrained() &&
435       AT->getKeyword() == AutoTypeKeyword::Auto &&
436       !hasAnyNestedLocalQualifiers(F->getDeclaredReturnType()))
437     return;
438 
439   // TODO: implement those
440   if (F->getDeclaredReturnType()->isFunctionPointerType() ||
441       F->getDeclaredReturnType()->isMemberFunctionPointerType() ||
442       F->getDeclaredReturnType()->isMemberPointerType()) {
443     diag(F->getLocation(), Message);
444     return;
445   }
446 
447   const ASTContext &Ctx = *Result.Context;
448   const SourceManager &SM = *Result.SourceManager;
449   const LangOptions &LangOpts = getLangOpts();
450 
451   const TypeSourceInfo *TSI = F->getTypeSourceInfo();
452   if (!TSI)
453     return;
454 
455   FunctionTypeLoc FTL =
456       TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
457   if (!FTL) {
458     // FIXME: This may happen if we have __attribute__((...)) on the function.
459     // We abort for now. Remove this when the function type location gets
460     // available in clang.
461     diag(F->getLocation(), Message);
462     return;
463   }
464 
465   SourceLocation InsertionLoc =
466       findTrailingReturnTypeSourceLocation(*F, FTL, Ctx, SM, LangOpts);
467   if (InsertionLoc.isInvalid()) {
468     diag(F->getLocation(), Message);
469     return;
470   }
471 
472   // Using the declared return type via F->getDeclaredReturnType().getAsString()
473   // discards user formatting and order of const, volatile, type, whitespace,
474   // space before & ... .
475   SourceRange ReturnTypeCVRange =
476       findReturnTypeAndCVSourceRange(*F, FTL.getReturnLoc(), Ctx, SM, LangOpts);
477   if (ReturnTypeCVRange.isInvalid())
478     return;
479 
480   // Check if unqualified names in the return type conflict with other entities
481   // after the rewrite.
482   // FIXME: this could be done better, by performing a lookup of all
483   // unqualified names in the return type in the scope of the function. If the
484   // lookup finds a different entity than the original entity identified by the
485   // name, then we can either not perform a rewrite or explicitly qualify the
486   // entity. Such entities could be function parameter names, (inherited) class
487   // members, template parameters, etc.
488   UnqualNameVisitor UNV{*F};
489   UNV.TraverseTypeLoc(FTL.getReturnLoc());
490   if (UNV.Collision) {
491     diag(F->getLocation(), Message);
492     return;
493   }
494 
495   SourceLocation ReturnTypeEnd =
496       Lexer::getLocForEndOfToken(ReturnTypeCVRange.getEnd(), 0, SM, LangOpts);
497   StringRef CharAfterReturnType = Lexer::getSourceText(
498       CharSourceRange::getCharRange(ReturnTypeEnd,
499                                     ReturnTypeEnd.getLocWithOffset(1)),
500       SM, LangOpts);
501   bool NeedSpaceAfterAuto =
502       CharAfterReturnType.empty() || !llvm::isSpace(CharAfterReturnType[0]);
503 
504   std::string Auto = NeedSpaceAfterAuto ? "auto " : "auto";
505   std::string ReturnType =
506       std::string(tooling::fixit::getText(ReturnTypeCVRange, Ctx));
507   keepSpecifiers(ReturnType, Auto, ReturnTypeCVRange, *F, Fr, Ctx, SM,
508                  LangOpts);
509 
510   diag(F->getLocation(), Message)
511       << FixItHint::CreateReplacement(ReturnTypeCVRange, Auto)
512       << FixItHint::CreateInsertion(InsertionLoc, " -> " + ReturnType);
513 }
514 
515 } // namespace modernize
516 } // namespace tidy
517 } // namespace clang
518