Author: Yanzuo Liu
Date: 2025-09-08T10:19:52+08:00
New Revision: 2ec6b3bceda275b4146bb229668e38797d6afe62

URL: 
https://github.com/llvm/llvm-project/commit/2ec6b3bceda275b4146bb229668e38797d6afe62
DIFF: 
https://github.com/llvm/llvm-project/commit/2ec6b3bceda275b4146bb229668e38797d6afe62.diff

LOG: [Clang-Tidy] Handle nested-name-specifier in 
"llvm-prefer-isa-or-dyn-cast-in-conditionals" (#155982)

Use `declRefExpr` matcher to match callee so that we can get the
`SourceRange` of the identifier of the callee for replacement.

Drive-by changes:

- Use `hasConditionVariableStatement` matcher to handle `if` statements
with init-statement.
- Support `for` loops.

Fixes #154790

Added: 
    

Modified: 
    clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp
    clang-tools-extra/docs/ReleaseNotes.rst
    
clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp 
b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp
index 4c138bcc564d8..cb289af46ea44 100644
--- 
a/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp
+++ 
b/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp
@@ -11,6 +11,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/Lex/Lexer.h"
+#include "llvm/Support/FormatVariadic.h"
 
 using namespace clang::ast_matchers;
 
@@ -22,105 +23,104 @@ AST_MATCHER(Expr, isMacroID) { return 
Node.getExprLoc().isMacroID(); }
 
 void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(
     MatchFinder *Finder) {
-  auto Condition = hasCondition(implicitCastExpr(has(
-      callExpr(unless(isMacroID()), unless(cxxMemberCallExpr()),
-               anyOf(callee(namedDecl(hasName("cast"))),
-                     callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast"))))
-          .bind("call"))));
-
-  auto Any = anyOf(
-      has(declStmt(containsDeclaration(
-          0, varDecl(hasInitializer(callExpr(unless(isMacroID()),
-                                             unless(cxxMemberCallExpr()),
-                                             
callee(namedDecl(hasName("cast"))))
-                                        .bind("assign")))))),
-      Condition);
-
-  auto CallExpression =
+  auto AnyCalleeName = [](ArrayRef<StringRef> CalleeName) {
+    return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+                 callee(expr(ignoringImpCasts(
+                     declRefExpr(to(namedDecl(hasAnyName(CalleeName))),
+                                 hasAnyTemplateArgumentLoc(anything()))
+                         .bind("callee")))));
+  };
+
+  auto CondExpr = hasCondition(implicitCastExpr(
+      has(callExpr(AnyCalleeName({"cast", "dyn_cast"})).bind("cond"))));
+
+  auto CondExprOrCondVar =
+      anyOf(hasConditionVariableStatement(containsDeclaration(
+                0, varDecl(hasInitializer(callExpr(AnyCalleeName({"cast"}))))
+                       .bind("var"))),
+            CondExpr);
+
+  auto CallWithBindedArg =
       callExpr(
-
-          unless(isMacroID()), unless(cxxMemberCallExpr()),
-          callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null", 
"dyn_cast",
-                                      "dyn_cast_or_null"))
-                     .bind("func")),
+          AnyCalleeName(
+              {"isa", "cast", "cast_or_null", "dyn_cast", "dyn_cast_or_null"}),
           hasArgument(0, mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))
           .bind("rhs");
 
   Finder->addMatcher(
-      traverse(
-          TK_AsIs,
-          stmt(anyOf(
-              ifStmt(Any), whileStmt(Any), doStmt(Condition),
-              binaryOperator(unless(isExpansionInFileMatching(
-                                 "llvm/include/llvm/Support/Casting.h")),
-                             hasOperatorName("&&"),
-                             hasLHS(implicitCastExpr().bind("lhs")),
-                             
hasRHS(anyOf(implicitCastExpr(has(CallExpression)),
-                                          CallExpression)))
-                  .bind("and")))),
+      stmt(anyOf(ifStmt(CondExprOrCondVar), forStmt(CondExprOrCondVar),
+                 whileStmt(CondExprOrCondVar), doStmt(CondExpr),
+                 binaryOperator(unless(isExpansionInFileMatching(
+                                    "llvm/include/llvm/Support/Casting.h")),
+                                hasOperatorName("&&"),
+                                hasLHS(implicitCastExpr().bind("lhs")),
+                                hasRHS(ignoringImpCasts(CallWithBindedArg)))
+                     .bind("and"))),
       this);
 }
 
 void PreferIsaOrDynCastInConditionalsCheck::check(
     const MatchFinder::MatchResult &Result) {
-  if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) {
-    SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
-    SourceLocation EndLoc =
-        StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
+  const auto *Callee = Result.Nodes.getNodeAs<DeclRefExpr>("callee");
+
+  assert(Callee && "Callee should be binded if anything is matched");
+
+  // The first and last letter of the identifier
+  //   llvm::cast<T>(x)
+  //         ^  ^
+  //  StartLoc  EndLoc
+  SourceLocation StartLoc = Callee->getLocation();
+  SourceLocation EndLoc = Callee->getNameInfo().getEndLoc();
 
-    diag(MatchedDecl->getBeginLoc(),
+  if (Result.Nodes.getNodeAs<VarDecl>("var")) {
+    diag(StartLoc,
          "cast<> in conditional will assert rather than return a null pointer")
         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
                                         "dyn_cast");
-  } else if (const auto *MatchedDecl =
-                 Result.Nodes.getNodeAs<CallExpr>("call")) {
-    SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
-    SourceLocation EndLoc =
-        StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
-
+  } else if (Result.Nodes.getNodeAs<CallExpr>("cond")) {
     StringRef Message =
         "cast<> in conditional will assert rather than return a null pointer";
-    if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast"))
+    if (Callee->getDecl()->getName() == "dyn_cast")
       Message = "return value from dyn_cast<> not used";
 
-    diag(MatchedDecl->getBeginLoc(), Message)
+    diag(StartLoc, Message)
         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
-  } else if (const auto *MatchedDecl =
-                 Result.Nodes.getNodeAs<BinaryOperator>("and")) {
+  } else if (Result.Nodes.getNodeAs<BinaryOperator>("and")) {
     const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
     const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
     const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
-    const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func");
 
     assert(LHS && "LHS is null");
     assert(RHS && "RHS is null");
     assert(Arg && "Arg is null");
-    assert(Func && "Func is null");
 
-    StringRef LHSString(Lexer::getSourceText(
-        CharSourceRange::getTokenRange(LHS->getSourceRange()),
-        *Result.SourceManager, getLangOpts()));
+    auto GetText = [&](SourceRange R) {
+      return Lexer::getSourceText(CharSourceRange::getTokenRange(R),
+                                  *Result.SourceManager, getLangOpts());
+    };
 
-    StringRef ArgString(Lexer::getSourceText(
-        CharSourceRange::getTokenRange(Arg->getSourceRange()),
-        *Result.SourceManager, getLangOpts()));
+    const StringRef LHSString = GetText(LHS->getSourceRange());
+    const StringRef ArgString = GetText(Arg->getSourceRange());
 
     if (ArgString != LHSString)
       return;
 
-    StringRef RHSString(Lexer::getSourceText(
-        CharSourceRange::getTokenRange(RHS->getSourceRange()),
-        *Result.SourceManager, getLangOpts()));
-
-    std::string Replacement("isa_and_nonnull");
-    Replacement += RHSString.substr(Func->getName().size());
+    // It is not clear which is preferred between `isa_and_nonnull` and
+    // `isa_and_present`. See
+    // 
https://discourse.llvm.org/t/psa-swapping-out-or-null-with-if-present/65018
+    const std::string Replacement = llvm::formatv(
+        "{}isa_and_nonnull{}",
+        GetText(Callee->getQualifierLoc().getSourceRange()),
+        GetText(SourceRange(Callee->getLAngleLoc(), RHS->getEndLoc())));
 
-    diag(MatchedDecl->getBeginLoc(),
+    diag(LHS->getBeginLoc(),
          "isa_and_nonnull<> is preferred over an explicit test for null "
          "followed by calling isa<>")
-        << FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(),
-                                                    MatchedDecl->getEndLoc()),
-                                        Replacement);
+        << FixItHint::CreateReplacement(
+               SourceRange(LHS->getBeginLoc(), RHS->getEndLoc()), Replacement);
+  } else {
+    llvm_unreachable(
+        R"(One of "var", "cond" and "and" should be binded if anything is 
matched)");
   }
 }
 

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 62916906f96fe..bd757851abe02 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -215,6 +215,16 @@ Changes in existing checks
   adding an option to allow pointer arithmetic via prefix/postfix increment or
   decrement operators.
 
