[clang-tools-extra] [clang-tidy] detect redundant uses of LLVM's cast, dyn_cast (PR #189274)

Henrik G. Olsson via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 29 17:25:55 PDT 2026


https://github.com/hnrklssn updated https://github.com/llvm/llvm-project/pull/189274

>From 3c91a0e1744ce531af38404ffa7c20409937d79b Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 10:39:21 -0700
Subject: [PATCH 01/11] [clang-tidy] detect redundant uses of LLVM's cast,
 dyn_cast

Warns when casting to the same pointee type, or when the target
pointee type is a super type of the argument's pointee type.
Supported functions:
 - cast
 - cast_if_present
 - cast_or_null
 - dyn_cast
 - dyn_cast_if_present
 - dyn_cast_or_null
---
 .../clang-tidy/llvm/CMakeLists.txt            |   1 +
 .../clang-tidy/llvm/LLVMTidyModule.cpp        |   3 +
 .../clang-tidy/llvm/RedundantCastingCheck.cpp | 131 ++++++++++++++
 .../clang-tidy/llvm/RedundantCastingCheck.h   |  33 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   5 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/llvm/redundant-casting.rst         |  24 +++
 .../checkers/llvm/redundant-casting.cpp       | 163 ++++++++++++++++++
 8 files changed, 361 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index a807f0ab65f87..c81882e0e2024 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_library(clangTidyLLVMModule STATIC
   PreferIsaOrDynCastInConditionalsCheck.cpp
   PreferRegisterOverUnsignedCheck.cpp
   PreferStaticOverAnonymousNamespaceCheck.cpp
+  RedundantCastingCheck.cpp
   TwineLocalCheck.cpp
   TypeSwitchCaseTypesCheck.cpp
   UseNewMLIROpBuilderCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
index c180574bdeed6..104fcf63712f7 100644
--- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
@@ -16,6 +16,7 @@
 #include "PreferIsaOrDynCastInConditionalsCheck.h"
 #include "PreferRegisterOverUnsignedCheck.h"
 #include "PreferStaticOverAnonymousNamespaceCheck.h"
+#include "RedundantCastingCheck.h"
 #include "TwineLocalCheck.h"
 #include "TypeSwitchCaseTypesCheck.h"
 #include "UseNewMLIROpBuilderCheck.h"
@@ -43,6 +44,8 @@ class LLVMModule : public ClangTidyModule {
         "llvm-prefer-static-over-anonymous-namespace");
     CheckFactories.registerCheck<readability::QualifiedAutoCheck>(
         "llvm-qualified-auto");
+    CheckFactories.registerCheck<RedundantCastingCheck>(
+        "llvm-redundant-casting");
     CheckFactories.registerCheck<TwineLocalCheck>("llvm-twine-local");
     CheckFactories.registerCheck<TypeSwitchCaseTypesCheck>(
         "llvm-type-switch-case-types");
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
new file mode 100644
index 0000000000000..a29701cc012b6
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -0,0 +1,131 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "RedundantCastingCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/TemplateBase.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); }
+AST_MATCHER_P(OverloadExpr, hasAnyUnresolvedName, ArrayRef<StringRef>, Names) {
+  auto DeclName = Node.getName();
+  if (!DeclName.isIdentifier())
+    return false;
+  const IdentifierInfo *II = DeclName.getAsIdentifierInfo();
+  return llvm::any_of(Names, [II](StringRef Name) { return II->isStr(Name); });
+}
+} // namespace
+
+static StringRef FunctionNames[] = {
+    "cast",     "cast_or_null",     "cast_if_present",
+    "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present"};
+
+void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
+  auto AnyCalleeName = [](ArrayRef<StringRef> CalleeName) {
+    return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+                 callee(expr(ignoringImpCasts(
+                     declRefExpr(to(namedDecl(hasAnyName(CalleeName))),
+                                 hasAnyTemplateArgumentLoc(anything()))
+                         .bind("callee")))));
+  };
+  auto AnyCalleeNameInUninstantiatedTemplate =
+      [](ArrayRef<StringRef> CalleeName) {
+        return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+                     callee(expr(ignoringImpCasts(
+                         unresolvedLookupExpr(hasAnyUnresolvedName(CalleeName))
+                             .bind("callee")))));
+      };
+  Finder->addMatcher(
+      callExpr(
+          AnyCalleeName(FunctionNames),
+          hasAncestor(functionDecl().bind("context")))
+          .bind("call"),
+      this);
+  Finder->addMatcher(
+      callExpr(AnyCalleeNameInUninstantiatedTemplate(FunctionNames))
+          .bind("call"),
+      this);
+}
+
+static QualType stripPointerOrReference(QualType Ty) {
+  QualType Pointee = Ty->getPointeeType();
+  if (Pointee.isNull())
+    return Ty;
+  return Pointee;
+}
+
+void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto &Nodes = Result.Nodes;
+  const auto *Call = Nodes.getNodeAs<CallExpr>("call");
+  if (Call->getNumArgs() != 1)
+    return;
+
+  CanQualType RetTy;
+  std::string FuncName;
+  if (const auto *ResolvedCallee = Nodes.getNodeAs<DeclRefExpr>("callee")) {
+    const auto *F = dyn_cast<FunctionDecl>(ResolvedCallee->getDecl());
+    const auto *SurroundingFunc = Nodes.getNodeAs<FunctionDecl>("context");
+    // Casts can be redundant for some instantiations but not others.
+    // Only emit warnings in templates in the uninstantated versions.
+    if (SurroundingFunc->isTemplateInstantiation())
+      return;
+
+    RetTy = stripPointerOrReference(F->getReturnType())
+                ->getCanonicalTypeUnqualified();
+    FuncName = F->getName();
+  } else if (const auto *UnresolvedCallee =
+                 Nodes.getNodeAs<UnresolvedLookupExpr>("callee")) {
+    if (UnresolvedCallee->getNumTemplateArgs() != 1)
+      return;
+    auto TArg = UnresolvedCallee->template_arguments()[0].getArgument();
+    if (TArg.getKind() != TemplateArgument::Type)
+      return;
+
+    RetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
+    FuncName = UnresolvedCallee->getName().getAsString();
+  } else {
+    return;
+  }
+
+  const auto *Arg = Call->getArg(0);
+  const CanQualType FromTy =
+      stripPointerOrReference(Arg->getType())->getCanonicalTypeUnqualified();
+  const auto *FromDecl = FromTy->getAsCXXRecordDecl();
+  const auto *RetDecl = RetTy->getAsCXXRecordDecl();
+  const bool IsDerived = FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
+  if (FromTy != RetTy && !IsDerived)
+    return;
+
+  auto GetText = [&](SourceRange R) {
+    return Lexer::getSourceText(CharSourceRange::getTokenRange(R),
+                                *Result.SourceManager, getLangOpts());
+  };
+  StringRef ArgText = GetText(Arg->getSourceRange());
+  diag(Call->getExprLoc(), "redundant use of '%0'")
+      << FuncName
+      << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
+  diag(Arg->getExprLoc(), "source expression %0 has type %1",
+       DiagnosticIDs::Note)
+      << Arg << Arg->IgnoreParenImpCasts()->getType();
+
+  if (FromTy != RetTy) {
+    diag(Arg->getExprLoc(), "%0 is a subtype of %1", DiagnosticIDs::Note)
+        << FromTy << RetTy;
+  }
+}
+
+} // namespace clang::tidy::llvm_check
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
new file mode 100644
index 0000000000000..3243616976014
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_REDUNDANTCASTINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_REDUNDANTCASTINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Detect redundant uses of LLVM's cast and dyn_cast functions.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/redundant-casting.html
+class RedundantCastingCheck : public ClangTidyCheck {
+public:
+  RedundantCastingCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+};
+
+} // namespace clang::tidy::llvm_check
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_REDUNDANTCASTINGCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index eb735e6e62ee4..43d67e0831fd9 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -114,6 +114,11 @@ New checks
   Finds functions where throwing exceptions is unsafe but the function is still
   marked as potentially throwing.
 
