[clang-tools-extra] [clang-tidy] Add `readability-redundant-nested-if` check (PR #181558)
Baranov Victor via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 11 03:17:13 PDT 2026
================
@@ -0,0 +1,641 @@
+//===--- RedundantNestedIfCheck.cpp - clang-tidy -------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "RedundantNestedIfCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Stmt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include <cassert>
+#include <optional>
+#include <string>
+#include <vector>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+static constexpr llvm::StringLiteral WarnOnDependentConstexprIfStr =
+ "WarnOnDependentConstexprIf";
+static constexpr llvm::StringLiteral AllowUserDefinedBoolConversionStr =
+ "AllowUserDefinedBoolConversion";
+static constexpr llvm::StringLiteral MergeableIfDiag =
+ "nested if statements can be merged";
+static constexpr llvm::StringLiteral DependentConstexprDiag =
+ "nested instantiation-dependent if constexpr statements can be merged";
+static constexpr llvm::StringLiteral NestedIfNote =
+ "nested if statement to merge is here";
+
+namespace {
+
+using IfChain = llvm::SmallVector<const IfStmt *>;
+
+enum class ChainHandling {
+ None,
+ WarnOnly,
+ WarnOnlyDependentConstexpr,
+ WarnAndFix,
+};
+
+enum class CombinedConditionBuildStatus {
+ Success,
+ UnsupportedCommentPlacement,
+ Failure,
+};
+
+struct CombinedConditionBuildResult {
+ CombinedConditionBuildStatus Status = CombinedConditionBuildStatus::Failure;
+ std::string Text;
+};
+
+} // namespace
+
+// Conjoining conditions with `&&` can change behavior when a condition relies
+// on contextual user-defined conversion to `bool`.
+static bool containsUserDefinedBoolConversion(const Expr *Expression) {
+ assert(Expression);
+
+ if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Expression);
+ Cast && Cast->getCastKind() == CK_UserDefinedConversion)
+ return true;
+
+ return llvm::any_of(Expression->children(), [](const Stmt *Child) {
+ const auto *ChildExpr = dyn_cast_or_null<Expr>(Child);
+ return ChildExpr && containsUserDefinedBoolConversion(ChildExpr);
+ });
+}
+
+static bool conditionNeedsBoolCast(const Expr *Condition) {
+ assert(Condition);
+
+ if (!containsUserDefinedBoolConversion(Condition))
+ return false;
+
+ const Expr *const Unwrapped =
+ Condition->IgnoreImplicitAsWritten()->IgnoreParens();
+ if (!Unwrapped)
+ return true;
+
+ const QualType ConditionType = Unwrapped->getType();
+ return ConditionType.isNull() || !ConditionType->isScalarType();
+}
+
+static bool
+isConditionExpressionMergeable(const Expr *Condition,
+ bool AllowUserDefinedBoolConversion) {
+ assert(Condition);
+
+ if (Condition->isTypeDependent())
+ return false;
+
+ if (containsUserDefinedBoolConversion(Condition))
+ return AllowUserDefinedBoolConversion;
+
+ const Expr *const Unwrapped = Condition->IgnoreParenImpCasts();
+ if (!Unwrapped)
+ return false;
+
+ const QualType ConditionType = Unwrapped->getType();
+ return !ConditionType.isNull() && ConditionType->isScalarType();
+}
+
+static std::optional<CharSourceRange>
+getIfConditionRange(const IfStmt *If, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ assert(If);
+
+ const SourceLocation ConditionBegin =
+ Lexer::getLocForEndOfToken(If->getLParenLoc(), 0, SM, LangOpts);
+ if (ConditionBegin.isInvalid() || If->getRParenLoc().isInvalid())
+ return std::nullopt;
+
+ const CharSourceRange ConditionRange =
+ CharSourceRange::getCharRange(ConditionBegin, If->getRParenLoc());
+ const CharSourceRange FileRange =
+ Lexer::makeFileCharRange(ConditionRange, SM, LangOpts);
+ if (FileRange.isInvalid())
+ return std::nullopt;
+
+ return FileRange;
+}
+
+static std::optional<std::string>
+getIfConditionText(const IfStmt *If, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ const std::optional<CharSourceRange> ConditionRange =
+ getIfConditionRange(If, SM, LangOpts);
+ if (!ConditionRange)
+ return std::nullopt;
+
+ bool Invalid = false;
+ const StringRef ConditionText =
+ Lexer::getSourceText(*ConditionRange, SM, LangOpts, &Invalid);
+ return Invalid || ConditionText.empty()
+ ? std::nullopt
+ : std::optional<std::string>(ConditionText.str());
+}
+
+static bool isLocationInCharRange(SourceLocation Loc, CharSourceRange Range,
+ const SourceManager &SM) {
+ return Loc.isValid() && Range.isValid() &&
+ !SM.isBeforeInTranslationUnit(Loc, Range.getBegin()) &&
+ SM.isBeforeInTranslationUnit(Loc, Range.getEnd());
+}
+
+// Keep fix-its only when comments in removed nested headers stay inside the
+// preserved condition text. Other comment placements keep the diagnostic but
+// suppress the rewrite.
+static bool hasOnlyPayloadCommentsInNestedHeader(const IfStmt *Nested,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ assert(Nested);
+ assert(Nested->getThen());
+
+ const CharSourceRange HeaderRange = CharSourceRange::getCharRange(
+ Nested->getBeginLoc(), Nested->getThen()->getBeginLoc());
+ const CharSourceRange HeaderFileRange =
+ Lexer::makeFileCharRange(HeaderRange, SM, LangOpts);
+ if (HeaderFileRange.isInvalid())
+ return false;
+
+ const std::optional<CharSourceRange> PayloadRange =
+ getIfConditionRange(Nested, SM, LangOpts);
+ if (!PayloadRange)
+ return false;
+
+ const std::vector<utils::lexer::CommentToken> Comments =
+ utils::lexer::getCommentsInRange(HeaderFileRange, SM, LangOpts);
+ return llvm::all_of(Comments, [&](const utils::lexer::CommentToken &Comment) {
+ return isLocationInCharRange(Comment.Loc, *PayloadRange, SM);
+ });
+}
+
+// Only an outer condition variable can be rewritten safely by moving the
+// declaration into an init-statement and conjoining the condition variable.
+static bool
+canRewriteOuterConditionVariable(const IfStmt *If,
+ bool AllowUserDefinedBoolConversion) {
+ assert(If);
+ assert(If->hasVarStorage());
+
+ if (If->hasInitStorage())
+ return false;
+
+ const auto *const ConditionVariable = If->getConditionVariable();
+ const auto *const ConditionVariableDeclStmt =
+ If->getConditionVariableDeclStmt();
+ if (!ConditionVariable || !ConditionVariableDeclStmt ||
+ !ConditionVariableDeclStmt->isSingleDecl() ||
+ ConditionVariable->getName().empty())
+ return false;
+
+ return If->getCond() && isConditionExpressionMergeable(
+ If->getCond(), AllowUserDefinedBoolConversion);
+}
+
+// Accept either `if (...) if (...)` or `if (...) { if (...) }` where the
+// compound contains exactly one statement.
+static const IfStmt *getOnlyNestedIf(const Stmt *Then) {
+ if (!Then)
+ return nullptr;
+ if (const auto *NestedIf = dyn_cast<IfStmt>(Then))
+ return NestedIf;
+
+ const auto *const Compound = dyn_cast<CompoundStmt>(Then);
+ if (!Compound || Compound->size() != 1)
+ return nullptr;
+
+ return dyn_cast<IfStmt>(Compound->body_front());
+}
+
+static bool hasMergeableStructure(const IfStmt *If, bool AllowInitStorage,
+ bool RequireConstexpr) {
+ assert(If);
+
+ return If->getThen() && !If->isConsteval() && !If->getElse() &&
+ (AllowInitStorage || !If->hasInitStorage()) &&
+ If->isConstexpr() == RequireConstexpr;
+}
+
+static bool isMergeCandidate(const IfStmt *If, bool AllowInitStorage,
+ bool RequireConstexpr, bool AllowConditionVariable,
+ bool AllowUserDefinedBoolConversion,
+ const LangOptions &LangOpts) {
+ assert(If);
+
+ if (!hasMergeableStructure(If, AllowInitStorage, RequireConstexpr))
+ return false;
+
+ if (If->hasVarStorage())
+ return AllowConditionVariable && LangOpts.CPlusPlus17 &&
+ canRewriteOuterConditionVariable(If, AllowUserDefinedBoolConversion);
+
+ return If->getCond() && isConditionExpressionMergeable(
+ If->getCond(), AllowUserDefinedBoolConversion);
+}
+
+// Statement attributes wrap the `if` in an `AttributedStmt`, so removing nested
+// `if` tokens can invalidate attribute placement.
+static bool isAttributedIf(const IfStmt *If, ASTContext &Context) {
+ assert(If);
+
+ const DynTypedNodeList Parents = Context.getParents(*If);
+ return !Parents.empty() && Parents[0].get<AttributedStmt>() != nullptr;
+}
+
+static IfChain getMergeChain(const IfStmt *Root, ASTContext &Context,
+ bool AllowUserDefinedBoolConversion) {
+ assert(Root);
+
+ IfChain Chain;
+ const LangOptions &LangOpts = Context.getLangOpts();
+ const bool RequireConstexpr = Root->isConstexpr();
+ if (!isMergeCandidate(Root, /*AllowInitStorage=*/true,
+ /*RequireConstexpr=*/RequireConstexpr,
+ /*AllowConditionVariable=*/true,
+ AllowUserDefinedBoolConversion, LangOpts) ||
+ isAttributedIf(Root, Context)) {
+ return Chain;
+ }
+
+ Chain.push_back(Root);
+ const IfStmt *Current = Root;
+ while (const IfStmt *const Nested = getOnlyNestedIf(Current->getThen())) {
+ if (!isMergeCandidate(Nested, /*AllowInitStorage=*/false,
+ /*RequireConstexpr=*/RequireConstexpr,
+ /*AllowConditionVariable=*/false,
+ AllowUserDefinedBoolConversion, LangOpts) ||
+ isAttributedIf(Nested, Context)) {
+ break;
+ }
+
+ Chain.push_back(Nested);
+ Current = Nested;
+ }
+
+ return Chain;
+}
+
+static bool isConstantBooleanCondition(const Expr *Condition,
+ const ASTContext &Context,
+ bool RequiredValue) {
+ if (!Condition || Condition->isValueDependent() ||
+ Condition->isInstantiationDependent())
+ return false;
+
+ bool EvaluatedValue = false;
+ return Condition->EvaluateAsBooleanCondition(EvaluatedValue, Context) &&
+ EvaluatedValue == RequiredValue;
+}
+
+static bool
+isConstexprChainSemanticallySafe(llvm::ArrayRef<const IfStmt *> Chain,
+ const ASTContext &Context) {
+ if (Chain.empty() || !Chain.front()->isConstexpr())
+ return true;
+
+ const bool OuterIsDependent =
+ Chain.front()->getCond()->isInstantiationDependent();
+
+ // Allow outer instantiation-dependence only when every nested condition is a
+ // non-dependent constant `true` expression. This preserves discarded-branch
+ // behavior for template-dependent `if constexpr`.
+ return llvm::all_of(llvm::drop_begin(Chain), [&](const IfStmt *Nested) {
+ const Expr *const NestedCondition = Nested->getCond();
+ if (NestedCondition->isInstantiationDependent())
+ return false;
+ return !OuterIsDependent ||
+ isConstantBooleanCondition(NestedCondition, Context,
+ /*RequiredValue=*/true);
+ });
+}
+
+// A range is unsafe for text edits if it crosses macro expansions or
+// preprocessor directives.
+template <typename RangeT>
+static bool isUnsafeRange(RangeT Range, CharSourceRange FileRange,
+ bool ContainsExpansionsOrDirectives) {
+ return Range.isInvalid() || Range.getBegin().isMacroID() ||
+ Range.getEnd().isMacroID() || FileRange.isInvalid() ||
+ ContainsExpansionsOrDirectives;
+}
+
+static bool isUnsafeTokenRange(SourceRange Range, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ return isUnsafeRange(
+ Range,
+ Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Range), SM,
+ LangOpts),
+ utils::lexer::rangeContainsExpansionsOrDirectives(Range, SM, LangOpts));
+}
+
+static bool isUnsafeCharRange(CharSourceRange Range, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ return isUnsafeRange(Range, Lexer::makeFileCharRange(Range, SM, LangOpts),
+ utils::lexer::rangeContainsExpansionsOrDirectives(
+ Range.getAsRange(), SM, LangOpts));
+}
+
+// Validate every range that contributes to the final edit set before offering
+// fix-its. If any range is unsafe, keep looking for a diagnosable child chain.
+static bool isFixitSafeForChain(llvm::ArrayRef<const IfStmt *> Chain,
+ const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ if (Chain.empty())
+ return false;
+
+ const IfStmt *const Root = Chain.front();
+ const std::optional<CharSourceRange> RootConditionRange =
+ Root->hasInitStorage() && !Root->hasVarStorage()
+ ? std::optional<CharSourceRange>(CharSourceRange::getTokenRange(
+ Root->getCond()->getSourceRange()))
+ : getIfConditionRange(Root, SM, LangOpts);
+ if (!RootConditionRange ||
+ isUnsafeCharRange(*RootConditionRange, SM, LangOpts))
+ return false;
+ if (!Root->hasVarStorage() &&
+ isUnsafeTokenRange(Root->getCond()->getSourceRange(), SM, LangOpts))
+ return false;
+
+ if (Root->hasVarStorage()) {
+ const auto *const ConditionVariableDeclStmt =
+ Root->getConditionVariableDeclStmt();
+ if (!ConditionVariableDeclStmt ||
+ isUnsafeTokenRange(ConditionVariableDeclStmt->getSourceRange(), SM,
+ LangOpts)) {
+ return false;
+ }
+ }
+
+ const llvm::ArrayRef<const IfStmt *> ChainRef(Chain);
+ return llvm::all_of(
+ llvm::zip(ChainRef.drop_back(), ChainRef.drop_front()),
+ [&](const auto &ParentAndChild) {
+ const auto &[Parent, Child] = ParentAndChild;
+ if (isUnsafeTokenRange(Child->getCond()->getSourceRange(), SM,
+ LangOpts))
+ return false;
+
+ const CharSourceRange ChildHeaderRange = CharSourceRange::getCharRange(
+ Child->getBeginLoc(), Child->getThen()->getBeginLoc());
+ if (isUnsafeCharRange(ChildHeaderRange, SM, LangOpts))
+ return false;
+
+ const auto *const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
+ return !Wrapper ||
+ (!isUnsafeTokenRange(
+ SourceRange(Wrapper->getLBracLoc(), Wrapper->getLBracLoc()),
+ SM, LangOpts) &&
+ !isUnsafeTokenRange(
+ SourceRange(Wrapper->getRBracLoc(), Wrapper->getRBracLoc()),
+ SM, LangOpts));
+ });
+}
+
+static std::string wrapConditionText(StringRef ConditionText,
+ bool NeedBoolCast) {
+ if (!NeedBoolCast)
+ return ConditionText.str();
+
+ std::string Result("static_cast<bool>(");
+ Result += ConditionText;
+ Result += ")";
+ return Result;
+}
+
+static std::optional<std::string> getConjunctText(const IfStmt *If,
+ const ASTContext &Context,
+ bool UseConditionExprText) {
+ assert(If);
+
+ const SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+
+ std::optional<std::string> ConditionText;
+ if (UseConditionExprText) {
+ const StringRef ConditionExprText =
+ tooling::fixit::getText(*If->getCond(), Context);
+ if (ConditionExprText.empty())
+ return std::nullopt;
+ ConditionText = ConditionExprText.str();
+ } else {
+ ConditionText = getIfConditionText(If, SM, LangOpts);
+ if (!ConditionText)
+ return std::nullopt;
+ }
+
+ return wrapConditionText(*ConditionText,
+ conditionNeedsBoolCast(If->getCond()));
+}
+
+static CombinedConditionBuildResult
+buildCombinedCondition(llvm::ArrayRef<const IfStmt *> Chain,
+ const ASTContext &Context) {
+ if (Chain.empty())
+ return {};
+
+ const SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+ std::string CombinedCondition;
+
+ for (const auto &[Index, If] : llvm::enumerate(Chain)) {
+ const bool IsRoot = Index == 0;
+ if (!IsRoot && !hasOnlyPayloadCommentsInNestedHeader(If, SM, LangOpts))
+ return {CombinedConditionBuildStatus::UnsupportedCommentPlacement, {}};
+
+ if (IsRoot && If->hasVarStorage()) {
+ const auto *const ConditionVariable = If->getConditionVariable();
+ if (!ConditionVariable)
+ return {};
+
+ const std::optional<std::string> ConditionText =
+ getIfConditionText(If, SM, LangOpts);
+ if (!ConditionText)
+ return {};
+
+ CombinedCondition = *ConditionText;
+ CombinedCondition += "; ";
+ CombinedCondition += wrapConditionText(
+ ConditionVariable->getName(), conditionNeedsBoolCast(If->getCond()));
+ continue;
+ }
+
+ const std::optional<std::string> ConjunctText =
+ getConjunctText(If, Context,
+ /*UseConditionExprText=*/IsRoot &&
+ If->hasInitStorage() && !If->hasVarStorage());
+ if (!ConjunctText)
+ return {};
+
+ if (!CombinedCondition.empty())
+ CombinedCondition += " && ";
+ CombinedCondition += '(';
+ CombinedCondition += *ConjunctText;
+ CombinedCondition += ')';
+ }
+
+ return {CombinedConditionBuildStatus::Success, std::move(CombinedCondition)};
+}
+
+static std::optional<CharSourceRange>
+getConditionReplacementRange(const IfStmt *If, const SourceManager &SM,
+ const LangOptions &LangOpts) {
+ assert(If);
+
+ return If->hasInitStorage() && !If->hasVarStorage()
+ ? std::optional<CharSourceRange>(CharSourceRange::getTokenRange(
+ If->getCond()->getSourceRange()))
+ : getIfConditionRange(If, SM, LangOpts);
+}
+
+static ChainHandling
+getChainHandling(llvm::ArrayRef<const IfStmt *> Chain,
+ const ASTContext &Context, const SourceManager &SM,
+ const LangOptions &LangOpts, bool WarnOnDependentConstexprIf,
+ std::optional<std::string> *CombinedCondition) {
+ if (Chain.size() < 2 || !isFixitSafeForChain(Chain, SM, LangOpts))
+ return ChainHandling::None;
+
+ if (!isConstexprChainSemanticallySafe(Chain, Context)) {
+ return WarnOnDependentConstexprIf
+ ? ChainHandling::WarnOnlyDependentConstexpr
+ : ChainHandling::None;
+ }
+
+ const CombinedConditionBuildResult Combined =
+ buildCombinedCondition(Chain, Context);
+ if (Combined.Status ==
+ CombinedConditionBuildStatus::UnsupportedCommentPlacement)
+ return ChainHandling::WarnOnly;
+ if (Combined.Status != CombinedConditionBuildStatus::Success)
+ return ChainHandling::None;
+
+ if (CombinedCondition)
+ *CombinedCondition = Combined.Text;
+ return ChainHandling::WarnAndFix;
+}
+
+static void emitNestedIfNotes(RedundantNestedIfCheck &Check,
+ llvm::ArrayRef<const IfStmt *> Chain) {
+ for (const IfStmt *Nested : llvm::drop_begin(Chain))
+ Check.diag(Nested->getIfLoc(), NestedIfNote, DiagnosticIDs::Note);
+}
+
+static void diagnoseChain(RedundantNestedIfCheck &Check, const IfStmt *If,
+ ASTContext &Context, bool WarnOnDependentConstexprIf,
+ bool AllowUserDefinedBoolConversion);
+
+static void diagnoseChildChain(RedundantNestedIfCheck &Check,
+ const Stmt *Branch, ASTContext &Context,
+ bool WarnOnDependentConstexprIf,
+ bool AllowUserDefinedBoolConversion) {
+ if (const IfStmt *const Nested = getOnlyNestedIf(Branch))
+ diagnoseChain(Check, Nested, Context, WarnOnDependentConstexprIf,
+ AllowUserDefinedBoolConversion);
+}
+
+// Match only syntactic chain roots. If a root cannot be diagnosed because it is
+// unsafe to rewrite, descend into excluded single-child nested `if` statements
+// in both branches and try again there.
+static void diagnoseChain(RedundantNestedIfCheck &Check, const IfStmt *If,
+ ASTContext &Context, bool WarnOnDependentConstexprIf,
+ bool AllowUserDefinedBoolConversion) {
+ assert(If);
+
+ const SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+ const IfChain Chain =
+ getMergeChain(If, Context, AllowUserDefinedBoolConversion);
+
+ std::optional<std::string> CombinedCondition;
+ const ChainHandling Handling =
+ getChainHandling(Chain, Context, SM, LangOpts, WarnOnDependentConstexprIf,
+ &CombinedCondition);
+ if (Handling == ChainHandling::None) {
+ diagnoseChildChain(Check, If->getThen(), Context,
+ WarnOnDependentConstexprIf,
+ AllowUserDefinedBoolConversion);
+ diagnoseChildChain(Check, If->getElse(), Context,
+ WarnOnDependentConstexprIf,
+ AllowUserDefinedBoolConversion);
+ return;
+ }
+
+ if (Handling == ChainHandling::WarnOnlyDependentConstexpr) {
+ Check.diag(If->getIfLoc(), DependentConstexprDiag);
+ emitNestedIfNotes(Check, Chain);
+ return;
+ }
+
+ {
+ const DiagnosticBuilder Diag = Check.diag(If->getIfLoc(), MergeableIfDiag);
+ if (Handling == ChainHandling::WarnAndFix) {
+ const std::optional<CharSourceRange> ConditionRange =
+ getConditionReplacementRange(If, SM, LangOpts);
+ if (!ConditionRange || !CombinedCondition)
+ return;
+
+ Diag << FixItHint::CreateReplacement(*ConditionRange, *CombinedCondition);
+ const llvm::ArrayRef<const IfStmt *> ChainRef(Chain);
+ for (const auto &[Parent, Child] :
+ llvm::zip(ChainRef.drop_back(), ChainRef.drop_front())) {
+ Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+ Child->getBeginLoc(), Child->getThen()->getBeginLoc()));
+
+ const auto *const Wrapper = dyn_cast<CompoundStmt>(Parent->getThen());
+ if (!Wrapper)
+ continue;
+
+ Diag << FixItHint::CreateRemoval(Wrapper->getLBracLoc())
+ << FixItHint::CreateRemoval(Wrapper->getRBracLoc());
+ }
+ }
+ }
+
+ emitNestedIfNotes(Check, Chain);
+}
+
+RedundantNestedIfCheck::RedundantNestedIfCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context), WarnOnDependentConstexprIf(Options.get(
+ WarnOnDependentConstexprIfStr, false)),
+ AllowUserDefinedBoolConversion(
+ Options.get(AllowUserDefinedBoolConversionStr, false)) {}
+
+void RedundantNestedIfCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, WarnOnDependentConstexprIfStr,
+ WarnOnDependentConstexprIf);
+ Options.store(Opts, AllowUserDefinedBoolConversionStr,
+ AllowUserDefinedBoolConversion);
+}
+
+void RedundantNestedIfCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ ifStmt(unless(anyOf(hasParent(ifStmt()),
----------------
vbvictor wrote:
Can we also write unless(hasElse())?
https://github.com/llvm/llvm-project/pull/181558
More information about the cfe-commits
mailing list