xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp (revision e87f94a6a806a941242506680f88573d6a87a828)
1 //===--- UseAutoCheck.cpp - clang-tidy-------------------------------------===//
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 
9 #include "UseAutoCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/TypeLoc.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Basic/CharInfo.h"
15 #include "clang/Tooling/FixIt.h"
16 #include "llvm/ADT/STLExtras.h"
17 
18 using namespace clang;
19 using namespace clang::ast_matchers;
20 using namespace clang::ast_matchers::internal;
21 
22 namespace clang::tidy::modernize {
23 namespace {
24 
25 const char IteratorDeclStmtId[] = "iterator_decl";
26 const char DeclWithNewId[] = "decl_new";
27 const char DeclWithCastId[] = "decl_cast";
28 const char DeclWithTemplateCastId[] = "decl_template";
29 
30 size_t getTypeNameLength(bool RemoveStars, StringRef Text) {
31   enum CharType { Space, Alpha, Punctuation };
32   CharType LastChar = Space, BeforeSpace = Punctuation;
33   size_t NumChars = 0;
34   int TemplateTypenameCntr = 0;
35   for (const unsigned char C : Text) {
36     if (C == '<')
37       ++TemplateTypenameCntr;
38     else if (C == '>')
39       --TemplateTypenameCntr;
40     const CharType NextChar =
41         isAlphanumeric(C)
42             ? Alpha
43             : (isWhitespace(C) ||
44                (!RemoveStars && TemplateTypenameCntr == 0 && C == '*'))
45                   ? Space
46                   : Punctuation;
47     if (NextChar != Space) {
48       ++NumChars; // Count the non-space character.
49       if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha)
50         ++NumChars; // Count a single space character between two words.
51       BeforeSpace = NextChar;
52     }
53     LastChar = NextChar;
54   }
55   return NumChars;
56 }
57 
58 /// Matches variable declarations that have explicit initializers that
59 /// are not initializer lists.
60 ///
61 /// Given
62 /// \code
63 ///   iterator I = Container.begin();
64 ///   MyType A(42);
65 ///   MyType B{2};
66 ///   MyType C;
67 /// \endcode
68 ///
69 /// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B
70 /// or \c C.
71 AST_MATCHER(VarDecl, hasWrittenNonListInitializer) {
72   const Expr *Init = Node.getAnyInitializer();
73   if (!Init)
74     return false;
75 
76   Init = Init->IgnoreImplicit();
77 
78   // The following test is based on DeclPrinter::VisitVarDecl() to find if an
79   // initializer is implicit or not.
80   if (const auto *Construct = dyn_cast<CXXConstructExpr>(Init)) {
81     return !Construct->isListInitialization() && Construct->getNumArgs() > 0 &&
82            !Construct->getArg(0)->isDefaultArgument();
83   }
84   return Node.getInitStyle() != VarDecl::ListInit;
85 }
86 
87 /// Matches QualTypes that are type sugar for QualTypes that match \c
88 /// SugarMatcher.
89 ///
90 /// Given
91 /// \code
92 ///   class C {};
93 ///   typedef C my_type;
94 ///   typedef my_type my_other_type;
95 /// \endcode
96 ///
97 /// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C"))))))
98 /// matches \c my_type and \c my_other_type.
99 AST_MATCHER_P(QualType, isSugarFor, Matcher<QualType>, SugarMatcher) {
100   QualType QT = Node;
101   while (true) {
102     if (SugarMatcher.matches(QT, Finder, Builder))
103       return true;
104 
105     QualType NewQT = QT.getSingleStepDesugaredType(Finder->getASTContext());
106     if (NewQT == QT)
107       return false;
108     QT = NewQT;
109   }
110 }
111 
112 /// Matches named declarations that have one of the standard iterator
113 /// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator.
114 ///
115 /// Given
116 /// \code
117 ///   iterator I;
118 ///   const_iterator CI;
119 /// \endcode
120 ///
121 /// namedDecl(hasStdIteratorName()) matches \c I and \c CI.
122 Matcher<NamedDecl> hasStdIteratorName() {
123   static const StringRef IteratorNames[] = {"iterator", "reverse_iterator",
124                                             "const_iterator",
125                                             "const_reverse_iterator"};
126   return hasAnyName(IteratorNames);
127 }
128 
129 /// Matches named declarations that have one of the standard container
130 /// names.
131 ///
132 /// Given
133 /// \code
134 ///   class vector {};
135 ///   class forward_list {};
136 ///   class my_ver{};
137 /// \endcode
138 ///
139 /// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list
140 /// but not \c my_vec.
141 Matcher<NamedDecl> hasStdContainerName() {
142   static StringRef ContainerNames[] = {"array",         "deque",
143                                        "forward_list",  "list",
144                                        "vector",
145 
146                                        "map",           "multimap",
147                                        "set",           "multiset",
148 
149                                        "unordered_map", "unordered_multimap",
150                                        "unordered_set", "unordered_multiset",
151 
152                                        "queue",         "priority_queue",
153                                        "stack"};
154 
155   return hasAnyName(ContainerNames);
156 }
157 
158 /// Matches declaration reference or member expressions with explicit template
159 /// arguments.
160 AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs,
161                         AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr,
162                                                         MemberExpr)) {
163   return Node.hasExplicitTemplateArgs();
164 }
165 
166 /// Returns a DeclarationMatcher that matches standard iterators nested
167 /// inside records with a standard container name.
168 DeclarationMatcher standardIterator() {
169   return decl(
170       namedDecl(hasStdIteratorName()),
171       hasDeclContext(recordDecl(hasStdContainerName(), isInStdNamespace())));
172 }
173 
174 /// Returns a TypeMatcher that matches typedefs for standard iterators
175 /// inside records with a standard container name.
176 TypeMatcher typedefIterator() {
177   return typedefType(hasDeclaration(standardIterator()));
178 }
179 
180 /// Returns a TypeMatcher that matches records named for standard
181 /// iterators nested inside records named for standard containers.
182 TypeMatcher nestedIterator() {
183   return recordType(hasDeclaration(standardIterator()));
184 }
185 
186 /// Returns a TypeMatcher that matches types declared with using
187 /// declarations and which name standard iterators for standard containers.
188 TypeMatcher iteratorFromUsingDeclaration() {
189   auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName()));
190   // Types resulting from using declarations are represented by elaboratedType.
191   return elaboratedType(
192       // Unwrap the nested name specifier to test for one of the standard
193       // containers.
194       hasQualifier(specifiesType(templateSpecializationType(hasDeclaration(
195           namedDecl(hasStdContainerName(), isInStdNamespace()))))),
196       // the named type is what comes after the final '::' in the type. It
197       // should name one of the standard iterator names.
198       namesType(
199           anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))));
200 }
201 
202 /// This matcher returns declaration statements that contain variable
203 /// declarations with written non-list initializer for standard iterators.
204 StatementMatcher makeIteratorDeclMatcher() {
205   return declStmt(unless(has(
206                       varDecl(anyOf(unless(hasWrittenNonListInitializer()),
207                                     unless(hasType(isSugarFor(anyOf(
208                                         typedefIterator(), nestedIterator(),
209                                         iteratorFromUsingDeclaration())))))))))
210       .bind(IteratorDeclStmtId);
211 }
212 
213 StatementMatcher makeDeclWithNewMatcher() {
214   return declStmt(
215              unless(has(varDecl(anyOf(
216                  unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))),
217                  // FIXME: TypeLoc information is not reliable where CV
218                  // qualifiers are concerned so these types can't be
219                  // handled for now.
220                  hasType(pointerType(
221                      pointee(hasCanonicalType(hasLocalQualifiers())))),
222 
223                  // FIXME: Handle function pointers. For now we ignore them
224                  // because the replacement replaces the entire type
225                  // specifier source range which includes the identifier.
226                  hasType(pointsTo(
227                      pointsTo(parenType(innerType(functionType()))))))))))
228       .bind(DeclWithNewId);
229 }
230 
231 StatementMatcher makeDeclWithCastMatcher() {
232   return declStmt(
233              unless(has(varDecl(unless(hasInitializer(explicitCastExpr()))))))
234       .bind(DeclWithCastId);
235 }
236 
237 StatementMatcher makeDeclWithTemplateCastMatcher() {
238   auto ST =
239       substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg")));
240 
241   auto ExplicitCall =
242       anyOf(has(memberExpr(hasExplicitTemplateArgs())),
243             has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs()))));
244 
245   auto TemplateArg =
246       hasTemplateArgument(0, refersToType(qualType().bind("arg")));
247 
248   auto TemplateCall = callExpr(
249       ExplicitCall,
250       callee(functionDecl(TemplateArg,
251                           returns(anyOf(ST, pointsTo(ST), references(ST))))));
252 
253   return declStmt(unless(has(varDecl(
254                       unless(hasInitializer(ignoringImplicit(TemplateCall)))))))
255       .bind(DeclWithTemplateCastId);
256 }
257 
258 StatementMatcher makeCombinedMatcher() {
259   return declStmt(
260       // At least one varDecl should be a child of the declStmt to ensure
261       // it's a declaration list and avoid matching other declarations,
262       // e.g. using directives.
263       has(varDecl(unless(isImplicit()))),
264       // Skip declarations that are already using auto.
265       unless(has(varDecl(anyOf(hasType(autoType()),
266                                hasType(qualType(hasDescendant(autoType()))))))),
267       anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(),
268             makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher()));
269 }
270 
271 } // namespace
272 
273 UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context)
274     : ClangTidyCheck(Name, Context),
275       MinTypeNameLength(Options.get("MinTypeNameLength", 5)),
276       RemoveStars(Options.get("RemoveStars", false)) {}
277 
278 void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
279   Options.store(Opts, "MinTypeNameLength", MinTypeNameLength);
280   Options.store(Opts, "RemoveStars", RemoveStars);
281 }
282 
283 void UseAutoCheck::registerMatchers(MatchFinder *Finder) {
284   Finder->addMatcher(traverse(TK_AsIs, makeCombinedMatcher()), this);
285 }
286 
287 void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) {
288   for (const auto *Dec : D->decls()) {
289     const auto *V = cast<VarDecl>(Dec);
290     const Expr *ExprInit = V->getInit();
291 
292     // Skip expressions with cleanups from the initializer expression.
293     if (const auto *E = dyn_cast<ExprWithCleanups>(ExprInit))
294       ExprInit = E->getSubExpr();
295 
296     const auto *Construct = dyn_cast<CXXConstructExpr>(ExprInit);
297     if (!Construct)
298       continue;
299 
300     // Ensure that the constructor receives a single argument.
301     if (Construct->getNumArgs() != 1)
302       return;
303 
304     // Drill down to the as-written initializer.
305     const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts();
306     if (E != E->IgnoreConversionOperatorSingleStep()) {
307       // We hit a conversion operator. Early-out now as they imply an implicit
308       // conversion from a different type. Could also mean an explicit
309       // conversion from the same type but that's pretty rare.
310       return;
311     }
312 
313     if (const auto *NestedConstruct = dyn_cast<CXXConstructExpr>(E)) {
314       // If we ran into an implicit conversion constructor, can't convert.
315       //
316       // FIXME: The following only checks if the constructor can be used
317       // implicitly, not if it actually was. Cases where the converting
318       // constructor was used explicitly won't get converted.
319       if (NestedConstruct->getConstructor()->isConvertingConstructor(false))
320         return;
321     }
322     if (!Context->hasSameType(V->getType(), E->getType()))
323       return;
324   }
325 
326   // Get the type location using the first declaration.
327   const auto *V = cast<VarDecl>(*D->decl_begin());
328 
329   // WARNING: TypeLoc::getSourceRange() will include the identifier for things
330   // like function pointers. Not a concern since this action only works with
331   // iterators but something to keep in mind in the future.
332 
333   SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange());
334   diag(Range.getBegin(), "use auto when declaring iterators")
335       << FixItHint::CreateReplacement(Range, "auto");
336 }
337 
338 static void ignoreTypeLocClasses(
339     TypeLoc &Loc,
340     std::initializer_list<TypeLoc::TypeLocClass> const &LocClasses) {
341   while (llvm::is_contained(LocClasses, Loc.getTypeLocClass()))
342     Loc = Loc.getNextTypeLoc();
343 }
344 
345 static bool isMultiLevelPointerToTypeLocClasses(
346     TypeLoc Loc,
347     std::initializer_list<TypeLoc::TypeLocClass> const &LocClasses) {
348   ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified});
349   TypeLoc::TypeLocClass TLC = Loc.getTypeLocClass();
350   if (TLC != TypeLoc::Pointer && TLC != TypeLoc::MemberPointer)
351     return false;
352   ignoreTypeLocClasses(Loc, {TypeLoc::Paren, TypeLoc::Qualified,
353                              TypeLoc::Pointer, TypeLoc::MemberPointer});
354   return llvm::is_contained(LocClasses, Loc.getTypeLocClass());
355 }
356 
357 void UseAutoCheck::replaceExpr(
358     const DeclStmt *D, ASTContext *Context,
359     llvm::function_ref<QualType(const Expr *)> GetType, StringRef Message) {
360   const auto *FirstDecl = dyn_cast<VarDecl>(*D->decl_begin());
361   // Ensure that there is at least one VarDecl within the DeclStmt.
362   if (!FirstDecl)
363     return;
364 
365   const QualType FirstDeclType = FirstDecl->getType().getCanonicalType();
366   TypeSourceInfo *TSI = FirstDecl->getTypeSourceInfo();
367 
368   if (TSI == nullptr)
369     return;
370 
371   std::vector<FixItHint> StarRemovals;
372   for (const auto *Dec : D->decls()) {
373     const auto *V = cast<VarDecl>(Dec);
374     // Ensure that every DeclStmt child is a VarDecl.
375     if (!V)
376       return;
377 
378     const auto *Expr = V->getInit()->IgnoreParenImpCasts();
379     // Ensure that every VarDecl has an initializer.
380     if (!Expr)
381       return;
382 
383     // If VarDecl and Initializer have mismatching unqualified types.
384     if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr)))
385       return;
386 
387     // All subsequent variables in this declaration should have the same
388     // canonical type.  For example, we don't want to use `auto` in
389     // `T *p = new T, **pp = new T*;`.
390     if (FirstDeclType != V->getType().getCanonicalType())
391       return;
392 
393     if (RemoveStars) {
394       // Remove explicitly written '*' from declarations where there's more than
395       // one declaration in the declaration list.
396       if (Dec == *D->decl_begin())
397         continue;
398 
399       auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs<PointerTypeLoc>();
400       while (!Q.isNull()) {
401         StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc()));
402         Q = Q.getNextTypeLoc().getAs<PointerTypeLoc>();
403       }
404     }
405   }
406 
407   // FIXME: There is, however, one case we can address: when the VarDecl pointee
408   // is the same as the initializer, just more CV-qualified. However, TypeLoc
409   // information is not reliable where CV qualifiers are concerned so we can't
410   // do anything about this case for now.
411   TypeLoc Loc = TSI->getTypeLoc();
412   if (!RemoveStars)
413     ignoreTypeLocClasses(Loc, {TypeLoc::Pointer, TypeLoc::Qualified});
414   ignoreTypeLocClasses(Loc, {TypeLoc::LValueReference, TypeLoc::RValueReference,
415                              TypeLoc::Qualified});
416   SourceRange Range(Loc.getSourceRange());
417 
418   if (MinTypeNameLength != 0 &&
419       getTypeNameLength(RemoveStars,
420                         tooling::fixit::getText(Loc.getSourceRange(),
421                                                 FirstDecl->getASTContext())) <
422           MinTypeNameLength)
423     return;
424 
425   auto Diag = diag(Range.getBegin(), Message);
426 
427   bool ShouldReplenishVariableName = isMultiLevelPointerToTypeLocClasses(
428       TSI->getTypeLoc(), {TypeLoc::FunctionProto, TypeLoc::ConstantArray});
429 
430   // Space after 'auto' to handle cases where the '*' in the pointer type is
431   // next to the identifier. This avoids changing 'int *p' into 'autop'.
432   llvm::StringRef Auto = ShouldReplenishVariableName
433                              ? (RemoveStars ? "auto " : "auto *")
434                              : (RemoveStars ? "auto " : "auto");
435   std::string ReplenishedVariableName =
436       ShouldReplenishVariableName ? FirstDecl->getQualifiedNameAsString() : "";
437   std::string Replacement =
438       (Auto + llvm::StringRef{ReplenishedVariableName}).str();
439   Diag << FixItHint::CreateReplacement(Range, Replacement) << StarRemovals;
440 }
441 
442 void UseAutoCheck::check(const MatchFinder::MatchResult &Result) {
443   if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(IteratorDeclStmtId)) {
444     replaceIterators(Decl, Result.Context);
445   } else if (const auto *Decl =
446                  Result.Nodes.getNodeAs<DeclStmt>(DeclWithNewId)) {
447     replaceExpr(Decl, Result.Context,
448                 [](const Expr *Expr) { return Expr->getType(); },
449                 "use auto when initializing with new to avoid "
450                 "duplicating the type name");
451   } else if (const auto *Decl =
452                  Result.Nodes.getNodeAs<DeclStmt>(DeclWithCastId)) {
453     replaceExpr(
454         Decl, Result.Context,
455         [](const Expr *Expr) {
456           return cast<ExplicitCastExpr>(Expr)->getTypeAsWritten();
457         },
458         "use auto when initializing with a cast to avoid duplicating the type "
459         "name");
460   } else if (const auto *Decl =
461                  Result.Nodes.getNodeAs<DeclStmt>(DeclWithTemplateCastId)) {
462     replaceExpr(
463         Decl, Result.Context,
464         [](const Expr *Expr) {
465           return cast<CallExpr>(Expr->IgnoreImplicit())
466               ->getDirectCallee()
467               ->getReturnType();
468         },
469         "use auto when initializing with a template cast to avoid duplicating "
470         "the type name");
471   } else {
472     llvm_unreachable("Bad Callback. No node provided.");
473   }
474 }
475 
476 } // namespace clang::tidy::modernize
477