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"
13 using namespace clang::ast_matchers
;
14 using namespace clang::tidy::utils::lexer
;
18 namespace readability
{
21 AST_MATCHER(DeclStmt
, isSingleDecl
) { return Node
.isSingleDecl(); }
22 AST_MATCHER(DeclStmt
, onlyDeclaresVariables
) {
23 return llvm::all_of(Node
.decls(), [](Decl
*D
) { return isa
<VarDecl
>(D
); });
27 void IsolateDeclarationCheck::registerMatchers(MatchFinder
*Finder
) {
28 Finder
->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()),
29 hasParent(compoundStmt()))
34 static SourceLocation
findStartOfIndirection(SourceLocation Start
,
36 const SourceManager
&SM
,
37 const LangOptions
&LangOpts
) {
38 assert(Indirections
>= 0 && "Indirections must be non-negative");
39 if (Indirections
== 0)
42 // Note that the post-fix decrement is necessary to perform the correct
43 // number of transformations.
44 while (Indirections
-- != 0) {
45 Start
= findPreviousAnyTokenKind(Start
, SM
, LangOpts
, tok::star
, tok::amp
);
46 if (Start
.isInvalid() || Start
.isMacroID())
47 return SourceLocation();
52 static bool isMacroID(SourceRange R
) {
53 return R
.getBegin().isMacroID() || R
.getEnd().isMacroID();
56 /// This function counts the number of written indirections for the given
57 /// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing
60 static int countIndirections(const Type
*T
, int Indirections
= 0) {
61 if (T
->isFunctionPointerType()) {
62 const auto *Pointee
= T
->getPointeeType()->castAs
<FunctionType
>();
63 return countIndirections(
64 Pointee
->getReturnType().IgnoreParens().getTypePtr(), ++Indirections
);
67 // Note: Do not increment the 'Indirections' because it is not yet clear
68 // if there is an indirection added in the source code of the array
70 if (const auto *AT
= dyn_cast
<ArrayType
>(T
))
71 return countIndirections(AT
->getElementType().IgnoreParens().getTypePtr(),
74 if (isa
<PointerType
>(T
) || isa
<ReferenceType
>(T
))
75 return countIndirections(T
->getPointeeType().IgnoreParens().getTypePtr(),
81 static bool typeIsMemberPointer(const Type
*T
) {
82 if (isa
<ArrayType
>(T
))
83 return typeIsMemberPointer(T
->getArrayElementTypeNoTypeQual());
85 if ((isa
<PointerType
>(T
) || isa
<ReferenceType
>(T
)) &&
86 isa
<PointerType
>(T
->getPointeeType()))
87 return typeIsMemberPointer(T
->getPointeeType().getTypePtr());
89 return isa
<MemberPointerType
>(T
);
92 /// This function tries to extract the SourceRanges that make up all
93 /// declarations in this \c DeclStmt.
95 /// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}.
96 /// Each \c SourceRange is of the form [Begin, End).
97 /// If any of the create ranges is invalid or in a macro the result will be
99 /// If the \c DeclStmt contains only one declaration, the result is \c None.
100 /// If the \c DeclStmt contains declarations other than \c VarDecl the result
104 /// int * ptr1 = nullptr, value = 42;
105 /// // [ ][ ] [ ] - The ranges here are inclusive
107 /// \todo Generalize this function to take other declarations than \c VarDecl.
108 static Optional
<std::vector
<SourceRange
>>
109 declRanges(const DeclStmt
*DS
, const SourceManager
&SM
,
110 const LangOptions
&LangOpts
) {
111 std::size_t DeclCount
= std::distance(DS
->decl_begin(), DS
->decl_end());
115 if (rangeContainsExpansionsOrDirectives(DS
->getSourceRange(), SM
, LangOpts
))
118 // The initial type of the declaration and each declaration has it's own
119 // slice. This is necessary, because pointers and references bind only
120 // to the local variable and not to all variables in the declaration.
121 // Example: 'int *pointer, value = 42;'
122 std::vector
<SourceRange
> Slices
;
123 Slices
.reserve(DeclCount
+ 1);
125 // Calculate the first slice, for now only variables are handled but in the
126 // future this should be relaxed and support various kinds of declarations.
127 const auto *FirstDecl
= dyn_cast
<VarDecl
>(*DS
->decl_begin());
129 if (FirstDecl
== nullptr)
132 // FIXME: Member pointers are not transformed correctly right now, that's
133 // why they are treated as problematic here.
134 if (typeIsMemberPointer(FirstDecl
->getType().IgnoreParens().getTypePtr()))
137 // Consider the following case: 'int * pointer, value = 42;'
138 // Created slices (inclusive) [ ][ ] [ ]
139 // Because 'getBeginLoc' points to the start of the variable *name*, the
140 // location of the pointer must be determined separately.
141 SourceLocation Start
= findStartOfIndirection(
142 FirstDecl
->getLocation(),
143 countIndirections(FirstDecl
->getType().IgnoreParens().getTypePtr()), SM
,
146 // Fix function-pointer declarations that have a '(' in front of the
148 // Example: 'void (*f2)(int), (*g2)(int, float) = gg;'
149 // Slices: [ ][ ] [ ]
150 if (FirstDecl
->getType()->isFunctionPointerType())
151 Start
= findPreviousTokenKind(Start
, SM
, LangOpts
, tok::l_paren
);
153 // It is possible that a declarator is wrapped with parens.
154 // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;'
155 // The slice for the type-part must not contain these parens. Consequently
156 // 'Start' is moved to the most left paren if there are parens.
158 if (Start
.isInvalid() || Start
.isMacroID())
161 Token T
= getPreviousToken(Start
, SM
, LangOpts
);
162 if (T
.is(tok::l_paren
)) {
163 Start
= findPreviousTokenStart(Start
, SM
, LangOpts
);
169 SourceRange
DeclRange(DS
->getBeginLoc(), Start
);
170 if (DeclRange
.isInvalid() || isMacroID(DeclRange
))
173 // The first slice, that is prepended to every isolated declaration, is
175 Slices
.emplace_back(DeclRange
);
177 // Create all following slices that each declare a variable.
178 SourceLocation DeclBegin
= Start
;
179 for (const auto &Decl
: DS
->decls()) {
180 const auto *CurrentDecl
= cast
<VarDecl
>(Decl
);
182 // FIXME: Member pointers are not transformed correctly right now, that's
183 // why they are treated as problematic here.
184 if (typeIsMemberPointer(CurrentDecl
->getType().IgnoreParens().getTypePtr()))
187 SourceLocation DeclEnd
=
188 CurrentDecl
->hasInit()
189 ? findNextTerminator(CurrentDecl
->getInit()->getEndLoc(), SM
,
191 : findNextTerminator(CurrentDecl
->getEndLoc(), SM
, LangOpts
);
193 SourceRange
VarNameRange(DeclBegin
, DeclEnd
);
194 if (VarNameRange
.isInvalid() || isMacroID(VarNameRange
))
197 Slices
.emplace_back(VarNameRange
);
198 DeclBegin
= DeclEnd
.getLocWithOffset(1);
203 static Optional
<std::vector
<StringRef
>>
204 collectSourceRanges(llvm::ArrayRef
<SourceRange
> Ranges
, const SourceManager
&SM
,
205 const LangOptions
&LangOpts
) {
206 std::vector
<StringRef
> Snippets
;
207 Snippets
.reserve(Ranges
.size());
209 for (const auto &Range
: Ranges
) {
210 CharSourceRange CharRange
= Lexer::getAsCharRange(
211 CharSourceRange::getCharRange(Range
.getBegin(), Range
.getEnd()), SM
,
214 if (CharRange
.isInvalid())
217 bool InvalidText
= false;
219 Lexer::getSourceText(CharRange
, SM
, LangOpts
, &InvalidText
);
224 Snippets
.emplace_back(Snippet
);
230 /// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
231 static std::vector
<std::string
>
232 createIsolatedDecls(llvm::ArrayRef
<StringRef
> Snippets
) {
233 // The first section is the type snippet, which does not make a decl itself.
234 assert(Snippets
.size() > 2 && "Not enough snippets to create isolated decls");
235 std::vector
<std::string
> Decls(Snippets
.size() - 1);
237 for (std::size_t I
= 1; I
< Snippets
.size(); ++I
)
238 Decls
[I
- 1] = Twine(Snippets
[0])
239 .concat(Snippets
[0].endswith(" ") ? "" : " ")
240 .concat(Snippets
[I
].ltrim())
247 void IsolateDeclarationCheck::check(const MatchFinder::MatchResult
&Result
) {
248 const auto *WholeDecl
= Result
.Nodes
.getNodeAs
<DeclStmt
>("decl_stmt");
251 diag(WholeDecl
->getBeginLoc(),
252 "multiple declarations in a single statement reduces readability");
254 Optional
<std::vector
<SourceRange
>> PotentialRanges
=
255 declRanges(WholeDecl
, *Result
.SourceManager
, getLangOpts());
256 if (!PotentialRanges
)
259 Optional
<std::vector
<StringRef
>> PotentialSnippets
= collectSourceRanges(
260 *PotentialRanges
, *Result
.SourceManager
, getLangOpts());
262 if (!PotentialSnippets
)
265 std::vector
<std::string
> NewDecls
= createIsolatedDecls(*PotentialSnippets
);
266 std::string Replacement
= llvm::join(
268 (Twine("\n") + Lexer::getIndentationForLine(WholeDecl
->getBeginLoc(),
269 *Result
.SourceManager
))
272 Diag
<< FixItHint::CreateReplacement(WholeDecl
->getSourceRange(),
275 } // namespace readability