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