1 //===--- QualifiedAutoCheck.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 "QualifiedAutoCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/ASTMatchers/ASTMatchers.h"
12 #include "llvm/ADT/Optional.h"
13 #include "llvm/ADT/SmallVector.h"
15 using namespace clang::ast_matchers
;
19 namespace readability
{
23 // FIXME move to ASTMatchers
24 AST_MATCHER_P(QualType
, hasUnqualifiedType
,
25 ast_matchers::internal::Matcher
<QualType
>, InnerMatcher
) {
26 return InnerMatcher
.matches(Node
.getUnqualifiedType(), Finder
, Builder
);
29 enum class Qualifier
{ Const
, Volatile
, Restrict
};
31 llvm::Optional
<Token
> findQualToken(const VarDecl
*Decl
, Qualifier Qual
,
32 const MatchFinder::MatchResult
&Result
) {
33 // Since either of the locs can be in a macro, use `makeFileCharRange` to be
34 // sure that we have a consistent `CharSourceRange`, located entirely in the
37 assert((Qual
== Qualifier::Const
|| Qual
== Qualifier::Volatile
||
38 Qual
== Qualifier::Restrict
) &&
41 SourceLocation BeginLoc
= Decl
->getQualifierLoc().getBeginLoc();
42 if (BeginLoc
.isInvalid())
43 BeginLoc
= Decl
->getBeginLoc();
44 SourceLocation EndLoc
= Decl
->getLocation();
46 CharSourceRange FileRange
= Lexer::makeFileCharRange(
47 CharSourceRange::getCharRange(BeginLoc
, EndLoc
), *Result
.SourceManager
,
48 Result
.Context
->getLangOpts());
50 if (FileRange
.isInvalid())
54 Qual
== Qualifier::Const
56 : Qual
== Qualifier::Volatile
? tok::kw_volatile
: tok::kw_restrict
;
58 return utils::lexer::getQualifyingToken(Tok
, FileRange
, *Result
.Context
,
59 *Result
.SourceManager
);
62 llvm::Optional
<SourceRange
>
63 getTypeSpecifierLocation(const VarDecl
*Var
,
64 const MatchFinder::MatchResult
&Result
) {
65 SourceRange
TypeSpecifier(
66 Var
->getTypeSpecStartLoc(),
67 Var
->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
68 Var
->getTypeSpecEndLoc(), *Result
.SourceManager
,
69 Result
.Context
->getLangOpts())));
71 if (TypeSpecifier
.getBegin().isMacroID() ||
72 TypeSpecifier
.getEnd().isMacroID())
77 llvm::Optional
<SourceRange
> mergeReplacementRange(SourceRange
&TypeSpecifier
,
78 const Token
&ConstToken
) {
79 if (TypeSpecifier
.getBegin().getLocWithOffset(-1) == ConstToken
.getEndLoc()) {
80 TypeSpecifier
.setBegin(ConstToken
.getLocation());
83 if (TypeSpecifier
.getEnd().getLocWithOffset(1) == ConstToken
.getLocation()) {
84 TypeSpecifier
.setEnd(ConstToken
.getEndLoc());
87 return SourceRange(ConstToken
.getLocation(), ConstToken
.getEndLoc());
90 bool isPointerConst(QualType QType
) {
91 QualType Pointee
= QType
->getPointeeType();
92 assert(!Pointee
.isNull() && "can't have a null Pointee");
93 return Pointee
.isConstQualified();
96 bool isAutoPointerConst(QualType QType
) {
98 cast
<AutoType
>(QType
->getPointeeType().getTypePtr())->desugar();
99 assert(!Pointee
.isNull() && "can't have a null Pointee");
100 return Pointee
.isConstQualified();
105 void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
106 Options
.store(Opts
, "AddConstToQualified", AddConstToQualified
);
109 void QualifiedAutoCheck::registerMatchers(MatchFinder
*Finder
) {
110 auto ExplicitSingleVarDecl
=
111 [](const ast_matchers::internal::Matcher
<VarDecl
> &InnerMatcher
,
112 llvm::StringRef ID
) {
114 unless(isInTemplateInstantiation()),
116 varDecl(unless(isImplicit()), InnerMatcher
).bind(ID
)));
118 auto ExplicitSingleVarDeclInTemplate
=
119 [](const ast_matchers::internal::Matcher
<VarDecl
> &InnerMatcher
,
120 llvm::StringRef ID
) {
122 isInTemplateInstantiation(),
124 varDecl(unless(isImplicit()), InnerMatcher
).bind(ID
)));
127 auto IsBoundToType
= refersToType(equalsBoundNode("type"));
128 auto UnlessFunctionType
= unless(hasUnqualifiedDesugaredType(functionType()));
129 auto IsAutoDeducedToPointer
= [](const auto &...InnerMatchers
) {
130 return autoType(hasDeducedType(
131 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers
...)))));
135 ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType
)),
140 ExplicitSingleVarDeclInTemplate(
141 allOf(hasType(IsAutoDeducedToPointer(
142 hasUnqualifiedType(qualType().bind("type")),
143 UnlessFunctionType
)),
145 functionDecl(hasAnyTemplateArgument(IsBoundToType
))),
146 hasAncestor(classTemplateSpecializationDecl(
147 hasAnyTemplateArgument(IsBoundToType
))))),
150 if (!AddConstToQualified
)
152 Finder
->addMatcher(ExplicitSingleVarDecl(
153 hasType(pointerType(pointee(autoType()))), "auto_ptr"),
156 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
161 void QualifiedAutoCheck::check(const MatchFinder::MatchResult
&Result
) {
162 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto")) {
163 SourceRange TypeSpecifier
;
164 if (llvm::Optional
<SourceRange
> TypeSpec
=
165 getTypeSpecifierLocation(Var
, Result
)) {
166 TypeSpecifier
= *TypeSpec
;
170 llvm::SmallVector
<SourceRange
, 4> RemoveQualifiersRange
;
171 auto CheckQualifier
= [&](bool IsPresent
, Qualifier Qual
) {
173 llvm::Optional
<Token
> Token
= findQualToken(Var
, Qual
, Result
);
174 if (!Token
|| Token
->getLocation().isMacroID())
175 return true; // Disregard this VarDecl.
176 if (llvm::Optional
<SourceRange
> Result
=
177 mergeReplacementRange(TypeSpecifier
, *Token
))
178 RemoveQualifiersRange
.push_back(*Result
);
183 bool IsLocalConst
= Var
->getType().isLocalConstQualified();
184 bool IsLocalVolatile
= Var
->getType().isLocalVolatileQualified();
185 bool IsLocalRestrict
= Var
->getType().isLocalRestrictQualified();
187 if (CheckQualifier(IsLocalConst
, Qualifier::Const
) ||
188 CheckQualifier(IsLocalVolatile
, Qualifier::Volatile
) ||
189 CheckQualifier(IsLocalRestrict
, Qualifier::Restrict
))
192 // Check for bridging the gap between the asterisk and name.
193 if (Var
->getLocation() == TypeSpecifier
.getEnd().getLocWithOffset(1))
194 TypeSpecifier
.setEnd(TypeSpecifier
.getEnd().getLocWithOffset(1));
196 CharSourceRange FixItRange
= CharSourceRange::getCharRange(TypeSpecifier
);
197 if (FixItRange
.isInvalid())
200 SourceLocation FixitLoc
= FixItRange
.getBegin();
201 for (SourceRange
&Range
: RemoveQualifiersRange
) {
202 if (Range
.getBegin() < FixitLoc
)
203 FixitLoc
= Range
.getBegin();
206 std::string ReplStr
= [&] {
207 llvm::StringRef PtrConst
= isPointerConst(Var
->getType()) ? "const " : "";
208 llvm::StringRef LocalConst
= IsLocalConst
? "const " : "";
209 llvm::StringRef LocalVol
= IsLocalVolatile
? "volatile " : "";
210 llvm::StringRef LocalRestrict
= IsLocalRestrict
? "__restrict " : "";
211 return (PtrConst
+ "auto *" + LocalConst
+ LocalVol
+ LocalRestrict
)
215 DiagnosticBuilder Diag
=
217 "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
218 "%3' can be declared as '%4%3'")
219 << IsLocalConst
<< IsLocalVolatile
<< IsLocalRestrict
<< Var
->getName()
222 for (SourceRange
&Range
: RemoveQualifiersRange
) {
223 Diag
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range
));
226 Diag
<< FixItHint::CreateReplacement(FixItRange
, ReplStr
);
229 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto_ptr")) {
230 if (!isPointerConst(Var
->getType()))
231 return; // Pointer isn't const, no need to add const qualifier.
232 if (!isAutoPointerConst(Var
->getType()))
233 return; // Const isn't wrapped in the auto type, so must be declared
236 if (Var
->getType().isLocalConstQualified()) {
237 llvm::Optional
<Token
> Token
=
238 findQualToken(Var
, Qualifier::Const
, Result
);
239 if (!Token
|| Token
->getLocation().isMacroID())
242 if (Var
->getType().isLocalVolatileQualified()) {
243 llvm::Optional
<Token
> Token
=
244 findQualToken(Var
, Qualifier::Volatile
, Result
);
245 if (!Token
|| Token
->getLocation().isMacroID())
248 if (Var
->getType().isLocalRestrictQualified()) {
249 llvm::Optional
<Token
> Token
=
250 findQualToken(Var
, Qualifier::Restrict
, Result
);
251 if (!Token
|| Token
->getLocation().isMacroID())
255 if (llvm::Optional
<SourceRange
> TypeSpec
=
256 getTypeSpecifierLocation(Var
, Result
)) {
257 if (TypeSpec
->isInvalid() || TypeSpec
->getBegin().isMacroID() ||
258 TypeSpec
->getEnd().isMacroID())
260 SourceLocation InsertPos
= TypeSpec
->getBegin();
262 "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
263 "'const auto *%select{|const }0%select{|volatile }1%2'")
264 << Var
->getType().isLocalConstQualified()
265 << Var
->getType().isLocalVolatileQualified() << Var
->getName()
266 << FixItHint::CreateInsertion(InsertPos
, "const ");
270 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto_ref")) {
271 if (!isPointerConst(Var
->getType()))
272 return; // Pointer isn't const, no need to add const qualifier.
273 if (!isAutoPointerConst(Var
->getType()))
274 // Const isn't wrapped in the auto type, so must be declared explicitly.
277 if (llvm::Optional
<SourceRange
> TypeSpec
=
278 getTypeSpecifierLocation(Var
, Result
)) {
279 if (TypeSpec
->isInvalid() || TypeSpec
->getBegin().isMacroID() ||
280 TypeSpec
->getEnd().isMacroID())
282 SourceLocation InsertPos
= TypeSpec
->getBegin();
283 diag(InsertPos
, "'auto &%0' can be declared as 'const auto &%0'")
284 << Var
->getName() << FixItHint::CreateInsertion(InsertPos
, "const ");
290 } // namespace readability