================
@@ -0,0 +1,646 @@
+//===--- 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/DeclTemplate.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 AllowUserDefinedBoolConversionStr =
+ "AllowUserDefinedBoolConversion";
+static constexpr llvm::StringLiteral MergeableIfDiag =
+ "nested if 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,
+ 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);
+
+ 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 (init; bool X = cond())` cannot generally become
+ // `if (init; bool X = cond(); X)`.
+ // Same-type declaration merging, like `if (bool Y = init(), X = cond(); X)`,
+ // is possible but too narrow to be worth supporting.
+ 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 isMergeCandidate(const IfStmt *If, bool AllowInitStorage,
+ bool RequireConstexpr, bool
AllowConditionVariable,
+ bool AllowUserDefinedBoolConversion,
+ const LangOptions &LangOpts) {
+ assert(If);
+
+ const bool HasAllowedInitStorage = AllowInitStorage || !If->hasInitStorage();
+ const bool HasRequiredConstexpr = If->isConstexpr() == RequireConstexpr;
+ const bool IsMergeableStructure = If->getThen() && !If->isConsteval() &&
+ !If->getElse() && HasAllowedInitStorage &&
+ HasRequiredConstexpr;
+
+ const bool HasMergeableConditionVariable =
+ If->hasVarStorage() && AllowConditionVariable && LangOpts.CPlusPlus17 &&
+ canRewriteOuterConditionVariable(If, AllowUserDefinedBoolConversion);
+ const bool HasMergeableConditionExpression =
+ !If->hasVarStorage() && If->getCond() &&
+ isConditionExpressionMergeable(If->getCond(),
+ AllowUserDefinedBoolConversion);
+
+ return IsMergeableStructure &&
+ (HasMergeableConditionVariable || HasMergeableConditionExpression);
+}
+
+// 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) {
----------------
vbvictor wrote:
```suggestion
static bool isAttributedIf(const IfStmt *If, const ASTContext &Context) {
```
https://github.com/llvm/llvm-project/pull/181558
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits