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/SmallVector.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::readability
{
21 // FIXME move to ASTMatchers
22 AST_MATCHER_P(QualType
, hasUnqualifiedType
,
23 ast_matchers::internal::Matcher
<QualType
>, InnerMatcher
) {
24 return InnerMatcher
.matches(Node
.getUnqualifiedType(), Finder
, Builder
);
27 enum class Qualifier
{ Const
, Volatile
, Restrict
};
29 std::optional
<Token
> findQualToken(const VarDecl
*Decl
, Qualifier Qual
,
30 const MatchFinder::MatchResult
&Result
) {
31 // Since either of the locs can be in a macro, use `makeFileCharRange` to be
32 // sure that we have a consistent `CharSourceRange`, located entirely in the
35 assert((Qual
== Qualifier::Const
|| Qual
== Qualifier::Volatile
||
36 Qual
== Qualifier::Restrict
) &&
39 SourceLocation BeginLoc
= Decl
->getQualifierLoc().getBeginLoc();
40 if (BeginLoc
.isInvalid())
41 BeginLoc
= Decl
->getBeginLoc();
42 SourceLocation EndLoc
= Decl
->getLocation();
44 CharSourceRange FileRange
= Lexer::makeFileCharRange(
45 CharSourceRange::getCharRange(BeginLoc
, EndLoc
), *Result
.SourceManager
,
46 Result
.Context
->getLangOpts());
48 if (FileRange
.isInvalid())
52 Qual
== Qualifier::Const
54 : Qual
== Qualifier::Volatile
? tok::kw_volatile
: tok::kw_restrict
;
56 return utils::lexer::getQualifyingToken(Tok
, FileRange
, *Result
.Context
,
57 *Result
.SourceManager
);
60 std::optional
<SourceRange
>
61 getTypeSpecifierLocation(const VarDecl
*Var
,
62 const MatchFinder::MatchResult
&Result
) {
63 SourceRange
TypeSpecifier(
64 Var
->getTypeSpecStartLoc(),
65 Var
->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
66 Var
->getTypeSpecEndLoc(), *Result
.SourceManager
,
67 Result
.Context
->getLangOpts())));
69 if (TypeSpecifier
.getBegin().isMacroID() ||
70 TypeSpecifier
.getEnd().isMacroID())
75 std::optional
<SourceRange
> mergeReplacementRange(SourceRange
&TypeSpecifier
,
76 const Token
&ConstToken
) {
77 if (TypeSpecifier
.getBegin().getLocWithOffset(-1) == ConstToken
.getEndLoc()) {
78 TypeSpecifier
.setBegin(ConstToken
.getLocation());
81 if (TypeSpecifier
.getEnd().getLocWithOffset(1) == ConstToken
.getLocation()) {
82 TypeSpecifier
.setEnd(ConstToken
.getEndLoc());
85 return SourceRange(ConstToken
.getLocation(), ConstToken
.getEndLoc());
88 bool isPointerConst(QualType QType
) {
89 QualType Pointee
= QType
->getPointeeType();
90 assert(!Pointee
.isNull() && "can't have a null Pointee");
91 return Pointee
.isConstQualified();
94 bool isAutoPointerConst(QualType QType
) {
96 cast
<AutoType
>(QType
->getPointeeType().getTypePtr())->desugar();
97 assert(!Pointee
.isNull() && "can't have a null Pointee");
98 return Pointee
.isConstQualified();
103 void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
104 Options
.store(Opts
, "AddConstToQualified", AddConstToQualified
);
107 void QualifiedAutoCheck::registerMatchers(MatchFinder
*Finder
) {
108 auto ExplicitSingleVarDecl
=
109 [](const ast_matchers::internal::Matcher
<VarDecl
> &InnerMatcher
,
110 llvm::StringRef ID
) {
112 unless(isInTemplateInstantiation()),
114 varDecl(unless(isImplicit()), InnerMatcher
).bind(ID
)));
116 auto ExplicitSingleVarDeclInTemplate
=
117 [](const ast_matchers::internal::Matcher
<VarDecl
> &InnerMatcher
,
118 llvm::StringRef ID
) {
120 isInTemplateInstantiation(),
122 varDecl(unless(isImplicit()), InnerMatcher
).bind(ID
)));
125 auto IsBoundToType
= refersToType(equalsBoundNode("type"));
126 auto UnlessFunctionType
= unless(hasUnqualifiedDesugaredType(functionType()));
127 auto IsAutoDeducedToPointer
= [](const auto &...InnerMatchers
) {
128 return autoType(hasDeducedType(
129 hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers
...)))));
133 ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType
)),
138 ExplicitSingleVarDeclInTemplate(
139 allOf(hasType(IsAutoDeducedToPointer(
140 hasUnqualifiedType(qualType().bind("type")),
141 UnlessFunctionType
)),
143 functionDecl(hasAnyTemplateArgument(IsBoundToType
))),
144 hasAncestor(classTemplateSpecializationDecl(
145 hasAnyTemplateArgument(IsBoundToType
))))),
148 if (!AddConstToQualified
)
150 Finder
->addMatcher(ExplicitSingleVarDecl(
151 hasType(pointerType(pointee(autoType()))), "auto_ptr"),
154 ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
159 void QualifiedAutoCheck::check(const MatchFinder::MatchResult
&Result
) {
160 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto")) {
161 SourceRange TypeSpecifier
;
162 if (std::optional
<SourceRange
> TypeSpec
=
163 getTypeSpecifierLocation(Var
, Result
)) {
164 TypeSpecifier
= *TypeSpec
;
168 llvm::SmallVector
<SourceRange
, 4> RemoveQualifiersRange
;
169 auto CheckQualifier
= [&](bool IsPresent
, Qualifier Qual
) {
171 std::optional
<Token
> Token
= findQualToken(Var
, Qual
, Result
);
172 if (!Token
|| Token
->getLocation().isMacroID())
173 return true; // Disregard this VarDecl.
174 if (std::optional
<SourceRange
> Result
=
175 mergeReplacementRange(TypeSpecifier
, *Token
))
176 RemoveQualifiersRange
.push_back(*Result
);
181 bool IsLocalConst
= Var
->getType().isLocalConstQualified();
182 bool IsLocalVolatile
= Var
->getType().isLocalVolatileQualified();
183 bool IsLocalRestrict
= Var
->getType().isLocalRestrictQualified();
185 if (CheckQualifier(IsLocalConst
, Qualifier::Const
) ||
186 CheckQualifier(IsLocalVolatile
, Qualifier::Volatile
) ||
187 CheckQualifier(IsLocalRestrict
, Qualifier::Restrict
))
190 // Check for bridging the gap between the asterisk and name.
191 if (Var
->getLocation() == TypeSpecifier
.getEnd().getLocWithOffset(1))
192 TypeSpecifier
.setEnd(TypeSpecifier
.getEnd().getLocWithOffset(1));
194 CharSourceRange FixItRange
= CharSourceRange::getCharRange(TypeSpecifier
);
195 if (FixItRange
.isInvalid())
198 SourceLocation FixitLoc
= FixItRange
.getBegin();
199 for (SourceRange
&Range
: RemoveQualifiersRange
) {
200 if (Range
.getBegin() < FixitLoc
)
201 FixitLoc
= Range
.getBegin();
204 std::string ReplStr
= [&] {
205 llvm::StringRef PtrConst
= isPointerConst(Var
->getType()) ? "const " : "";
206 llvm::StringRef LocalConst
= IsLocalConst
? "const " : "";
207 llvm::StringRef LocalVol
= IsLocalVolatile
? "volatile " : "";
208 llvm::StringRef LocalRestrict
= IsLocalRestrict
? "__restrict " : "";
209 return (PtrConst
+ "auto *" + LocalConst
+ LocalVol
+ LocalRestrict
)
213 DiagnosticBuilder Diag
=
215 "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
216 "%3' can be declared as '%4%3'")
217 << IsLocalConst
<< IsLocalVolatile
<< IsLocalRestrict
<< Var
->getName()
220 for (SourceRange
&Range
: RemoveQualifiersRange
) {
221 Diag
<< FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range
));
224 Diag
<< FixItHint::CreateReplacement(FixItRange
, ReplStr
);
227 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto_ptr")) {
228 if (!isPointerConst(Var
->getType()))
229 return; // Pointer isn't const, no need to add const qualifier.
230 if (!isAutoPointerConst(Var
->getType()))
231 return; // Const isn't wrapped in the auto type, so must be declared
234 if (Var
->getType().isLocalConstQualified()) {
235 std::optional
<Token
> Token
= findQualToken(Var
, Qualifier::Const
, Result
);
236 if (!Token
|| Token
->getLocation().isMacroID())
239 if (Var
->getType().isLocalVolatileQualified()) {
240 std::optional
<Token
> Token
=
241 findQualToken(Var
, Qualifier::Volatile
, Result
);
242 if (!Token
|| Token
->getLocation().isMacroID())
245 if (Var
->getType().isLocalRestrictQualified()) {
246 std::optional
<Token
> Token
=
247 findQualToken(Var
, Qualifier::Restrict
, Result
);
248 if (!Token
|| Token
->getLocation().isMacroID())
252 if (std::optional
<SourceRange
> TypeSpec
=
253 getTypeSpecifierLocation(Var
, Result
)) {
254 if (TypeSpec
->isInvalid() || TypeSpec
->getBegin().isMacroID() ||
255 TypeSpec
->getEnd().isMacroID())
257 SourceLocation InsertPos
= TypeSpec
->getBegin();
259 "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
260 "'const auto *%select{|const }0%select{|volatile }1%2'")
261 << Var
->getType().isLocalConstQualified()
262 << Var
->getType().isLocalVolatileQualified() << Var
->getName()
263 << FixItHint::CreateInsertion(InsertPos
, "const ");
267 if (const auto *Var
= Result
.Nodes
.getNodeAs
<VarDecl
>("auto_ref")) {
268 if (!isPointerConst(Var
->getType()))
269 return; // Pointer isn't const, no need to add const qualifier.
270 if (!isAutoPointerConst(Var
->getType()))
271 // Const isn't wrapped in the auto type, so must be declared explicitly.
274 if (std::optional
<SourceRange
> TypeSpec
=
275 getTypeSpecifierLocation(Var
, Result
)) {
276 if (TypeSpec
->isInvalid() || TypeSpec
->getBegin().isMacroID() ||
277 TypeSpec
->getEnd().isMacroID())
279 SourceLocation InsertPos
= TypeSpec
->getBegin();
280 diag(InsertPos
, "'auto &%0' can be declared as 'const auto &%0'")
281 << Var
->getName() << FixItHint::CreateInsertion(InsertPos
, "const ");
287 } // namespace clang::tidy::readability