+- New :doc:`llvm-redundant-casting
+  <clang-tidy/checks/llvm/redundant-casting>` check.
+
+  Detect redundant uses of LLVM's cast functions.
+
 - New :doc:`llvm-type-switch-case-types
   <clang-tidy/checks/llvm/type-switch-case-types>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index ceab1e9414951..03ed10217fe45 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -251,6 +251,7 @@ Clang-Tidy Checks
    :doc:`llvm-prefer-isa-or-dyn-cast-in-conditionals <llvm/prefer-isa-or-dyn-cast-in-conditionals>`, "Yes"
    :doc:`llvm-prefer-register-over-unsigned <llvm/prefer-register-over-unsigned>`, "Yes"
    :doc:`llvm-prefer-static-over-anonymous-namespace <llvm/prefer-static-over-anonymous-namespace>`,
+   :doc:`llvm-redundant-casting <llvm/redundant-casting>`, "Yes"
    :doc:`llvm-twine-local <llvm/twine-local>`, "Yes"
    :doc:`llvm-type-switch-case-types <llvm/type-switch-case-types>`, "Yes"
    :doc:`llvm-use-new-mlir-op-builder <llvm/use-new-mlir-op-builder>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst b/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
new file mode 100644
index 0000000000000..c09bc40dd207e
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
@@ -0,0 +1,24 @@
+.. title:: clang-tidy - llvm-redundant-casting
+
+llvm-redundant-casting
+======================
+
+Points out uses of ``cast<>``, ``dyn_cast<>`` and their ``or_null`` variants
+that are unnecessary because the argument already is of the target type, or a
+derived type thereof.
+
+.. code-block:: c++
+
+  struct A {};
+  A a;
+  // Finds:
+  A x = cast<A>(a);
+  // replaced by:
+  A x = a;
+
+  struct B : public A {};
+  B b;
+  // Finds:
+  A y = cast<A>(b);
+  // replaced by:
+  A y = b;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
new file mode 100644
index 0000000000000..4027933f95c4b
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -0,0 +1,163 @@
+// RUN: %check_clang_tidy -std=c++17 %s llvm-redundant-casting %t
+
+namespace llvm {
+#define CAST_FUNCTION(name)                                          \
+template <typename To, typename From>                                \
+[[nodiscard]] inline decltype(auto) name(const From &Val) {          \
+  return static_cast<const To&>(Val);                                \
+}                                                                    \
+template <typename To, typename From>                                \
+[[nodiscard]] inline decltype(auto) name(From &Val) {                \
+  return static_cast<To&>(Val);                                      \
+}                                                                    \
+template <typename To, typename From>                                \
+[[nodiscard]] inline decltype(auto) name(const From *Val) {          \
+  return static_cast<const To*>(Val);                                \
+}                                                                    \
+template <typename To, typename From>                                \
+[[nodiscard]] inline decltype(auto) name(From *Val) {                \
+  return static_cast<To*>(Val);                                      \
+}
+CAST_FUNCTION(cast)
+CAST_FUNCTION(dyn_cast)
+CAST_FUNCTION(cast_or_null)
+CAST_FUNCTION(cast_if_present)
+CAST_FUNCTION(dyn_cast_or_null)
+CAST_FUNCTION(dyn_cast_if_present)
+}
+
+struct A {};
+struct B : A {};
+A &getA();
+
+void testCast(A& value) {
+  A& a1 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a1 = value;
+  (void)a1;
+}
+
+void testDynCast(A& value) {
+  A& a2 = llvm::dyn_cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:29: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a2 = value;
+  (void)a2;
+}
+
+void testCastOrNull(A& value) {
+  A& a3 = llvm::cast_or_null<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast_or_null' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:33: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a3 = value;
+  (void)a3;
+}
+
+void testCastIfPresent(A& value) {
+  A& a4 = llvm::cast_if_present<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast_if_present' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:36: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a4 = value;
+  (void)a4;
+}
+
+void testDynCastOrNull(A& value) {
+  A& a5 = llvm::dyn_cast_or_null<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast_or_null' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:37: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a5 = value;
+  (void)a5;
+}
+
+void testDynCastIfPresent(A& value) {
+  A& a6 = llvm::dyn_cast_if_present<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast_if_present' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:40: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a6 = value;
+  (void)a6;
+}
+
+void testCastNonDeclRef() {
+  A& a7 = llvm::cast<A>((getA()));
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression '(getA())' has type 'A'
+  // CHECK-FIXES: A& a7 = (getA());
+  (void)a7;
+}
+
+void testUpcast(B& value) {
+  A& a8 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression 'value' has type 'B'
+  // CHECK-MESSAGES: :[[@LINE-3]]:25: note: 'B' is a subtype of 'A'
+  // CHECK-FIXES: A& a8 = value;
+  (void)a8;
+}
+
+void testDowncast(A& value) {
+  B& a9 = llvm::cast<B>(value);
+  // CHECK-MESSAGES-NOT: warning
+  // CHECK-FIXES-NOT: A& a9 = value;
+  (void)a9;
+}
+
+namespace llvm {
+void testCastInLLVM(A& value) {
+  A& a11 = cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:20: note: source expression 'value' has type 'A'
+  // CHECK-FIXES: A& a11 = value;
+  (void)a11;
+}
+} // namespace llvm
+
+void testCastPointer(A* value) {
+  A *a12 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'A *'
+  // CHECK-FIXES: A *a12 = value;
+  (void)a12;
+}
+
+template <typename T>
+void testCastTemplateIgnore(T* value) {
+  A *a13 = llvm::cast<A>(value);
+  // CHECK-MESSAGES-NOT: warning
+  // CHECK-FIXES-NOT: A *a13 = value;
+  (void)a13;
+}
+template void testCastTemplateIgnore<A>(A *value);
+
+template <typename T>
+struct testCastTemplateIgnoreStruct {
+  void method(T* value) {
+    A *a14 = llvm::cast<A>(value);
+    // CHECK-MESSAGES-NOT: warning
+    // CHECK-FIXES-NOT: A *a14 = value;
+    (void)a14;
+  }
+};
+
+void call(testCastTemplateIgnoreStruct<A> s, A *a) {
+  s.method(a);
+}
+
+template <typename T>
+void testCastTemplateTrigger1(T* value) {
+  T *a15 = llvm::cast<T>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'T *'
+  // CHECK-FIXES: T *a15 = value;
+  (void)a15;
+}
+
+template <typename T>
+void testCastTemplateTrigger2(A* value, T other) {
+  A *a16 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'A *'
+  // CHECK-FIXES: A *a16 = value;
+  (void)a16; (void) other;
+}
+

