1 //===--- IsolateDeclarationCheck.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 "IsolateDeclarationCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 using namespace clang::ast_matchers
;
15 using namespace clang::tidy::utils::lexer
;
17 namespace clang::tidy::readability
{
20 AST_MATCHER(DeclStmt
, isSingleDecl
) { return Node
.isSingleDecl(); }
21 AST_MATCHER(DeclStmt
, onlyDeclaresVariables
) {
22 return llvm::all_of(Node
.decls(), [](Decl
*D
) { return isa
<VarDecl
>(D
); });
26 void IsolateDeclarationCheck::registerMatchers(MatchFinder
*Finder
) {
27 Finder
->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()),
28 hasParent(compoundStmt()))
33 static SourceLocation
findStartOfIndirection(SourceLocation Start
,
35 const SourceManager
&SM
,
36 const LangOptions
&LangOpts
) {
37 assert(Indirections
>= 0 && "Indirections must be non-negative");
38 if (Indirections
== 0)
41 // Note that the post-fix decrement is necessary to perform the correct
42 // number of transformations.
43 while (Indirections
-- != 0) {
44 Start
= findPreviousAnyTokenKind(Start
, SM
, LangOpts
, tok::star
, tok::amp
);
45 if (Start
.isInvalid() || Start
.isMacroID())
51 static bool isMacroID(SourceRange R
) {
52 return R
.getBegin().isMacroID() || R
.getEnd().isMacroID();
55 /// This function counts the number of written indirections for the given
56 /// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing
59 static int countIndirections(const Type
*T
, int Indirections
= 0) {
60 if (T
->isFunctionPointerType()) {
61 const auto *Pointee
= T
->getPointeeType()->castAs
<FunctionType
>();
62 return countIndirections(
63 Pointee
->getReturnType().IgnoreParens().getTypePtr(), ++Indirections
);
66 // Note: Do not increment the 'Indirections' because it is not yet clear
67 // if there is an indirection added in the source code of the array
69 if (const auto *AT
= dyn_cast
<ArrayType
>(T
))
70 return countIndirections(AT
->getElementType().IgnoreParens().getTypePtr(),
73 if (isa
<PointerType
>(T
) || isa
<ReferenceType
>(T
))
74 return countIndirections(T
->getPointeeType().IgnoreParens().getTypePtr(),
80 static bool typeIsMemberPointer(const Type
*T
) {
81 if (isa
<ArrayType
>(T
))
82 return typeIsMemberPointer(T
->getArrayElementTypeNoTypeQual());
84 if ((isa
<PointerType
>(T
) || isa
<ReferenceType
>(T
)) &&
85 isa
<PointerType
>(T
->getPointeeType()))
86 return typeIsMemberPointer(T
->getPointeeType().getTypePtr());
88 return isa
<MemberPointerType
>(T
);
91 /// This function tries to extract the SourceRanges that make up all
92 /// declarations in this \c DeclStmt.
94 /// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}.
95 /// Each \c SourceRange is of the form [Begin, End).
96 /// If any of the create ranges is invalid or in a macro the result will be
98 /// If the \c DeclStmt contains only one declaration, the result is \c None.
99 /// If the \c DeclStmt contains declarations other than \c VarDecl the result
103 /// int * ptr1 = nullptr, value = 42;
104 /// // [ ][ ] [ ] - The ranges here are inclusive
106 /// \todo Generalize this function to take other declarations than \c VarDecl.
107 static std::optional
<std::vector
<SourceRange
>>
108 declRanges(const DeclStmt
*DS
, const SourceManager
&SM
,
109 const LangOptions
&LangOpts
) {
110 std::size_t DeclCount
= std::distance(DS
->decl_begin(), DS
->decl_end());
114 if (rangeContainsExpansionsOrDirectives(DS
->getSourceRange(), SM
, LangOpts
))
117 // The initial type of the declaration and each declaration has it's own
118 // slice. This is necessary, because pointers and references bind only
119 // to the local variable and not to all variables in the declaration.
120 // Example: 'int *pointer, value = 42;'
121 std::vector
<SourceRange
> Slices
;
122 Slices
.reserve(DeclCount
+ 1);
124 // Calculate the first slice, for now only variables are handled but in the
125 // future this should be relaxed and support various kinds of declarations.
126 const auto *FirstDecl
= dyn_cast
<VarDecl
>(*DS
->decl_begin());
128 if (FirstDecl
== nullptr)
131 // FIXME: Member pointers are not transformed correctly right now, that's
132 // why they are treated as problematic here.
133 if (typeIsMemberPointer(FirstDecl
->getType().IgnoreParens().getTypePtr()))
136 // Consider the following case: 'int * pointer, value = 42;'
137 // Created slices (inclusive) [ ][ ] [ ]
138 // Because 'getBeginLoc' points to the start of the variable *name*, the
139 // location of the pointer must be determined separately.
140 SourceLocation Start
= findStartOfIndirection(
141 FirstDecl
->getLocation(),
142 countIndirections(FirstDecl
->getType().IgnoreParens().getTypePtr()), SM
,
145 // Fix function-pointer declarations that have a '(' in front of the
147 // Example: 'void (*f2)(int), (*g2)(int, float) = gg;'
148 // Slices: [ ][ ] [ ]
149 if (FirstDecl
->getType()->isFunctionPointerType())
150 Start
= findPreviousTokenKind(Start
, SM
, LangOpts
, tok::l_paren
);
152 // It is possible that a declarator is wrapped with parens.
153 // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;'
154 // The slice for the type-part must not contain these parens. Consequently
155 // 'Start' is moved to the most left paren if there are parens.
157 if (Start
.isInvalid() || Start
.isMacroID())
160 Token T
= getPreviousToken(Start
, SM
, LangOpts
);
161 if (T
.is(tok::l_paren
)) {
162 Start
= findPreviousTokenStart(Start
, SM
, LangOpts
);
168 SourceRange
DeclRange(DS
->getBeginLoc(), Start
);
169 if (DeclRange
.isInvalid() || isMacroID(DeclRange
))
172 // The first slice, that is prepended to every isolated declaration, is
174 Slices
.emplace_back(DeclRange
);
176 // Create all following slices that each declare a variable.
177 SourceLocation DeclBegin
= Start
;
178 for (const auto &Decl
: DS
->decls()) {
179 const auto *CurrentDecl
= cast
<VarDecl
>(Decl
);
181 // FIXME: Member pointers are not transformed correctly right now, that's
182 // why they are treated as problematic here.
183 if (typeIsMemberPointer(CurrentDecl
->getType().IgnoreParens().getTypePtr()))
186 SourceLocation DeclEnd
=
187 CurrentDecl
->hasInit()
188 ? findNextTerminator(CurrentDecl
->getInit()->getEndLoc(), SM
,
190 : findNextTerminator(CurrentDecl
->getEndLoc(), SM
, LangOpts
);
192 SourceRange
VarNameRange(DeclBegin
, DeclEnd
);
193 if (VarNameRange
.isInvalid() || isMacroID(VarNameRange
))
196 Slices
.emplace_back(VarNameRange
);
197 DeclBegin
= DeclEnd
.getLocWithOffset(1);
202 static std::optional
<std::vector
<StringRef
>>
203 collectSourceRanges(llvm::ArrayRef
<SourceRange
> Ranges
, const SourceManager
&SM
,
204 const LangOptions
&LangOpts
) {
205 std::vector
<StringRef
> Snippets
;
206 Snippets
.reserve(Ranges
.size());
208 for (const auto &Range
: Ranges
) {
209 CharSourceRange CharRange
= Lexer::getAsCharRange(
210 CharSourceRange::getCharRange(Range
.getBegin(), Range
.getEnd()), SM
,
213 if (CharRange
.isInvalid())
216 bool InvalidText
= false;
218 Lexer::getSourceText(CharRange
, SM
, LangOpts
, &InvalidText
);
223 Snippets
.emplace_back(Snippet
);
229 /// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
230 static std::vector
<std::string
>
231 createIsolatedDecls(llvm::ArrayRef
<StringRef
> Snippets
) {
232 // The first section is the type snippet, which does not make a decl itself.
233 assert(Snippets
.size() > 2 && "Not enough snippets to create isolated decls");
234 std::vector
<std::string
> Decls(Snippets
.size() - 1);
236 for (std::size_t I
= 1; I
< Snippets
.size(); ++I
)
237 Decls
[I
- 1] = Twine(Snippets
[0])
238 .concat(Snippets
[0].ends_with(" ") ? "" : " ")
239 .concat(Snippets
[I
].ltrim())
246 void IsolateDeclarationCheck::check(const MatchFinder::MatchResult
&Result
) {
247 const auto *WholeDecl
= Result
.Nodes
.getNodeAs
<DeclStmt
>("decl_stmt");
250 diag(WholeDecl
->getBeginLoc(),
251 "multiple declarations in a single statement reduces readability");
253 std::optional
<std::vector
<SourceRange
>> PotentialRanges
=
254 declRanges(WholeDecl
, *Result
.SourceManager
, getLangOpts());
255 if (!PotentialRanges
)
258 std::optional
<std::vector
<StringRef
>> PotentialSnippets
= collectSourceRanges(
259 *PotentialRanges
, *Result
.SourceManager
, getLangOpts());
261 if (!PotentialSnippets
)
264 std::vector
<std::string
> NewDecls
= createIsolatedDecls(*PotentialSnippets
);
265 std::string Replacement
= llvm::join(
267 (Twine("\n") + Lexer::getIndentationForLine(WholeDecl
->getBeginLoc(),
268 *Result
.SourceManager
))
271 Diag
<< FixItHint::CreateReplacement(WholeDecl
->getSourceRange(),
274 } // namespace clang::tidy::readability