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"
31 "AllowUserDefinedBoolConversion";
33 "nested 'if' statements can be merged together";
35 "nested 'if' statement to merge declared here";
39using IfChain = llvm::SmallVector<const IfStmt *>;
41enum class WarningType {
47enum class CombinedConditionBuildStatus {
49 UnsupportedCommentPlacement,
53struct CombinedConditionBuildResult {
54 CombinedConditionBuildStatus Status = CombinedConditionBuildStatus::Failure;
65 if (
const auto *Cast = dyn_cast<ImplicitCastExpr>(Expression);
66 Cast && Cast->getCastKind() == CK_UserDefinedConversion)
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);
81 const Expr *
const Unwrapped =
82 Condition->IgnoreImplicitAsWritten()->IgnoreParens();
83 const QualType ConditionType = Unwrapped->getType();
84 return ConditionType.isNull() || !ConditionType->isScalarType();
89 bool AllowUserDefinedBoolConversion) {
92 if (Condition->isTypeDependent())
96 return AllowUserDefinedBoolConversion;
98 const Expr *
const Unwrapped = Condition->IgnoreParenImpCasts();
99 const QualType ConditionType = Unwrapped->getType();
100 return !ConditionType.isNull() && ConditionType->isScalarType();
103static std::optional<CharSourceRange>
105 const LangOptions &LangOpts) {
108 const SourceLocation ConditionBegin =
109 Lexer::getLocForEndOfToken(If->getLParenLoc(), 0, SM, LangOpts);
110 if (ConditionBegin.isInvalid() || If->getRParenLoc().isInvalid())
113 const CharSourceRange FileRange = Lexer::makeFileCharRange(
114 CharSourceRange::getCharRange(ConditionBegin, If->getRParenLoc()), SM,
116 if (FileRange.isInvalid())
122static std::optional<std::string>
124 const LangOptions &LangOpts) {
125 const std::optional<CharSourceRange> ConditionRange =
130 bool Invalid =
false;
131 const StringRef ConditionText =
132 Lexer::getSourceText(*ConditionRange, SM, LangOpts, &Invalid);
133 return Invalid || ConditionText.empty()
135 : std::optional<std::string>(ConditionText.str());
139 const SourceManager &SM) {
140 return Loc.isValid() && Range.isValid() &&
141 !SM.isBeforeInTranslationUnit(Loc, Range.getBegin()) &&
142 SM.isBeforeInTranslationUnit(Loc, Range.getEnd());
149 const SourceManager &SM,
150 const LangOptions &LangOpts) {
153 const CharSourceRange HeaderFileRange = Lexer::makeFileCharRange(
154 CharSourceRange::getCharRange(Nested->getBeginLoc(),
155 Nested->getThen()->getBeginLoc()),
157 if (HeaderFileRange.isInvalid())
160 const std::optional<CharSourceRange> PayloadRange =
165 const std::vector<utils::lexer::CommentToken> Comments =
176 bool AllowUserDefinedBoolConversion) {
178 assert(If->hasVarStorage());
184 if (If->hasInitStorage())
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())
196 If->getCond(), AllowUserDefinedBoolConversion);
204 if (
const auto *NestedIf = dyn_cast<IfStmt>(Then))
207 const auto *
const Compound = dyn_cast<CompoundStmt>(Then);
208 if (!Compound || Compound->size() != 1)
211 return dyn_cast<IfStmt>(Compound->body_front());
215 bool RequireConstexpr,
bool AllowConditionVariable,
216 bool AllowUserDefinedBoolConversion,
217 const LangOptions &LangOpts) {
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;
226 const bool HasMergeableConditionVariable =
227 If->hasVarStorage() && AllowConditionVariable && LangOpts.CPlusPlus17 &&
229 const bool HasMergeableConditionExpression =
230 !If->hasVarStorage() && If->getCond() &&
232 AllowUserDefinedBoolConversion);
234 return IsMergeableStructure &&
235 (HasMergeableConditionVariable || HasMergeableConditionExpression);
243 const DynTypedNodeList Parents = Context.getParents(*If);
244 return !Parents.empty() && Parents[0].get<AttributedStmt>() !=
nullptr;
248 bool AllowUserDefinedBoolConversion) {
252 const LangOptions &LangOpts = Context.getLangOpts();
253 const bool RequireConstexpr = Root->isConstexpr();
257 AllowUserDefinedBoolConversion, LangOpts) ||
261 Chain.push_back(Root);
262 const IfStmt *Current = Root;
263 while (
const IfStmt *
const Nested =
getOnlyNestedIf(Current->getThen())) {
267 AllowUserDefinedBoolConversion, LangOpts) ||
271 Chain.push_back(Nested);
279 const ASTContext &Context,
280 bool RequiredValue) {
281 if (!Condition || Condition->isValueDependent() ||
282 Condition->isInstantiationDependent())
285 bool EvaluatedValue =
false;
286 return Condition->EvaluateAsBooleanCondition(EvaluatedValue, Context) &&
287 EvaluatedValue == RequiredValue;
297 Condition = Condition->IgnoreParenImpCasts();
298 if (isa<CXXBoolLiteralExpr, IntegerLiteral, RequiresExpr>(Condition))
301 if (
const auto *DeclRef = dyn_cast<DeclRefExpr>(Condition))
302 return isa<NonTypeTemplateParmDecl>(DeclRef->getDecl());
304 if (
const auto *Unary = dyn_cast<UnaryOperator>(Condition))
307 if (
const auto *Binary = dyn_cast<BinaryOperator>(Condition)) {
308 if (Binary->isAssignmentOp() || Binary->getOpcode() == BO_Comma)
320 const ASTContext &Context) {
321 if (Chain.empty() || !Chain.front()->isConstexpr())
324 bool AllPreviousConditionsAreConstantTrue =
true;
325 for (
const IfStmt *If : Chain) {
326 const Expr *
const Condition = If->getCond();
327 if (Condition->isInstantiationDependent() &&
328 !AllPreviousConditionsAreConstantTrue &&
332 AllPreviousConditionsAreConstantTrue =
333 AllPreviousConditionsAreConstantTrue &&
343 return Range.isInvalid() || Range.getBegin().isMacroID() ||
344 Range.getEnd().isMacroID();
348 const LangOptions &LangOpts) {
352 return Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range), SM,
359 const LangOptions &LangOpts) {
363 return Lexer::makeFileCharRange(Range, SM, LangOpts).isInvalid() ||
371 const SourceManager &SM,
372 const LangOptions &LangOpts) {
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()))
382 if (!RootConditionRange ||
385 if (!Root->hasVarStorage() &&
389 if (Root->hasVarStorage()) {
390 const DeclStmt *
const ConditionVariableDeclStmt =
391 Root->getConditionVariableDeclStmt();
392 if (!ConditionVariableDeclStmt ||
399 const llvm::ArrayRef<const IfStmt *> ChainRef(Chain);
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,
408 const CharSourceRange ChildHeaderRange = CharSourceRange::getCharRange(
409 Child->getBeginLoc(), Child->getThen()->getBeginLoc());
410 if (isUnsafeCharRange(ChildHeaderRange, SM, LangOpts))
413 const auto *const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
415 (!isUnsafeTokenRange(
416 SourceRange(Wrapper->getLBracLoc(), Wrapper->getLBracLoc()),
419 SourceRange(Wrapper->getRBracLoc(), Wrapper->getRBracLoc()),
427 return ConditionText.str();
429 std::string Result(
"static_cast<bool>(");
430 Result += ConditionText;
436 const ASTContext &Context,
437 bool UseConditionExprText) {
440 const SourceManager &SM = Context.getSourceManager();
441 const LangOptions &LangOpts = Context.getLangOpts();
443 std::optional<std::string> ConditionText;
444 if (UseConditionExprText) {
445 const StringRef ConditionExprText =
446 tooling::fixit::getText(*If->getCond(), Context);
447 if (ConditionExprText.empty())
449 ConditionText = ConditionExprText.str();
460static CombinedConditionBuildResult
462 const ASTContext &Context) {
466 const SourceManager &SM = Context.getSourceManager();
467 const LangOptions &LangOpts = Context.getLangOpts();
468 std::string CombinedCondition;
470 for (
const auto &[Index, If] : llvm::enumerate(Chain)) {
471 const bool IsRoot = Index == 0;
473 return {CombinedConditionBuildStatus::UnsupportedCommentPlacement, {}};
475 if (IsRoot && If->hasVarStorage()) {
476 const VarDecl *
const ConditionVariable = If->getConditionVariable();
477 if (!ConditionVariable)
480 const std::optional<std::string> ConditionText =
485 CombinedCondition = *ConditionText;
486 CombinedCondition +=
"; ";
492 const std::optional<std::string> ConjunctText =
495 If->hasInitStorage() && !If->hasVarStorage());
499 if (!CombinedCondition.empty())
500 CombinedCondition +=
" && ";
501 CombinedCondition +=
'(';
502 CombinedCondition += *ConjunctText;
503 CombinedCondition +=
')';
506 return {CombinedConditionBuildStatus::Success, std::move(CombinedCondition)};
509static std::optional<CharSourceRange>
511 const LangOptions &LangOpts) {
514 return If->hasInitStorage() && !If->hasVarStorage()
515 ? std::optional<CharSourceRange>(CharSourceRange::getTokenRange(
516 If->getCond()->getSourceRange()))
522 const SourceManager &SM,
const LangOptions &LangOpts,
523 std::optional<std::string> *CombinedCondition) {
525 return WarningType::None;
528 return WarningType::None;
530 const CombinedConditionBuildResult Combined =
532 if (Combined.Status ==
533 CombinedConditionBuildStatus::UnsupportedCommentPlacement)
534 return WarningType::WarnOnly;
535 if (Combined.Status != CombinedConditionBuildStatus::Success)
536 return WarningType::None;
538 if (CombinedCondition)
539 *CombinedCondition = Combined.Text;
540 return WarningType::WarnAndFix;
544 llvm::ArrayRef<const IfStmt *> Chain) {
545 for (
const IfStmt *Nested : llvm::drop_begin(Chain))
546 Check.diag(Nested->getIfLoc(),
NestedIfNote, DiagnosticIDs::Note);
549static void diagnoseChain(RedundantNestedIfCheck &Check,
const IfStmt *If,
551 bool AllowUserDefinedBoolConversion);
554 const Stmt *Branch, ASTContext &Context,
555 bool AllowUserDefinedBoolConversion) {
557 diagnoseChain(Check, Nested, Context, AllowUserDefinedBoolConversion);
565 bool AllowUserDefinedBoolConversion) {
566 const SourceManager &SM = Context.getSourceManager();
567 const LangOptions &LangOpts = Context.getLangOpts();
568 const IfChain Chain =
571 std::optional<std::string> CombinedCondition;
572 const WarningType Handling =
574 if (Handling == WarningType::None) {
576 AllowUserDefinedBoolConversion);
578 AllowUserDefinedBoolConversion);
583 const DiagnosticBuilder Diag = Check.diag(If->getIfLoc(),
MergeableIfDiag);
584 if (Handling == WarningType::WarnAndFix) {
585 const std::optional<CharSourceRange> ConditionRange =
587 if (!ConditionRange || !CombinedCondition)
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()));
597 const auto *
const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
601 Diag << FixItHint::CreateRemoval(Wrapper->getLBracLoc())
602 << FixItHint::CreateRemoval(Wrapper->getRBracLoc());
613 AllowUserDefinedBoolConversion(
618 AllowUserDefinedBoolConversion);
623 ifStmt(unless(hasElse(stmt())),
624 unless(anyOf(hasParent(ifStmt(unless(hasElse(stmt())))),
625 hasParent(compoundStmt(
627 hasParent(ifStmt(unless(hasElse(stmt())))))))))
633 const auto *
const If = Result.Nodes.getNodeAs<IfStmt>(
"if");
636 diagnoseChain(*
this, If, *Result.Context, AllowUserDefinedBoolConversion);
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