1 //===--- UseAutoCheck.cpp - clang-tidy-------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
18 using namespace clang
;
19 using namespace clang::ast_matchers
;
20 using namespace clang::ast_matchers::internal
;
22 namespace clang::tidy::modernize
{
25 const char IteratorDeclStmtId
[] = "iterator_decl";
26 const char DeclWithNewId
[] = "decl_new";
27 const char DeclWithCastId
[] = "decl_cast";
28 const char DeclWithTemplateCastId
[] = "decl_template";
30 size_t getTypeNameLength(bool RemoveStars
, StringRef Text
) {
31 enum CharType
{ Space
, Alpha
, Punctuation
};
32 CharType LastChar
= Space
, BeforeSpace
= Punctuation
;
34 int TemplateTypenameCntr
= 0;
35 for (const unsigned char C
: Text
) {
37 ++TemplateTypenameCntr
;
39 --TemplateTypenameCntr
;
40 const CharType NextChar
=
44 (!RemoveStars
&& TemplateTypenameCntr
== 0 && C
== '*'))
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
;
58 /// Matches variable declarations that have explicit initializers that
59 /// are not initializer lists.
63 /// iterator I = Container.begin();
69 /// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B
71 AST_MATCHER(VarDecl
, hasWrittenNonListInitializer
) {
72 const Expr
*Init
= Node
.getAnyInitializer();
76 Init
= Init
->IgnoreImplicit();
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();
84 return Node
.getInitStyle() != VarDecl::ListInit
;
87 /// Matches QualTypes that are type sugar for QualTypes that match \c
93 /// typedef C my_type;
94 /// typedef my_type my_other_type;
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
) {
102 if (SugarMatcher
.matches(QT
, Finder
, Builder
))
105 QualType NewQT
= QT
.getSingleStepDesugaredType(Finder
->getASTContext());
112 /// Matches named declarations that have one of the standard iterator
113 /// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator.
118 /// const_iterator CI;
121 /// namedDecl(hasStdIteratorName()) matches \c I and \c CI.
122 Matcher
<NamedDecl
> hasStdIteratorName() {
123 static const StringRef IteratorNames
[] = {"iterator", "reverse_iterator",
125 "const_reverse_iterator"};
126 return hasAnyName(IteratorNames
);
129 /// Matches named declarations that have one of the standard container
135 /// class forward_list {};
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",
149 "unordered_map", "unordered_multimap",
150 "unordered_set", "unordered_multiset",
152 "queue", "priority_queue",
155 return hasAnyName(ContainerNames
);
158 /// Matches declaration reference or member expressions with explicit template
160 AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs
,
161 AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr
,
163 return Node
.hasExplicitTemplateArgs();
166 /// Returns a DeclarationMatcher that matches standard iterators nested
167 /// inside records with a standard container name.
168 DeclarationMatcher
standardIterator() {
170 namedDecl(hasStdIteratorName()),
171 hasDeclContext(recordDecl(hasStdContainerName(), isInStdNamespace())));
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()));
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()));
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
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.
199 anyOf(typedefType(HasIteratorDecl
), recordType(HasIteratorDecl
))));
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
);
213 StatementMatcher
makeDeclWithNewMatcher() {
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
221 pointee(hasCanonicalType(hasLocalQualifiers())))),
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.
227 pointsTo(parenType(innerType(functionType()))))))))))
228 .bind(DeclWithNewId
);
231 StatementMatcher
makeDeclWithCastMatcher() {
233 unless(has(varDecl(unless(hasInitializer(explicitCastExpr()))))))
234 .bind(DeclWithCastId
);
237 StatementMatcher
makeDeclWithTemplateCastMatcher() {
239 substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg")));
242 anyOf(has(memberExpr(hasExplicitTemplateArgs())),
243 has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs()))));
246 hasTemplateArgument(0, refersToType(qualType().bind("arg")));
248 auto TemplateCall
= callExpr(
250 callee(functionDecl(TemplateArg
,
251 returns(anyOf(ST
, pointsTo(ST
), references(ST
))))));
253 return declStmt(unless(has(varDecl(
254 unless(hasInitializer(ignoringImplicit(TemplateCall
)))))))
255 .bind(DeclWithTemplateCastId
);
258 StatementMatcher
makeCombinedMatcher() {
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()));
273 UseAutoCheck::UseAutoCheck(StringRef Name
, ClangTidyContext
*Context
)
274 : ClangTidyCheck(Name
, Context
),
275 MinTypeNameLength(Options
.get("MinTypeNameLength", 5)),
276 RemoveStars(Options
.get("RemoveStars", false)) {}
278 void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
279 Options
.store(Opts
, "MinTypeNameLength", MinTypeNameLength
);
280 Options
.store(Opts
, "RemoveStars", RemoveStars
);
283 void UseAutoCheck::registerMatchers(MatchFinder
*Finder
) {
284 Finder
->addMatcher(traverse(TK_AsIs
, makeCombinedMatcher()), this);
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();
292 // Skip expressions with cleanups from the initializer expression.
293 if (const auto *E
= dyn_cast
<ExprWithCleanups
>(ExprInit
))
294 ExprInit
= E
->getSubExpr();
296 const auto *Construct
= dyn_cast
<CXXConstructExpr
>(ExprInit
);
300 // Ensure that the constructor receives a single argument.
301 if (Construct
->getNumArgs() != 1)
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.
313 if (const auto *NestedConstruct
= dyn_cast
<CXXConstructExpr
>(E
)) {
314 // If we ran into an implicit conversion constructor, can't convert.
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))
322 if (!Context
->hasSameType(V
->getType(), E
->getType()))
326 // Get the type location using the first declaration.
327 const auto *V
= cast
<VarDecl
>(*D
->decl_begin());
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.
333 SourceRange
Range(V
->getTypeSourceInfo()->getTypeLoc().getSourceRange());
334 diag(Range
.getBegin(), "use auto when declaring iterators")
335 << FixItHint::CreateReplacement(Range
, "auto");
338 static void ignoreTypeLocClasses(
340 std::initializer_list
<TypeLoc::TypeLocClass
> const &LocClasses
) {
341 while (llvm::is_contained(LocClasses
, Loc
.getTypeLocClass()))
342 Loc
= Loc
.getNextTypeLoc();
345 static bool isMutliLevelPointerToTypeLocClasses(
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
)
352 ignoreTypeLocClasses(Loc
, {TypeLoc::Paren
, TypeLoc::Qualified
,
353 TypeLoc::Pointer
, TypeLoc::MemberPointer
});
354 return llvm::is_contained(LocClasses
, Loc
.getTypeLocClass());
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.
365 const QualType FirstDeclType
= FirstDecl
->getType().getCanonicalType();
366 TypeSourceInfo
*TSI
= FirstDecl
->getTypeSourceInfo();
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.
378 const auto *Expr
= V
->getInit()->IgnoreParenImpCasts();
379 // Ensure that every VarDecl has an initializer.
383 // If VarDecl and Initializer have mismatching unqualified types.
384 if (!Context
->hasSameUnqualifiedType(V
->getType(), GetType(Expr
)))
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())
394 // Remove explicitly written '*' from declarations where there's more than
395 // one declaration in the declaration list.
396 if (Dec
== *D
->decl_begin())
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
>();
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();
413 ignoreTypeLocClasses(Loc
, {TypeLoc::Pointer
, TypeLoc::Qualified
});
414 ignoreTypeLocClasses(Loc
, {TypeLoc::LValueReference
, TypeLoc::RValueReference
,
415 TypeLoc::Qualified
});
416 SourceRange
Range(Loc
.getSourceRange());
418 if (MinTypeNameLength
!= 0 &&
419 getTypeNameLength(RemoveStars
,
420 tooling::fixit::getText(Loc
.getSourceRange(),
421 FirstDecl
->getASTContext())) <
425 auto Diag
= diag(Range
.getBegin(), Message
);
427 bool ShouldReplenishVariableName
= isMutliLevelPointerToTypeLocClasses(
428 TSI
->getTypeLoc(), {TypeLoc::FunctionProto
, TypeLoc::ConstantArray
});
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
;
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
)) {
454 Decl
, Result
.Context
,
455 [](const Expr
*Expr
) {
456 return cast
<ExplicitCastExpr
>(Expr
)->getTypeAsWritten();
458 "use auto when initializing with a cast to avoid duplicating the type "
460 } else if (const auto *Decl
=
461 Result
.Nodes
.getNodeAs
<DeclStmt
>(DeclWithTemplateCastId
)) {
463 Decl
, Result
.Context
,
464 [](const Expr
*Expr
) {
465 return cast
<CallExpr
>(Expr
->IgnoreImplicit())
469 "use auto when initializing with a template cast to avoid duplicating "
472 llvm_unreachable("Bad Callback. No node provided.");
476 } // namespace clang::tidy::modernize