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
18using namespace clang;
19using namespace clang::ast_matchers;
20using namespace clang::ast_matchers::internal;
21
22namespace clang::tidy::modernize {
23namespace {
24
25const char IteratorDeclStmtId[] = "iterator_decl";
26const char DeclWithNewId[] = "decl_new";
27const char DeclWithCastId[] = "decl_cast";
28const char DeclWithTemplateCastId[] = "decl_template";
29
30size_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: C)
42 ? Alpha
43 : (isWhitespace(c: 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.
71AST_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>(Val: Init)) {
81 return !Construct->isListInitialization() && Construct->getNumArgs() > 0 &&
82 !Construct->getArg(Arg: 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.
99AST_MATCHER_P(QualType, isSugarFor, Matcher<QualType>, SugarMatcher) {
100 QualType QT = Node;
101 while (true) {
102 if (SugarMatcher.matches(Node: QT, Finder, Builder))
103 return true;
104
105 QualType NewQT = QT.getSingleStepDesugaredType(Context: 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.
122Matcher<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.
141Matcher<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.
160AST_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.
168DeclarationMatcher standardIterator() {
169 return decl(
170 namedDecl(hasStdIteratorName()),
171 hasDeclContext(InnerMatcher: recordDecl(hasStdContainerName(), isInStdNamespace())));
172}
173
174/// Returns a TypeMatcher that matches typedefs for standard iterators
175/// inside records with a standard container name.
176TypeMatcher typedefIterator() {
177 return typedefType(hasDeclaration(InnerMatcher: standardIterator()));
178}
179
180/// Returns a TypeMatcher that matches records named for standard
181/// iterators nested inside records named for standard containers.
182TypeMatcher nestedIterator() {
183 return recordType(hasDeclaration(InnerMatcher: standardIterator()));
184}
185
186/// Returns a TypeMatcher that matches types declared with using
187/// declarations and which name standard iterators for standard containers.
188TypeMatcher iteratorFromUsingDeclaration() {
189 auto HasIteratorDecl = hasDeclaration(InnerMatcher: 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(InnerMatcher: specifiesType(InnerMatcher: templateSpecializationType(hasDeclaration(
195 InnerMatcher: 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 InnerMatcher: 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.
204StatementMatcher makeIteratorDeclMatcher() {
205 return declStmt(unless(has(
206 varDecl(anyOf(unless(hasWrittenNonListInitializer()),
207 unless(hasType(InnerMatcher: isSugarFor(SugarMatcher: anyOf(
208 typedefIterator(), nestedIterator(),
209 iteratorFromUsingDeclaration())))))))))
210 .bind(ID: IteratorDeclStmtId);
211}
212
213StatementMatcher makeDeclWithNewMatcher() {
214 return declStmt(
215 unless(has(varDecl(anyOf(
216 unless(hasInitializer(InnerMatcher: ignoringParenImpCasts(InnerMatcher: 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(InnerMatcher: pointerType(
221 pointee(hasCanonicalType(InnerMatcher: 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(InnerMatcher: pointsTo(
227 InnerMatcher: pointsTo(InnerMatcher: parenType(innerType(functionType()))))))))))
228 .bind(ID: DeclWithNewId);
229}
230
231StatementMatcher makeDeclWithCastMatcher() {
232 return declStmt(
233 unless(has(varDecl(unless(hasInitializer(InnerMatcher: explicitCastExpr()))))))
234 .bind(ID: DeclWithCastId);
235}
236
237StatementMatcher makeDeclWithTemplateCastMatcher() {
238 auto ST =
239 substTemplateTypeParmType(hasReplacementType(equalsBoundNode(ID: "arg")));
240
241 auto ExplicitCall =
242 anyOf(has(memberExpr(hasExplicitTemplateArgs())),
243 has(ignoringImpCasts(InnerMatcher: declRefExpr(hasExplicitTemplateArgs()))));
244
245 auto TemplateArg =
246 hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: qualType().bind(ID: "arg")));
247
248 auto TemplateCall = callExpr(
249 ExplicitCall,
250 callee(InnerMatcher: functionDecl(TemplateArg,
251 returns(InnerMatcher: anyOf(ST, pointsTo(InnerMatcher: ST), references(InnerMatcher: ST))))));
252
253 return declStmt(unless(has(varDecl(
254 unless(hasInitializer(InnerMatcher: ignoringImplicit(InnerMatcher: TemplateCall)))))))
255 .bind(ID: DeclWithTemplateCastId);
256}
257
258StatementMatcher 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(InnerMatcher: autoType()),
266 hasType(InnerMatcher: qualType(hasDescendant(autoType()))))))),
267 anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(),
268 makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher()));
269}
270
271} // namespace
272
273UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context)
274 : ClangTidyCheck(Name, Context),
275 MinTypeNameLength(Options.get(LocalName: "MinTypeNameLength", Default: 5)),
276 RemoveStars(Options.get(LocalName: "RemoveStars", Default: false)) {}
277
278void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
279 Options.store(Options&: Opts, LocalName: "MinTypeNameLength", Value: MinTypeNameLength);
280 Options.store(Options&: Opts, LocalName: "RemoveStars", Value: RemoveStars);
281}
282
283void UseAutoCheck::registerMatchers(MatchFinder *Finder) {
284 Finder->addMatcher(NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: makeCombinedMatcher()), Action: this);
285}
286
287void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) {
288 for (const auto *Dec : D->decls()) {
289 const auto *V = cast<VarDecl>(Val: 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>(Val: ExprInit))
294 ExprInit = E->getSubExpr();
295
296 const auto *Construct = dyn_cast<CXXConstructExpr>(Val: 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>(Val: *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(Loc: Range.getBegin(), Description: "use auto when declaring iterators")
335 << FixItHint::CreateReplacement(RemoveRange: Range, Code: "auto");
336}
337
338static void ignoreTypeLocClasses(
339 TypeLoc &Loc,
340 std::initializer_list<TypeLoc::TypeLocClass> const &LocClasses) {
341 while (llvm::is_contained(Set: LocClasses, Element: Loc.getTypeLocClass()))
342 Loc = Loc.getNextTypeLoc();
343}
344
345static bool isMutliLevelPointerToTypeLocClasses(
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(Set: LocClasses, Element: Loc.getTypeLocClass());
355}
356
357void 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>(Val: *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>(Val: 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(T1: V->getType(), T2: 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(Loc: Range.getBegin(), Description: Message);
426
427 bool ShouldReplenishVariableName = isMutliLevelPointerToTypeLocClasses(
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(RemoveRange: Range, Code: Replacement) << StarRemovals;
440}
441
442void UseAutoCheck::check(const MatchFinder::MatchResult &Result) {
443 if (const auto *Decl = Result.Nodes.getNodeAs<DeclStmt>(ID: IteratorDeclStmtId)) {
444 replaceIterators(D: Decl, Context: Result.Context);
445 } else if (const auto *Decl =
446 Result.Nodes.getNodeAs<DeclStmt>(ID: DeclWithNewId)) {
447 replaceExpr(D: Decl, Context: Result.Context,
448 GetType: [](const Expr *Expr) { return Expr->getType(); },
449 Message: "use auto when initializing with new to avoid "
450 "duplicating the type name");
451 } else if (const auto *Decl =
452 Result.Nodes.getNodeAs<DeclStmt>(ID: DeclWithCastId)) {
453 replaceExpr(
454 D: Decl, Context: Result.Context,
455 GetType: [](const Expr *Expr) {
456 return cast<ExplicitCastExpr>(Val: Expr)->getTypeAsWritten();
457 },
458 Message: "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>(ID: DeclWithTemplateCastId)) {
462 replaceExpr(
463 D: Decl, Context: Result.Context,
464 GetType: [](const Expr *Expr) {
465 return cast<CallExpr>(Val: Expr->IgnoreImplicit())
466 ->getDirectCallee()
467 ->getReturnType();
468 },
469 Message: "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

source code of clang-tools-extra/clang-tidy/modernize/UseAutoCheck.cpp