>From 2f63c0e1dfd8a163858695c1f6cdec1002786a06 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 11:52:24 -0700
Subject: [PATCH 02/11] attempt to fix windows ci failure

---
 .../test/clang-tidy/checkers/llvm/redundant-casting.cpp         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
index 4027933f95c4b..aad3d6a701fa4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy -std=c++17 %s llvm-redundant-casting %t
+// RUN: %check_clang_tidy -std=c++17 %s llvm-redundant-casting %t -- -- -fno-delayed-template-parsing
 
 namespace llvm {
 #define CAST_FUNCTION(name)                                          \

>From 6102c8bea704ca8cdb0de0bcff08eaa371fc666f Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <hnrklssn at gmail.com>
Date: Sun, 29 Mar 2026 13:54:31 -0700
Subject: [PATCH 03/11] Apply suggestions from code review

Co-authored-by: Victor Chernyakin <chernyakin.victor.j at outlook.com>
---
 .../clang-tidy/llvm/RedundantCastingCheck.cpp             | 8 ++++----
 .../test/clang-tidy/checkers/llvm/redundant-casting.cpp   | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index a29701cc012b6..16c6914897e07 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -30,7 +30,7 @@ AST_MATCHER_P(OverloadExpr, hasAnyUnresolvedName, ArrayRef<StringRef>, Names) {
 }
 } // namespace
 