+- Improved :doc:`llvm-prefer-isa-or-dyn-cast-in-conditionals
+  <clang-tidy/checks/llvm/prefer-isa-or-dyn-cast-in-conditionals>` check:
+
+  - Fix-it handles callees with nested-name-specifier correctly.
+
+  - ``if`` statements with init-statement (``if (auto X = ...; ...)``) are
+    handled correctly.
+
+  - ``for`` loops are supported.
+
 - Improved :doc:`misc-header-include-cycle
   <clang-tidy/checks/misc/header-include-cycle>` check performance.
 

diff  --git 
a/clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp
index 88e4b643004fc..6b4c917f6c599 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp
@@ -9,14 +9,20 @@ struct Z {
   bool baz(Y*);
 };
 
+namespace llvm {
 template <class X, class Y>
 bool isa(Y *);
 template <class X, class Y>
 X *cast(Y *);
 template <class X, class Y>
+X *cast_or_null(Y *);
+template <class X, class Y>
 X *dyn_cast(Y *);
 template <class X, class Y>
 X *dyn_cast_or_null(Y *);
+} // namespace llvm
+
+using namespace llvm;
 
 bool foo(Y *y, Z *z) {
   if (auto x = cast<X>(y))
@@ -24,6 +30,26 @@ bool foo(Y *y, Z *z) {
   // CHECK-MESSAGES: :[[@LINE-2]]:16: warning: cast<> in conditional will 
assert rather than return a null pointer 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
   // CHECK-FIXES: if (auto x = dyn_cast<X>(y))
 
+  if (auto x = ::cast<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:18: warning: cast<> in conditional will 
assert rather than return a null pointer 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (auto x = ::dyn_cast<X>(y))
+
+  if (auto x = llvm::cast<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:22: warning: cast<> in conditional will 
assert rather than return a null pointer 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (auto x = llvm::dyn_cast<X>(y))
+
+  if (auto x = ::llvm::cast<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:24: warning: cast<> in conditional will 
assert rather than return a null pointer 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (auto x = ::llvm::dyn_cast<X>(y))
+
+  for (; auto x = cast<X>(y); )
+    break;
+  // CHECK-MESSAGES: :[[@LINE-2]]:19: warning: cast<> in conditional
+  // CHECK-FIXES: for (; auto x = dyn_cast<X>(y); )
+
   while (auto x = cast<X>(y))
     break;
   // CHECK-MESSAGES: :[[@LINE-2]]:19: warning: cast<> in conditional
@@ -34,6 +60,16 @@ bool foo(Y *y, Z *z) {
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: cast<> in conditional
   // CHECK-FIXES: if (isa<X>(y))
 
+  if (auto x = cast<X>(y); cast<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:28: warning: cast<> in conditional will 
assert rather than return a null pointer 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (auto x = cast<X>(y); isa<X>(y))
+
+  for (; cast<X>(y); )
+    break;
+  // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: cast<> in conditional
+  // CHECK-FIXES: for (; isa<X>(y); )
+
   while (cast<X>(y))
     break;
   // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: cast<> in conditional
@@ -50,6 +86,11 @@ bool foo(Y *y, Z *z) {
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: return value from dyn_cast<> not 
used [llvm-prefer-isa-or-dyn-cast-in-conditionals]
   // CHECK-FIXES: if (isa<X>(y))
 
+  for (; dyn_cast<X>(y); )
+    break;
+  // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: return value from dyn_cast<> 
not used
+  // CHECK-FIXES: for (; isa<X>(y); )
+
   while (dyn_cast<X>(y))
     break;
   // CHECK-MESSAGES: :[[@LINE-2]]:10: warning: return value from dyn_cast<> 
not used
@@ -66,6 +107,21 @@ bool foo(Y *y, Z *z) {
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred 
over an explicit test for null followed by calling isa<> 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
   // CHECK-FIXES: if (isa_and_nonnull<X>(y))
 
+  if (y && ::isa<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred 
over an explicit test for null followed by calling isa<> 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (::isa_and_nonnull<X>(y))
+
+  if (y && llvm::isa<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred 
over an explicit test for null followed by calling isa<> 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (llvm::isa_and_nonnull<X>(y))
+
+  if (y && ::llvm::isa<X>(y))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred 
over an explicit test for null followed by calling isa<> 
[llvm-prefer-isa-or-dyn-cast-in-conditionals]
+  // CHECK-FIXES: if (::llvm::isa_and_nonnull<X>(y))
+
   if (z->bar() && isa<Y>(z->bar()))
     return true;
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning:  isa_and_nonnull<> is preferred
@@ -76,6 +132,11 @@ bool foo(Y *y, Z *z) {
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred
   // CHECK-FIXES: if (isa_and_nonnull<Y>(z->bar()))
 
+  if (z->bar() && cast_or_null<Y>(z->bar()))
+    return true;
+  // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred
+  // CHECK-FIXES: if (isa_and_nonnull<Y>(z->bar()))
+
   if (z->bar() && dyn_cast<Y>(z->bar()))
     return true;
   // CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to