[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 11:52:40 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 1/2] [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 2/2] 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) \
More information about the cfe-commits
mailing list