clang-tools 23.0.0git
RedundantNestedIfCheck.cpp
Go to the documentation of this file.
1//===--- RedundantNestedIfCheck.cpp - clang-tidy -------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
10#include "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/DeclTemplate.h"
13#include "clang/AST/Stmt.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/Basic/Diagnostic.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/Tooling/FixIt.h"
18#include "llvm/ADT/ArrayRef.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/ADT/SmallVector.h"
21#include <cassert>
22#include <optional>
23#include <string>
24#include <vector>
25
26using namespace clang::ast_matchers;
27
29
30static constexpr llvm::StringLiteral AllowUserDefinedBoolConversionStr =
31 "AllowUserDefinedBoolConversion";
32static constexpr llvm::StringLiteral MergeableIfDiag =
33 "nested 'if' statements can be merged together";
34static constexpr llvm::StringLiteral NestedIfNote =
35 "nested 'if' statement to merge declared here";
36
37namespace {
38
39using IfChain = llvm::SmallVector<const IfStmt *>;
40
41enum class WarningType {
42 None,
43 WarnOnly,
44 WarnAndFix,
45};
46
47enum class CombinedConditionBuildStatus {
48 Success,
49 UnsupportedCommentPlacement,
50 Failure,
51};
52
53struct CombinedConditionBuildResult {
54 CombinedConditionBuildStatus Status = CombinedConditionBuildStatus::Failure;
55 std::string Text;
56};
57
58} // namespace
59
60// Conjoining conditions with `&&` can change behavior when a condition relies
61// on contextual user-defined conversion to `bool`.
62static bool containsUserDefinedBoolConversion(const Expr *Expression) {
63 assert(Expression);
64
65 if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Expression);
66 Cast && Cast->getCastKind() == CK_UserDefinedConversion)
67 return true;
68
69 return llvm::any_of(Expression->children(), [](const Stmt *Child) {
70 const Expr *ChildExpr = dyn_cast_or_null<Expr>(Child);
71 return ChildExpr && containsUserDefinedBoolConversion(ChildExpr);
72 });
73}
74
75static bool conditionNeedsBoolCast(const Expr *Condition) {
76 assert(Condition);
77
79 return false;
80
81 const Expr *const Unwrapped =
82 Condition->IgnoreImplicitAsWritten()->IgnoreParens();
83 const QualType ConditionType = Unwrapped->getType();
84 return ConditionType.isNull() || !ConditionType->isScalarType();
85}
86
87static bool
88isConditionExpressionMergeable(const Expr *Condition,
89 bool AllowUserDefinedBoolConversion) {
90 assert(Condition);
91
92 if (Condition->isTypeDependent())
93 return false;
94
96 return AllowUserDefinedBoolConversion;
97
98 const Expr *const Unwrapped = Condition->IgnoreParenImpCasts();
99 const QualType ConditionType = Unwrapped->getType();
100 return !ConditionType.isNull() && ConditionType->isScalarType();
101}
102
103static std::optional<CharSourceRange>
104getIfConditionRange(const IfStmt *If, const SourceManager &SM,
105 const LangOptions &LangOpts) {
106 assert(If);
107
108 const SourceLocation ConditionBegin =
109 Lexer::getLocForEndOfToken(If->getLParenLoc(), 0, SM, LangOpts);
110 if (ConditionBegin.isInvalid() || If->getRParenLoc().isInvalid())
111 return std::nullopt;
112
113 const CharSourceRange FileRange = Lexer::makeFileCharRange(
114 CharSourceRange::getCharRange(ConditionBegin, If->getRParenLoc()), SM,
115 LangOpts);
116 if (FileRange.isInvalid())
117 return std::nullopt;
118
119 return FileRange;
120}
121
122static std::optional<std::string>
123getIfConditionText(const IfStmt *If, const SourceManager &SM,
124 const LangOptions &LangOpts) {
125 const std::optional<CharSourceRange> ConditionRange =
126 getIfConditionRange(If, SM, LangOpts);
127 if (!ConditionRange)
128 return std::nullopt;
129
130 bool Invalid = false;
131 const StringRef ConditionText =
132 Lexer::getSourceText(*ConditionRange, SM, LangOpts, &Invalid);
133 return Invalid || ConditionText.empty()
134 ? std::nullopt
135 : std::optional<std::string>(ConditionText.str());
136}
137
138static bool isLocationInCharRange(SourceLocation Loc, CharSourceRange Range,
139 const SourceManager &SM) {
140 return Loc.isValid() && Range.isValid() &&
141 !SM.isBeforeInTranslationUnit(Loc, Range.getBegin()) &&
142 SM.isBeforeInTranslationUnit(Loc, Range.getEnd());
143}
144
145// Keep fix-its only when comments in removed nested headers stay inside the
146// preserved condition text. Other comment placements keep the diagnostic but
147// suppress the rewrite.
148static bool hasOnlyPayloadCommentsInNestedHeader(const IfStmt *Nested,
149 const SourceManager &SM,
150 const LangOptions &LangOpts) {
151 assert(Nested);
152
153 const CharSourceRange HeaderFileRange = Lexer::makeFileCharRange(
154 CharSourceRange::getCharRange(Nested->getBeginLoc(),
155 Nested->getThen()->getBeginLoc()),
156 SM, LangOpts);
157 if (HeaderFileRange.isInvalid())
158 return false;
159
160 const std::optional<CharSourceRange> PayloadRange =
161 getIfConditionRange(Nested, SM, LangOpts);
162 if (!PayloadRange)
163 return false;
164
165 const std::vector<utils::lexer::CommentToken> Comments =
166 utils::lexer::getCommentsInRange(HeaderFileRange, SM, LangOpts);
167 return llvm::all_of(Comments, [&](const utils::lexer::CommentToken &Comment) {
168 return isLocationInCharRange(Comment.Loc, *PayloadRange, SM);
169 });
170}
171
172// Only an outer condition variable can be rewritten safely by moving the
173// declaration into an init-statement and conjoining the condition variable.
174static bool
176 bool AllowUserDefinedBoolConversion) {
177 assert(If);
178 assert(If->hasVarStorage());
179
180 // `if (init; bool X = cond())` cannot generally become
181 // `if (init; bool X = cond(); X)`.
182 // Same-type declaration merging, like `if (bool Y = init(), X = cond(); X)`,
183 // is possible but too narrow to be worth supporting.
184 if (If->hasInitStorage())
185 return false;
186
187 const VarDecl *const ConditionVariable = If->getConditionVariable();
188 const DeclStmt *const ConditionVariableDeclStmt =
189 If->getConditionVariableDeclStmt();
190 if (!ConditionVariable || !ConditionVariableDeclStmt ||
191 !ConditionVariableDeclStmt->isSingleDecl() ||
192 ConditionVariable->getName().empty())
193 return false;
194
195 return If->getCond() && isConditionExpressionMergeable(
196 If->getCond(), AllowUserDefinedBoolConversion);
197}
198
199// Accept either `if (...) if (...)` or `if (...) { if (...) }` where the
200// compound contains exactly one statement.
201static const IfStmt *getOnlyNestedIf(const Stmt *Then) {
202 if (!Then)
203 return nullptr;
204 if (const auto *NestedIf = dyn_cast<IfStmt>(Then))
205 return NestedIf;
206
207 const auto *const Compound = dyn_cast<CompoundStmt>(Then);
208 if (!Compound || Compound->size() != 1)
209 return nullptr;
210
211 return dyn_cast<IfStmt>(Compound->body_front());
212}
213
214static bool isMergeCandidate(const IfStmt *If, bool AllowInitStorage,
215 bool RequireConstexpr, bool AllowConditionVariable,
216 bool AllowUserDefinedBoolConversion,
217 const LangOptions &LangOpts) {
218 assert(If);
219
220 const bool HasAllowedInitStorage = AllowInitStorage || !If->hasInitStorage();
221 const bool HasRequiredConstexpr = If->isConstexpr() == RequireConstexpr;
222 const bool IsMergeableStructure = If->getThen() && !If->isConsteval() &&
223 !If->getElse() && HasAllowedInitStorage &&
224 HasRequiredConstexpr;
225
226 const bool HasMergeableConditionVariable =
227 If->hasVarStorage() && AllowConditionVariable && LangOpts.CPlusPlus17 &&
228 canRewriteOuterConditionVariable(If, AllowUserDefinedBoolConversion);
229 const bool HasMergeableConditionExpression =
230 !If->hasVarStorage() && If->getCond() &&
231 isConditionExpressionMergeable(If->getCond(),
232 AllowUserDefinedBoolConversion);
233
234 return IsMergeableStructure &&
235 (HasMergeableConditionVariable || HasMergeableConditionExpression);
236}
237
238// Statement attributes wrap the `if` in an `AttributedStmt`, so removing nested
239// `if` tokens can invalidate attribute placement.
240static bool isAttributedIf(const IfStmt *If, ASTContext &Context) {
241 assert(If);
242
243 const DynTypedNodeList Parents = Context.getParents(*If);
244 return !Parents.empty() && Parents[0].get<AttributedStmt>() != nullptr;
245}
246
247static IfChain getMergeChain(const IfStmt *Root, ASTContext &Context,
248 bool AllowUserDefinedBoolConversion) {
249 assert(Root);
250
251 IfChain Chain;
252 const LangOptions &LangOpts = Context.getLangOpts();
253 const bool RequireConstexpr = Root->isConstexpr();
254 if (!isMergeCandidate(Root, /*AllowInitStorage=*/true,
255 /*RequireConstexpr=*/RequireConstexpr,
256 /*AllowConditionVariable=*/true,
257 AllowUserDefinedBoolConversion, LangOpts) ||
258 isAttributedIf(Root, Context))
259 return Chain;
260
261 Chain.push_back(Root);
262 const IfStmt *Current = Root;
263 while (const IfStmt *const Nested = getOnlyNestedIf(Current->getThen())) {
264 if (!isMergeCandidate(Nested, /*AllowInitStorage=*/false,
265 /*RequireConstexpr=*/RequireConstexpr,
266 /*AllowConditionVariable=*/false,
267 AllowUserDefinedBoolConversion, LangOpts) ||
268 isAttributedIf(Nested, Context))
269 break;
270
271 Chain.push_back(Nested);
272 Current = Nested;
273 }
274
275 return Chain;
276}
277
278static bool isConstantBooleanCondition(const Expr *Condition,
279 const ASTContext &Context,
280 bool RequiredValue) {
281 if (!Condition || Condition->isValueDependent() ||
282 Condition->isInstantiationDependent())
283 return false;
284
285 bool EvaluatedValue = false;
286 return Condition->EvaluateAsBooleanCondition(EvaluatedValue, Context) &&
287 EvaluatedValue == RequiredValue;
288}
289
290// Some instantiation-dependent conditions are safe to form even when an
291// earlier `if constexpr` condition is not known to be `true`. For example,
292// non-type template parameters and `requires` expressions do not depend on a
293// discarded branch to avoid hard substitution errors.
294static bool isAlwaysFormableDependentConstexprCondition(const Expr *Condition) {
295 assert(Condition);
296
297 Condition = Condition->IgnoreParenImpCasts();
298 if (isa<CXXBoolLiteralExpr, IntegerLiteral, RequiresExpr>(Condition))
299 return true;
300
301 if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Condition))
302 return isa<NonTypeTemplateParmDecl>(DeclRef->getDecl());
303
304 if (const auto *Unary = dyn_cast<UnaryOperator>(Condition))
305 return isAlwaysFormableDependentConstexprCondition(Unary->getSubExpr());
306
307 if (const auto *Binary = dyn_cast<BinaryOperator>(Condition)) {
308 if (Binary->isAssignmentOp() || Binary->getOpcode() == BO_Comma)
309 return false;
310
311 return isAlwaysFormableDependentConstexprCondition(Binary->getLHS()) &&
313 }
314
315 return false;
316}
317
318static bool
319isConstexprChainSemanticallySafe(llvm::ArrayRef<const IfStmt *> Chain,
320 const ASTContext &Context) {
321 if (Chain.empty() || !Chain.front()->isConstexpr())
322 return true;
323
324 bool AllPreviousConditionsAreConstantTrue = true;
325 for (const IfStmt *If : Chain) {
326 const Expr *const Condition = If->getCond();
327 if (Condition->isInstantiationDependent() &&
328 !AllPreviousConditionsAreConstantTrue &&
330 return false;
331
332 AllPreviousConditionsAreConstantTrue =
333 AllPreviousConditionsAreConstantTrue &&
334 isConstantBooleanCondition(Condition, Context, /*RequiredValue=*/true);
335 }
336
337 return true;
338}
339
340// A range is unsafe for text edits if it crosses macro expansions or
341// preprocessor directives.
342template <typename RangeT> static bool isUnsafeRangeSpelling(RangeT Range) {
343 return Range.isInvalid() || Range.getBegin().isMacroID() ||
344 Range.getEnd().isMacroID();
345}
346
347static bool isUnsafeTokenRange(SourceRange Range, const SourceManager &SM,
348 const LangOptions &LangOpts) {
349 if (isUnsafeRangeSpelling(Range))
350 return true;
351
352 return Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range), SM,
353 LangOpts)
354 .isInvalid() ||
356}
357
358static bool isUnsafeCharRange(CharSourceRange Range, const SourceManager &SM,
359 const LangOptions &LangOpts) {
360 if (isUnsafeRangeSpelling(Range))
361 return true;
362
363 return Lexer::makeFileCharRange(Range, SM, LangOpts).isInvalid() ||
365 SM, LangOpts);
366}
367
368// Validate every range that contributes to the final edit set before offering
369// fix-its. If any range is unsafe, keep looking for a diagnosable child chain.
370static bool isFixitSafeForChain(llvm::ArrayRef<const IfStmt *> Chain,
371 const SourceManager &SM,
372 const LangOptions &LangOpts) {
373 if (Chain.empty())
374 return false;
375
376 const IfStmt *const Root = Chain.front();
377 const std::optional<CharSourceRange> RootConditionRange =
378 Root->hasInitStorage() && !Root->hasVarStorage()
379 ? std::optional<CharSourceRange>(CharSourceRange::getTokenRange(
380 Root->getCond()->getSourceRange()))
381 : getIfConditionRange(Root, SM, LangOpts);
382 if (!RootConditionRange ||
383 isUnsafeCharRange(*RootConditionRange, SM, LangOpts))
384 return false;
385 if (!Root->hasVarStorage() &&
386 isUnsafeTokenRange(Root->getCond()->getSourceRange(), SM, LangOpts))
387 return false;
388
389 if (Root->hasVarStorage()) {
390 const DeclStmt *const ConditionVariableDeclStmt =
391 Root->getConditionVariableDeclStmt();
392 if (!ConditionVariableDeclStmt ||
393 isUnsafeTokenRange(ConditionVariableDeclStmt->getSourceRange(), SM,
394 LangOpts)) {
395 return false;
396 }
397 }
398
399 const llvm::ArrayRef<const IfStmt *> ChainRef(Chain);
400 return llvm::all_of(
401 llvm::zip(ChainRef.drop_back(), ChainRef.drop_front()),
402 [&](const auto &ParentAndChild) {
403 const auto &[Parent, Child] = ParentAndChild;
404 if (isUnsafeTokenRange(Child->getCond()->getSourceRange(), SM,
405 LangOpts))
406 return false;
407
408 const CharSourceRange ChildHeaderRange = CharSourceRange::getCharRange(
409 Child->getBeginLoc(), Child->getThen()->getBeginLoc());
410 if (isUnsafeCharRange(ChildHeaderRange, SM, LangOpts))
411 return false;
412
413 const auto *const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
414 return !Wrapper ||
415 (!isUnsafeTokenRange(
416 SourceRange(Wrapper->getLBracLoc(), Wrapper->getLBracLoc()),
417 SM, LangOpts) &&
418 !isUnsafeTokenRange(
419 SourceRange(Wrapper->getRBracLoc(), Wrapper->getRBracLoc()),
420 SM, LangOpts));
421 });
422}
423
424static std::string wrapConditionText(StringRef ConditionText,
425 bool NeedBoolCast) {
426 if (!NeedBoolCast)
427 return ConditionText.str();
428
429 std::string Result("static_cast<bool>(");
430 Result += ConditionText;
431 Result += ')';
432 return Result;
433}
434
435static std::optional<std::string> getConjunctText(const IfStmt *If,
436 const ASTContext &Context,
437 bool UseConditionExprText) {
438 assert(If);
439
440 const SourceManager &SM = Context.getSourceManager();
441 const LangOptions &LangOpts = Context.getLangOpts();
442
443 std::optional<std::string> ConditionText;
444 if (UseConditionExprText) {
445 const StringRef ConditionExprText =
446 tooling::fixit::getText(*If->getCond(), Context);
447 if (ConditionExprText.empty())
448 return std::nullopt;
449 ConditionText = ConditionExprText.str();
450 } else {
451 ConditionText = getIfConditionText(If, SM, LangOpts);
452 if (!ConditionText)
453 return std::nullopt;
454 }
455
456 return wrapConditionText(*ConditionText,
457 conditionNeedsBoolCast(If->getCond()));
458}
459
460static CombinedConditionBuildResult
461buildCombinedCondition(llvm::ArrayRef<const IfStmt *> Chain,
462 const ASTContext &Context) {
463 if (Chain.empty())
464 return {};
465
466 const SourceManager &SM = Context.getSourceManager();
467 const LangOptions &LangOpts = Context.getLangOpts();
468 std::string CombinedCondition;
469
470 for (const auto &[Index, If] : llvm::enumerate(Chain)) {
471 const bool IsRoot = Index == 0;
472 if (!IsRoot && !hasOnlyPayloadCommentsInNestedHeader(If, SM, LangOpts))
473 return {CombinedConditionBuildStatus::UnsupportedCommentPlacement, {}};
474
475 if (IsRoot && If->hasVarStorage()) {
476 const VarDecl *const ConditionVariable = If->getConditionVariable();
477 if (!ConditionVariable)
478 return {};
479
480 const std::optional<std::string> ConditionText =
481 getIfConditionText(If, SM, LangOpts);
482 if (!ConditionText)
483 return {};
484
485 CombinedCondition = *ConditionText;
486 CombinedCondition += "; ";
487 CombinedCondition += wrapConditionText(
488 ConditionVariable->getName(), conditionNeedsBoolCast(If->getCond()));
489 continue;
490 }
491
492 const std::optional<std::string> ConjunctText =
493 getConjunctText(If, Context,
494 /*UseConditionExprText=*/IsRoot &&
495 If->hasInitStorage() && !If->hasVarStorage());
496 if (!ConjunctText)
497 return {};
498
499 if (!CombinedCondition.empty())
500 CombinedCondition += " && ";
501 CombinedCondition += '(';
502 CombinedCondition += *ConjunctText;
503 CombinedCondition += ')';
504 }
505
506 return {CombinedConditionBuildStatus::Success, std::move(CombinedCondition)};
507}
508
509static std::optional<CharSourceRange>
510getConditionReplacementRange(const IfStmt *If, const SourceManager &SM,
511 const LangOptions &LangOpts) {
512 assert(If);
513
514 return If->hasInitStorage() && !If->hasVarStorage()
515 ? std::optional<CharSourceRange>(CharSourceRange::getTokenRange(
516 If->getCond()->getSourceRange()))
517 : getIfConditionRange(If, SM, LangOpts);
518}
519
520static WarningType
521getWarningType(llvm::ArrayRef<const IfStmt *> Chain, const ASTContext &Context,
522 const SourceManager &SM, const LangOptions &LangOpts,
523 std::optional<std::string> *CombinedCondition) {
524 if (Chain.size() < 2 || !isFixitSafeForChain(Chain, SM, LangOpts))
525 return WarningType::None;
526
527 if (!isConstexprChainSemanticallySafe(Chain, Context))
528 return WarningType::None;
529
530 const CombinedConditionBuildResult Combined =
531 buildCombinedCondition(Chain, Context);
532 if (Combined.Status ==
533 CombinedConditionBuildStatus::UnsupportedCommentPlacement)
534 return WarningType::WarnOnly;
535 if (Combined.Status != CombinedConditionBuildStatus::Success)
536 return WarningType::None;
537
538 if (CombinedCondition)
539 *CombinedCondition = Combined.Text;
540 return WarningType::WarnAndFix;
541}
542
544 llvm::ArrayRef<const IfStmt *> Chain) {
545 for (const IfStmt *Nested : llvm::drop_begin(Chain))
546 Check.diag(Nested->getIfLoc(), NestedIfNote, DiagnosticIDs::Note);
547}
548
549static void diagnoseChain(RedundantNestedIfCheck &Check, const IfStmt *If,
550 ASTContext &Context,
551 bool AllowUserDefinedBoolConversion);
552
554 const Stmt *Branch, ASTContext &Context,
555 bool AllowUserDefinedBoolConversion) {
556 if (const IfStmt *const Nested = getOnlyNestedIf(Branch))
557 diagnoseChain(Check, Nested, Context, AllowUserDefinedBoolConversion);
558}
559
560// Match only syntactic chain roots. If a root cannot be diagnosed because it is
561// unsafe to rewrite, descend into excluded single-child nested `if` statements
562// in both branches and try again there.
563static void diagnoseChain(RedundantNestedIfCheck &Check, const IfStmt *If,
564 ASTContext &Context,
565 bool AllowUserDefinedBoolConversion) {
566 const SourceManager &SM = Context.getSourceManager();
567 const LangOptions &LangOpts = Context.getLangOpts();
568 const IfChain Chain =
569 getMergeChain(If, Context, AllowUserDefinedBoolConversion);
570
571 std::optional<std::string> CombinedCondition;
572 const WarningType Handling =
573 getWarningType(Chain, Context, SM, LangOpts, &CombinedCondition);
574 if (Handling == WarningType::None) {
575 diagnoseChildChain(Check, If->getThen(), Context,
576 AllowUserDefinedBoolConversion);
577 diagnoseChildChain(Check, If->getElse(), Context,
578 AllowUserDefinedBoolConversion);
579 return;
580 }
581
582 {
583 const DiagnosticBuilder Diag = Check.diag(If->getIfLoc(), MergeableIfDiag);
584 if (Handling == WarningType::WarnAndFix) {
585 const std::optional<CharSourceRange> ConditionRange =
586 getConditionReplacementRange(If, SM, LangOpts);
587 if (!ConditionRange || !CombinedCondition)
588 return;
589
590 Diag << FixItHint::CreateReplacement(*ConditionRange, *CombinedCondition);
591 const llvm::ArrayRef<const IfStmt *> ChainRef(Chain);
592 for (const auto &[Parent, Child] :
593 llvm::zip(ChainRef.drop_back(), ChainRef.drop_front())) {
594 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
595 Child->getBeginLoc(), Child->getThen()->getBeginLoc()));
596
597 const auto *const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
598 if (!Wrapper)
599 continue;
600
601 Diag << FixItHint::CreateRemoval(Wrapper->getLBracLoc())
602 << FixItHint::CreateRemoval(Wrapper->getRBracLoc());
603 }
604 }
605 }
606
607 emitNestedIfNotes(Check, Chain);
608}
609
611 ClangTidyContext *Context)
612 : ClangTidyCheck(Name, Context),
613 AllowUserDefinedBoolConversion(
614 Options.get(AllowUserDefinedBoolConversionStr, false)) {}
615
617 Options.store(Opts, AllowUserDefinedBoolConversionStr,
618 AllowUserDefinedBoolConversion);
619}
620
622 Finder->addMatcher(
623 ifStmt(unless(hasElse(stmt())),
624 unless(anyOf(hasParent(ifStmt(unless(hasElse(stmt())))),
625 hasParent(compoundStmt(
626 statementCountIs(1),
627 hasParent(ifStmt(unless(hasElse(stmt())))))))))
628 .bind("if"),
629 this);
630}
631
632void RedundantNestedIfCheck::check(const MatchFinder::MatchResult &Result) {
633 const auto *const If = Result.Nodes.getNodeAs<IfStmt>("if");
634 assert(If);
635
636 diagnoseChain(*this, If, *Result.Context, AllowUserDefinedBoolConversion);
637}
638
639} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Detects nested if statements that can be merged into one by / concatenating conditions with &&.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
RedundantNestedIfCheck(StringRef Name, ClangTidyContext *Context)
static void emitNestedIfNotes(RedundantNestedIfCheck &Check, llvm::ArrayRef< const IfStmt * > Chain)
static bool conditionNeedsBoolCast(const Expr *Condition)
static bool isUnsafeTokenRange(SourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
static constexpr llvm::StringLiteral MergeableIfDiag
static WarningType getWarningType(llvm::ArrayRef< const IfStmt * > Chain, const ASTContext &Context, const SourceManager &SM, const LangOptions &LangOpts, std::optional< std::string > *CombinedCondition)
static bool isFixitSafeForChain(llvm::ArrayRef< const IfStmt * > Chain, const SourceManager &SM, const LangOptions &LangOpts)
static constexpr llvm::StringLiteral AllowUserDefinedBoolConversionStr
static IfChain getMergeChain(const IfStmt *Root, ASTContext &Context, bool AllowUserDefinedBoolConversion)
static std::optional< CharSourceRange > getIfConditionRange(const IfStmt *If, const SourceManager &SM, const LangOptions &LangOpts)
static bool containsUserDefinedBoolConversion(const Expr *Expression)
static bool isUnsafeCharRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
static bool isConstexprChainSemanticallySafe(llvm::ArrayRef< const IfStmt * > Chain, const ASTContext &Context)
static CombinedConditionBuildResult buildCombinedCondition(llvm::ArrayRef< const IfStmt * > Chain, const ASTContext &Context)
static bool isUnsafeRangeSpelling(RangeT Range)
static bool isMergeCandidate(const IfStmt *If, bool AllowInitStorage, bool RequireConstexpr, bool AllowConditionVariable, bool AllowUserDefinedBoolConversion, const LangOptions &LangOpts)
static void diagnoseChain(RedundantNestedIfCheck &Check, const IfStmt *If, ASTContext &Context, bool AllowUserDefinedBoolConversion)
static bool isConditionExpressionMergeable(const Expr *Condition, bool AllowUserDefinedBoolConversion)
static bool canRewriteOuterConditionVariable(const IfStmt *If, bool AllowUserDefinedBoolConversion)
static bool isConstantBooleanCondition(const Expr *Condition, const ASTContext &Context, bool RequiredValue)
static void diagnoseChildChain(RedundantNestedIfCheck &Check, const Stmt *Branch, ASTContext &Context, bool AllowUserDefinedBoolConversion)
static bool isAttributedIf(const IfStmt *If, ASTContext &Context)
static std::optional< std::string > getIfConditionText(const IfStmt *If, const SourceManager &SM, const LangOptions &LangOpts)
static constexpr llvm::StringLiteral NestedIfNote
static bool hasOnlyPayloadCommentsInNestedHeader(const IfStmt *Nested, const SourceManager &SM, const LangOptions &LangOpts)
static std::optional< std::string > getConjunctText(const IfStmt *If, const ASTContext &Context, bool UseConditionExprText)
static bool isAlwaysFormableDependentConstexprCondition(const Expr *Condition)
static std::string wrapConditionText(StringRef ConditionText, bool NeedBoolCast)
static const IfStmt * getOnlyNestedIf(const Stmt *Then)
static std::optional< CharSourceRange > getConditionReplacementRange(const IfStmt *If, const SourceManager &SM, const LangOptions &LangOpts)
static bool isLocationInCharRange(SourceLocation Loc, CharSourceRange Range, const SourceManager &SM)
bool rangeContainsExpansionsOrDirectives(SourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Re-lex the provide Range and return false if either a macro spans multiple tokens,...
std::vector< CommentToken > getCommentsInRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Returns all comment tokens found in the given range.
llvm::StringMap< ClangTidyValue > OptionMap