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