-static StringRef FunctionNames[] = {
+static constexpr StringRef FunctionNames[] = {
     "cast",     "cast_or_null",     "cast_if_present",
     "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present"};
 
@@ -98,7 +98,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
     RetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
     FuncName = UnresolvedCallee->getName().getAsString();
   } else {
-    return;
+    llvm_unreachable("");
   }
 
   const auto *Arg = Call->getArg(0);
@@ -118,9 +118,9 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   diag(Call->getExprLoc(), "redundant use of '%0'")
       << FuncName
       << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
-  diag(Arg->getExprLoc(), "source expression %0 has type %1",
+  diag(Arg->getExprLoc(), "source expression has type %1",
        DiagnosticIDs::Note)
-      << Arg << Arg->IgnoreParenImpCasts()->getType();
+      << Arg->getSourceRange() << Arg->IgnoreParenImpCasts()->getType();
 
   if (FromTy != RetTy) {
     diag(Arg->getExprLoc(), "%0 is a subtype of %1", DiagnosticIDs::Note)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
index aad3d6a701fa4..11445c982d8fc 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy -std=c++17 %s llvm-redundant-casting %t -- -- -fno-delayed-template-parsing
+// RUN: %check_clang_tidy -std=c++17-or-later %s llvm-redundant-casting %t -- -- -fno-delayed-template-parsing
 
 namespace llvm {
 #define CAST_FUNCTION(name)                                          \

>From 61e9ed12a0cc7b66934120b69564b295f921c3f8 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 15:09:50 -0700
Subject: [PATCH 04/11] update test

---
 .../clang-tidy/llvm/RedundantCastingCheck.cpp |  2 +-
 .../checkers/llvm/redundant-casting.cpp       | 24 +++++++++----------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 16c6914897e07..2cd89639e28e7 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -118,7 +118,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   diag(Call->getExprLoc(), "redundant use of '%0'")
       << FuncName
       << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
-  diag(Arg->getExprLoc(), "source expression has type %1",
+  diag(Arg->getExprLoc(), "source expression has type %0",
        DiagnosticIDs::Note)
       << Arg->getSourceRange() << Arg->IgnoreParenImpCasts()->getType();
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
index 11445c982d8fc..74f78243fc197 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -33,7 +33,7 @@ A &getA();
 void testCast(A& value) {
   A& a1 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression has type 'A'
   // CHECK-FIXES: A& a1 = value;
   (void)a1;
 }
@@ -41,7 +41,7 @@ void testCast(A& value) {
 void testDynCast(A& value) {
   A& a2 = llvm::dyn_cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:29: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:29: note: source expression has type 'A'
   // CHECK-FIXES: A& a2 = value;
   (void)a2;
 }
@@ -49,7 +49,7 @@ void testDynCast(A& value) {
 void testCastOrNull(A& value) {
   A& a3 = llvm::cast_or_null<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast_or_null' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:33: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:33: note: source expression has type 'A'
   // CHECK-FIXES: A& a3 = value;
   (void)a3;
 }
@@ -57,7 +57,7 @@ void testCastOrNull(A& value) {
 void testCastIfPresent(A& value) {
   A& a4 = llvm::cast_if_present<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast_if_present' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:36: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:36: note: source expression has type 'A'
   // CHECK-FIXES: A& a4 = value;
   (void)a4;
 }
@@ -65,7 +65,7 @@ void testCastIfPresent(A& value) {
 void testDynCastOrNull(A& value) {
   A& a5 = llvm::dyn_cast_or_null<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast_or_null' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:37: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:37: note: source expression has type 'A'
   // CHECK-FIXES: A& a5 = value;
   (void)a5;
 }
@@ -73,7 +73,7 @@ void testDynCastOrNull(A& value) {
 void testDynCastIfPresent(A& value) {
   A& a6 = llvm::dyn_cast_if_present<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'dyn_cast_if_present' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:40: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:40: note: source expression has type 'A'
   // CHECK-FIXES: A& a6 = value;
   (void)a6;
 }
@@ -81,7 +81,7 @@ void testDynCastIfPresent(A& value) {
 void testCastNonDeclRef() {
   A& a7 = llvm::cast<A>((getA()));
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression '(getA())' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression has type 'A'
   // CHECK-FIXES: A& a7 = (getA());
   (void)a7;
 }
@@ -89,7 +89,7 @@ void testCastNonDeclRef() {
 void testUpcast(B& value) {
   A& a8 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression 'value' has type 'B'
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression has type 'B'
   // CHECK-MESSAGES: :[[@LINE-3]]:25: note: 'B' is a subtype of 'A'
   // CHECK-FIXES: A& a8 = value;
   (void)a8;
@@ -106,7 +106,7 @@ namespace llvm {
 void testCastInLLVM(A& value) {
   A& a11 = cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:20: note: source expression 'value' has type 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:20: note: source expression has type 'A'
   // CHECK-FIXES: A& a11 = value;
   (void)a11;
 }
@@ -115,7 +115,7 @@ void testCastInLLVM(A& value) {
 void testCastPointer(A* value) {
   A *a12 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'A *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A *'
   // CHECK-FIXES: A *a12 = value;
   (void)a12;
 }
@@ -147,7 +147,7 @@ template <typename T>
 void testCastTemplateTrigger1(T* value) {
   T *a15 = llvm::cast<T>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'T *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'T *'
   // CHECK-FIXES: T *a15 = value;
   (void)a15;
 }
@@ -156,7 +156,7 @@ template <typename T>
 void testCastTemplateTrigger2(A* value, T other) {
   A *a16 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression 'value' has type 'A *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A *'
   // CHECK-FIXES: A *a16 = value;
   (void)a16; (void) other;
 }

>From cde2f3fe8b28eb3d4fc13e4543af30267f6cceca Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 15:13:07 -0700
Subject: [PATCH 05/11] simplify ignoring template instantiation

---
 .../clang-tidy/llvm/RedundantCastingCheck.cpp     | 15 ++-------------
 .../clang-tidy/llvm/RedundantCastingCheck.h       |  6 ++++++
 2 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 2cd89639e28e7..1a1c1cee823da 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -49,12 +49,7 @@ void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
                          unresolvedLookupExpr(hasAnyUnresolvedName(CalleeName))
                              .bind("callee")))));
       };
-  Finder->addMatcher(
-      callExpr(
-          AnyCalleeName(FunctionNames),
-          hasAncestor(functionDecl().bind("context")))
-          .bind("call"),
-      this);
+  Finder->addMatcher(callExpr(AnyCalleeName(FunctionNames)).bind("call"), this);
   Finder->addMatcher(
       callExpr(AnyCalleeNameInUninstantiatedTemplate(FunctionNames))
           .bind("call"),
@@ -77,13 +72,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   CanQualType RetTy;
   std::string FuncName;
   if (const auto *ResolvedCallee = Nodes.getNodeAs<DeclRefExpr>("callee")) {
-    const auto *F = dyn_cast<FunctionDecl>(ResolvedCallee->getDecl());
-    const auto *SurroundingFunc = Nodes.getNodeAs<FunctionDecl>("context");
-    // Casts can be redundant for some instantiations but not others.
-    // Only emit warnings in templates in the uninstantated versions.
-    if (SurroundingFunc->isTemplateInstantiation())
-      return;
-
+    const auto *F = cast<FunctionDecl>(ResolvedCallee->getDecl());
     RetTy = stripPointerOrReference(F->getReturnType())
                 ->getCanonicalTypeUnqualified();
     FuncName = F->getName();
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
index 3243616976014..2ab7369c0358e 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.h
@@ -26,6 +26,12 @@ class RedundantCastingCheck : public ClangTidyCheck {
   bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
     return LangOpts.CPlusPlus;
   }
+
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    // Casts can be redundant for some instantiations but not others.
+    // Only emit warnings in templates in the uninstantated versions.
+    return TK_IgnoreUnlessSpelledInSource;
+  }
 };
 
 } // namespace clang::tidy::llvm_check

>From 5f38eed14aa30a1b3e3d88035e5168f7f671009c Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 15:14:46 -0700
Subject: [PATCH 06/11] clang format

---
 clang-tools-extra/clang-tidy/.clang-format                  | 4 ----
 clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp | 6 +++---
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/.clang-format b/clang-tools-extra/clang-tidy/.clang-format
index 1b342d3f71f71..99928eba2b7fe 100644
--- a/clang-tools-extra/clang-tidy/.clang-format
+++ b/clang-tools-extra/clang-tidy/.clang-format
@@ -1,9 +1,5 @@
 BasedOnStyle: LLVM
 InsertNewlineAtEOF: true
-KeepEmptyLines:
-  AtEndOfFile: false
-  AtStartOfBlock: false
-  AtStartOfFile: false
 LineEnding: LF
 QualifierAlignment: Left
 RemoveBracesLLVM: true
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 1a1c1cee823da..75e414963c1c9 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -95,7 +95,8 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
       stripPointerOrReference(Arg->getType())->getCanonicalTypeUnqualified();
   const auto *FromDecl = FromTy->getAsCXXRecordDecl();
   const auto *RetDecl = RetTy->getAsCXXRecordDecl();
-  const bool IsDerived = FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
+  const bool IsDerived =
+      FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
   if (FromTy != RetTy && !IsDerived)
     return;
 
@@ -107,8 +108,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   diag(Call->getExprLoc(), "redundant use of '%0'")
       << FuncName
       << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
-  diag(Arg->getExprLoc(), "source expression has type %0",
-       DiagnosticIDs::Note)
+  diag(Arg->getExprLoc(), "source expression has type %0", DiagnosticIDs::Note)
       << Arg->getSourceRange() << Arg->IgnoreParenImpCasts()->getType();
 
   if (FromTy != RetTy) {

>From 702274001b1a78bc8efb6e5ef47d10b50b4dbefa Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 15:32:10 -0700
Subject: [PATCH 07/11] add test cases with const

---
 .../checkers/llvm/redundant-casting.cpp          | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
index 74f78243fc197..cd07000eea666 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -161,3 +161,19 @@ void testCastTemplateTrigger2(A* value, T other) {
   (void)a16; (void) other;
 }
 
+void testCastConst(const A& value) {
+  const A& a17 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'const A'
+  // CHECK-FIXES: const A& a17 = value;
+  (void)a17;
+}
+
+void testCastConstPointer(const A* value) {
+  const A* a18 = llvm::cast<A>(value);
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: redundant use of 'cast' [llvm-redundant-casting]
+  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'const A *'
+  // CHECK-FIXES: const A* a18 = value;
+  (void)a18;
+}
+

>From 801ca54c8bad9c2b510ea510055934496c67b4e3 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 16:43:07 -0700
Subject: [PATCH 08/11] revert unintended change to clang-format config

---
 clang-tools-extra/clang-tidy/.clang-format | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang-tools-extra/clang-tidy/.clang-format b/clang-tools-extra/clang-tidy/.clang-format
index 99928eba2b7fe..1b342d3f71f71 100644
--- a/clang-tools-extra/clang-tidy/.clang-format
+++ b/clang-tools-extra/clang-tidy/.clang-format
@@ -1,5 +1,9 @@
 BasedOnStyle: LLVM
 InsertNewlineAtEOF: true
+KeepEmptyLines:
+  AtEndOfFile: false
+  AtStartOfBlock: false
+  AtStartOfFile: false
 LineEnding: LF
 QualifierAlignment: Left
 RemoveBracesLLVM: true

>From f5f8e973cbca599f1d5c3007999b3107cacce873 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 16:43:49 -0700
Subject: [PATCH 09/11] remove lambdas with a single caller

---
 .../clang-tidy/llvm/RedundantCastingCheck.cpp | 29 ++++++++-----------
 1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 75e414963c1c9..2a869e6b9b0f5 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -35,25 +35,20 @@ static constexpr StringRef FunctionNames[] = {
     "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present"};
 
 void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
-  auto AnyCalleeName = [](ArrayRef<StringRef> CalleeName) {
-    return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
-                 callee(expr(ignoringImpCasts(
-                     declRefExpr(to(namedDecl(hasAnyName(CalleeName))),
-                                 hasAnyTemplateArgumentLoc(anything()))
-                         .bind("callee")))));
-  };
+  auto AnyCalleeName =
+      allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+            callee(expr(ignoringImpCasts(
+                declRefExpr(to(namedDecl(hasAnyName(FunctionNames))),
+                            hasAnyTemplateArgumentLoc(anything()))
+                    .bind("callee")))));
   auto AnyCalleeNameInUninstantiatedTemplate =
-      [](ArrayRef<StringRef> CalleeName) {
-        return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
-                     callee(expr(ignoringImpCasts(
-                         unresolvedLookupExpr(hasAnyUnresolvedName(CalleeName))
-                             .bind("callee")))));
-      };
-  Finder->addMatcher(callExpr(AnyCalleeName(FunctionNames)).bind("call"), this);
+      allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+            callee(expr(ignoringImpCasts(
+                unresolvedLookupExpr(hasAnyUnresolvedName(FunctionNames))
+                    .bind("callee")))));
+  Finder->addMatcher(callExpr(AnyCalleeName).bind("call"), this);
   Finder->addMatcher(
-      callExpr(AnyCalleeNameInUninstantiatedTemplate(FunctionNames))
-          .bind("call"),
-      this);
+      callExpr(AnyCalleeNameInUninstantiatedTemplate).bind("call"), this);
 }
 
 static QualType stripPointerOrReference(QualType Ty) {

>From d2489890b5a8d1d947744e63a5d7c396de677b41 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 17:19:30 -0700
Subject: [PATCH 10/11] simplify note diagnostic

---
 .../clang-tidy/llvm/RedundantCastingCheck.cpp | 22 +++++++++++--------
 .../checkers/llvm/redundant-casting.cpp       | 13 +++++------
 2 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 2a869e6b9b0f5..5b01960fe181e 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -10,6 +10,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/TemplateBase.h"
+#include "clang/AST/TypeBase.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Lex/Lexer.h"
@@ -86,8 +87,9 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   }
 
   const auto *Arg = Call->getArg(0);
-  const CanQualType FromTy =
-      stripPointerOrReference(Arg->getType())->getCanonicalTypeUnqualified();
+  QualType ArgTy = Arg->getType();
+  QualType ArgPointeeTy = stripPointerOrReference(ArgTy);
+  const CanQualType FromTy = ArgPointeeTy->getCanonicalTypeUnqualified();
   const auto *FromDecl = FromTy->getAsCXXRecordDecl();
   const auto *RetDecl = RetTy->getAsCXXRecordDecl();
   const bool IsDerived =
@@ -103,13 +105,15 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   diag(Call->getExprLoc(), "redundant use of '%0'")
       << FuncName
       << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
-  diag(Arg->getExprLoc(), "source expression has type %0", DiagnosticIDs::Note)
-      << Arg->getSourceRange() << Arg->IgnoreParenImpCasts()->getType();
-
-  if (FromTy != RetTy) {
-    diag(Arg->getExprLoc(), "%0 is a subtype of %1", DiagnosticIDs::Note)
-        << FromTy << RetTy;
-  }
+  // printing the canonical type for a template parameter prints as e.g.
+  // 'type-parameter-0-0'
+  QualType DiagFromTy(ArgPointeeTy->getUnqualifiedDesugaredType(), 0);
+  diag(Arg->getExprLoc(),
+       "source expression has %select{|pointee}0 type %1%select{|, which is a "
+       "subtype of %3}2",
+       DiagnosticIDs::Note)
+      << Arg->getSourceRange() << ArgTy->isPointerType() << DiagFromTy
+      << (FromTy != RetTy) << RetTy;
 }
 
 } // namespace clang::tidy::llvm_check
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
index cd07000eea666..7ae036b7398ac 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-casting.cpp
@@ -89,8 +89,7 @@ void testCastNonDeclRef() {
 void testUpcast(B& value) {
   A& a8 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression has type 'B'
-  // CHECK-MESSAGES: :[[@LINE-3]]:25: note: 'B' is a subtype of 'A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:25: note: source expression has type 'B', which is a subtype of 'A'
   // CHECK-FIXES: A& a8 = value;
   (void)a8;
 }
@@ -115,7 +114,7 @@ void testCastInLLVM(A& value) {
 void testCastPointer(A* value) {
   A *a12 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has pointee type 'A'
   // CHECK-FIXES: A *a12 = value;
   (void)a12;
 }
@@ -147,7 +146,7 @@ template <typename T>
 void testCastTemplateTrigger1(T* value) {
   T *a15 = llvm::cast<T>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'T *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has pointee type 'T'
   // CHECK-FIXES: T *a15 = value;
   (void)a15;
 }
@@ -156,7 +155,7 @@ template <typename T>
 void testCastTemplateTrigger2(A* value, T other) {
   A *a16 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has pointee type 'A'
   // CHECK-FIXES: A *a16 = value;
   (void)a16; (void) other;
 }
@@ -164,7 +163,7 @@ void testCastTemplateTrigger2(A* value, T other) {
 void testCastConst(const A& value) {
   const A& a17 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'const A'
+  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'A'
   // CHECK-FIXES: const A& a17 = value;
   (void)a17;
 }
@@ -172,7 +171,7 @@ void testCastConst(const A& value) {
 void testCastConstPointer(const A* value) {
   const A* a18 = llvm::cast<A>(value);
   // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: redundant use of 'cast' [llvm-redundant-casting]
-  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'const A *'
+  // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has pointee type 'A'
   // CHECK-FIXES: const A* a18 = value;
   (void)a18;
 }

>From 6e6828a4e769fc2956282f5e66078bce07074e3b Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 17:25:34 -0700
Subject: [PATCH 11/11] const correctness

---
 clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 5b01960fe181e..1659be63b4629 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -87,8 +87,8 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
   }
 
   const auto *Arg = Call->getArg(0);
-  QualType ArgTy = Arg->getType();
-  QualType ArgPointeeTy = stripPointerOrReference(ArgTy);
+  const QualType ArgTy = Arg->getType();
+  const QualType ArgPointeeTy = stripPointerOrReference(ArgTy);
   const CanQualType FromTy = ArgPointeeTy->getCanonicalTypeUnqualified();
   const auto *FromDecl = FromTy->getAsCXXRecordDecl();
   const auto *RetDecl = RetTy->getAsCXXRecordDecl();
@@ -107,7 +107,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
       << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText);
   // printing the canonical type for a template parameter prints as e.g.
   // 'type-parameter-0-0'
-  QualType DiagFromTy(ArgPointeeTy->getUnqualifiedDesugaredType(), 0);
+  const QualType DiagFromTy(ArgPointeeTy->getUnqualifiedDesugaredType(), 0);
   diag(Arg->getExprLoc(),
        "source expression has %select{|pointee}0 type %1%select{|, which is a "
        "subtype of %3}2",



More information about the cfe-commits mailing list