1 //===--- ExplicitConstructorCheck.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 "ExplicitConstructorCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::google
{
19 void ExplicitConstructorCheck::registerMatchers(MatchFinder
*Finder
) {
21 cxxConstructorDecl(unless(anyOf(isImplicit(), // Compiler-generated.
22 isDeleted(), isInstantiated())))
26 cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit.
27 isImplicit(), // Compiler-generated.
28 isDeleted(), isInstantiated())))
34 // Looks for the token matching the predicate and returns the range of the found
35 // token including trailing whitespace.
36 static SourceRange
findToken(const SourceManager
&Sources
,
37 const LangOptions
&LangOpts
,
38 SourceLocation StartLoc
, SourceLocation EndLoc
,
39 bool (*Pred
)(const Token
&)) {
40 if (StartLoc
.isMacroID() || EndLoc
.isMacroID())
42 FileID File
= Sources
.getFileID(Sources
.getSpellingLoc(StartLoc
));
43 StringRef Buf
= Sources
.getBufferData(File
);
44 const char *StartChar
= Sources
.getCharacterData(StartLoc
);
45 Lexer
Lex(StartLoc
, LangOpts
, StartChar
, StartChar
, Buf
.end());
46 Lex
.SetCommentRetentionState(true);
49 Lex
.LexFromRawLexer(Tok
);
52 Lex
.LexFromRawLexer(NextTok
);
53 return {Tok
.getLocation(), NextTok
.getLocation()};
55 } while (Tok
.isNot(tok::eof
) && Tok
.getLocation() < EndLoc
);
60 static bool declIsStdInitializerList(const NamedDecl
*D
) {
61 // First use the fast getName() method to avoid unnecessary calls to the
62 // slow getQualifiedNameAsString().
63 return D
->getName() == "initializer_list" &&
64 D
->getQualifiedNameAsString() == "std::initializer_list";
67 static bool isStdInitializerList(QualType Type
) {
68 Type
= Type
.getCanonicalType();
69 if (const auto *TS
= Type
->getAs
<TemplateSpecializationType
>()) {
70 if (const TemplateDecl
*TD
= TS
->getTemplateName().getAsTemplateDecl())
71 return declIsStdInitializerList(TD
);
73 if (const auto *RT
= Type
->getAs
<RecordType
>()) {
74 if (const auto *Specialization
=
75 dyn_cast
<ClassTemplateSpecializationDecl
>(RT
->getDecl()))
76 return declIsStdInitializerList(Specialization
->getSpecializedTemplate());
81 void ExplicitConstructorCheck::check(const MatchFinder::MatchResult
&Result
) {
82 constexpr char WarningMessage
[] =
83 "%0 must be marked explicit to avoid unintentional implicit conversions";
85 if (const auto *Conversion
=
86 Result
.Nodes
.getNodeAs
<CXXConversionDecl
>("conversion")) {
87 if (Conversion
->isOutOfLine())
89 SourceLocation Loc
= Conversion
->getLocation();
90 // Ignore all macros until we learn to ignore specific ones (e.g. used in
91 // gmock to define matchers).
94 diag(Loc
, WarningMessage
)
95 << Conversion
<< FixItHint::CreateInsertion(Loc
, "explicit ");
99 const auto *Ctor
= Result
.Nodes
.getNodeAs
<CXXConstructorDecl
>("ctor");
100 if (Ctor
->isOutOfLine() || Ctor
->getNumParams() == 0 ||
101 Ctor
->getMinRequiredArguments() > 1)
104 bool TakesInitializerList
= isStdInitializerList(
105 Ctor
->getParamDecl(0)->getType().getNonReferenceType());
106 if (Ctor
->isExplicit() &&
107 (Ctor
->isCopyOrMoveConstructor() || TakesInitializerList
)) {
108 auto IsKwExplicit
= [](const Token
&Tok
) {
109 return Tok
.is(tok::raw_identifier
) &&
110 Tok
.getRawIdentifier() == "explicit";
112 SourceRange ExplicitTokenRange
=
113 findToken(*Result
.SourceManager
, getLangOpts(),
114 Ctor
->getOuterLocStart(), Ctor
->getEndLoc(), IsKwExplicit
);
115 StringRef ConstructorDescription
;
116 if (Ctor
->isMoveConstructor())
117 ConstructorDescription
= "move";
118 else if (Ctor
->isCopyConstructor())
119 ConstructorDescription
= "copy";
121 ConstructorDescription
= "initializer-list";
123 auto Diag
= diag(Ctor
->getLocation(),
124 "%0 constructor should not be declared explicit")
125 << ConstructorDescription
;
126 if (ExplicitTokenRange
.isValid()) {
127 Diag
<< FixItHint::CreateRemoval(
128 CharSourceRange::getCharRange(ExplicitTokenRange
));
133 if (Ctor
->isExplicit() || Ctor
->isCopyOrMoveConstructor() ||
134 TakesInitializerList
)
137 bool SingleArgument
=
138 Ctor
->getNumParams() == 1 && !Ctor
->getParamDecl(0)->isParameterPack();
139 SourceLocation Loc
= Ctor
->getLocation();
140 diag(Loc
, WarningMessage
)
142 ? "single-argument constructors"
143 : "constructors that are callable with a single argument")
144 << FixItHint::CreateInsertion(Loc
, "explicit ");
147 } // namespace clang::tidy::google