[clang-tools-extra] [clang-tidy] detect uses of llvm::isa that are always true (PR #191081)
Henrik G. Olsson via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 9 12:07:06 PDT 2026
https://github.com/hnrklssn updated https://github.com/llvm/llvm-project/pull/191081
>From e14bec55272e815a36662d2354c21dd4985d854a Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 12:48:15 -0700
Subject: [PATCH 1/9] [clang-tidy] detect uses of llvm::isa that are always
true
Warns when performing a dynamic type check that is always true, either
because the dynamic type is the same as the static type, or because the
static type derives the dynamic type.
Supported functions:
- isa
- isa_and_present
- isa_and_nonnull
---
.../clang-tidy/llvm/RedundantCastingCheck.cpp | 101 +++++++-------
clang-tools-extra/docs/ReleaseNotes.rst | 3 +-
.../checks/llvm/redundant-casting.rst | 12 +-
.../checkers/llvm/redundant-casting.cpp | 2 +-
.../checkers/llvm/redundant-isa.cpp | 126 ++++++++++++++++++
5 files changed, 194 insertions(+), 50 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 8f8c152b5de86..59808c539419d 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -38,7 +38,8 @@ AST_MATCHER_P(OverloadExpr, hasAnyUnresolvedName, ArrayRef<StringRef>, Names) {
static constexpr StringRef FunctionNames[] = {
"cast", "cast_or_null", "cast_if_present",
- "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present"};
+ "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present",
+ "isa", "isa_and_nonnull", "isa_and_present"};
void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
auto IsInLLVMNamespace = hasDeclContext(
@@ -93,12 +94,15 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
const auto &Nodes = Result.Nodes;
const auto *Call = Nodes.getNodeAs<CallExpr>("call");
- CanQualType RetTy;
+ TemplateArgument TArg;
std::string FuncName;
if (const auto *ResolvedCallee = Nodes.getNodeAs<DeclRefExpr>("callee")) {
const auto *F = cast<FunctionDecl>(ResolvedCallee->getDecl());
- RetTy = stripPointerOrReference(F->getReturnType())
- ->getCanonicalTypeUnqualified();
+ const auto *TArgs = F->getTemplateSpecializationArgs();
+ if (TArgs->size() != 2)
+ return;
+
+ TArg = TArgs->get(0);
FuncName = F->getName();
} else if (const auto *UnresolvedCallee =
Nodes.getNodeAs<UnresolvedLookupExpr>("callee")) {
@@ -107,16 +111,17 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
const auto *CallerNS = Nodes.getNodeAs<NamedDecl>("llvm_ns");
if (!IsExplicitlyLLVM && !CallerNS)
return;
- auto TArg = UnresolvedCallee->template_arguments()[0].getArgument();
- if (TArg.getKind() != TemplateArgument::Type)
- return;
- RetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
+ TArg = UnresolvedCallee->template_arguments()[0].getArgument();
FuncName = UnresolvedCallee->getName().getAsString();
} else {
- llvm_unreachable("");
+ llvm_unreachable("unexpected callee kind");
}
+ if (TArg.getKind() != TemplateArgument::Type)
+ return;
+
+ CanQualType RetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
const auto *Arg = Call->getArg(0);
const QualType ArgTy = Arg->getType();
const QualType ArgPointeeTy = stripPointerOrReference(ArgTy);
@@ -128,47 +133,53 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
if (FromTy != RetTy && !IsDerived)
return;
- QualType ParentTy;
- if (const auto *ParentCast = Nodes.getNodeAs<Expr>("parent_cast")) {
- ParentTy = ParentCast->getType();
+ const bool IsIsa = StringRef(FuncName).starts_with("isa");
+ if (IsIsa) {
+ diag(Call->getExprLoc(), "call to '%0' always succeeds")
+ << FuncName;
} else {
- // IgnoreUnlessSpelledInSource prevents matching implicit casts
- const TraversalKindScope TmpTraversalKind(*Result.Context, TK_AsIs);
- for (const DynTypedNode Parent : Result.Context->getParents(*Call)) {
- if (const auto *ParentCastExpr = Parent.get<CastExpr>()) {
- ParentTy = ParentCastExpr->getType();
- break;
+ QualType ParentTy;
+ if (const auto *ParentCast = Nodes.getNodeAs<Expr>("parent_cast")) {
+ ParentTy = ParentCast->getType();
+ } else {
+ // IgnoreUnlessSpelledInSource prevents matching implicit casts
+ const TraversalKindScope TmpTraversalKind(*Result.Context, TK_AsIs);
+ for (const DynTypedNode Parent : Result.Context->getParents(*Call)) {
+ if (const auto *ParentCastExpr = Parent.get<CastExpr>()) {
+ ParentTy = ParentCastExpr->getType();
+ break;
+ }
}
}
- }
- if (!ParentTy.isNull()) {
- const CXXRecordDecl *ParentDecl = ParentTy->getAsCXXRecordDecl();
- if (FromDecl && ParentDecl) {
- CXXBasePaths Paths(/*FindAmbiguities=*/true,
- /*RecordPaths=*/false,
- /*DetectVirtual=*/false);
- const bool IsDerivedFromParent =
- FromDecl && ParentDecl && FromDecl->isDerivedFrom(ParentDecl, Paths);
- // For the following case a direct `cast<A>(d)` would be ambiguous:
- // struct A {};
- // struct B : A {};
- // struct C : A {};
- // struct D : B, C {};
- // So we should not warn for `A *a = cast<C>(d)`.
- if (IsDerivedFromParent &&
- Paths.isAmbiguous(ParentTy->getCanonicalTypeUnqualified()))
- return;
+ if (!ParentTy.isNull()) {
+ const CXXRecordDecl *ParentDecl = ParentTy->getAsCXXRecordDecl();
+ if (FromDecl && ParentDecl) {
+ CXXBasePaths Paths(/*FindAmbiguities=*/true,
+ /*RecordPaths=*/false,
+ /*DetectVirtual=*/false);
+ const bool IsDerivedFromParent =
+ FromDecl && ParentDecl && FromDecl->isDerivedFrom(ParentDecl, Paths);
+ // For the following case a direct `cast<A>(d)` would be ambiguous:
+ // struct A {};
+ // struct B : A {};
+ // struct C : A {};
+ // struct D : B, C {};
+ // So we should not warn for `A *a = cast<C>(d)`.
+ if (IsDerivedFromParent &&
+ Paths.isAmbiguous(ParentTy->getCanonicalTypeUnqualified()))
+ 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);
+ 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);
+ }
// printing the canonical type for a template parameter prints as e.g.
// 'type-parameter-0-0'
const QualType DiagFromTy(ArgPointeeTy->getUnqualifiedDesugaredType(), 0);
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 78a7ff6ff7561..dcdede6229e8e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -130,7 +130,8 @@ New checks
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.
+ derived type thereof. Also does similar analysis for calls to ``isa<>`` that
+ always return ``true``.
- 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/llvm/redundant-casting.rst b/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
index 84596b26ee382..cc2717f527847 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
@@ -3,9 +3,9 @@
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.
+Points out uses of ``cast<>``, ``dyn_cast<>``, ``isa<>`` 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++
@@ -16,6 +16,9 @@ derived type thereof.
// replaced by:
A x = a;
+ // Finds:
+ bool b = isa<A>(a) // always true
+
struct B : public A {};
B b;
// Finds:
@@ -30,3 +33,6 @@ Supported functions:
- ``llvm::dyn_cast``
- ``llvm::dyn_cast_or_null``
- ``llvm::dyn_cast_if_present``
+ - ``llvm::isa``
+ - ``llvm::isa_and_nonnull``
+ - ``llvm::isa_and_present``
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 933cbadc51701..5955c8a6870a6 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
@@ -24,7 +24,7 @@ CAST_FUNCTION(cast_or_null)
CAST_FUNCTION(cast_if_present)
CAST_FUNCTION(dyn_cast_or_null)
CAST_FUNCTION(dyn_cast_if_present)
-}
+} // namespace llvm
struct A {};
struct B : A {};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
new file mode 100644
index 0000000000000..9275f659b51a4
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
@@ -0,0 +1,126 @@
+// RUN: %check_clang_tidy -std=c++17 %s llvm-redundant-casting %t -- -- -fno-delayed-template-parsing
+
+namespace llvm {
+#define ISA_FUNCTION(name) \
+template <typename To, typename From> \
+[[nodiscard]] inline bool name(const From &Val) { \
+ return true; \
+} \
+template <typename To, typename From> \
+[[nodiscard]] inline bool name(const From *Val) { \
+ return true; \
+}
+
+ISA_FUNCTION(isa)
+ISA_FUNCTION(isa_and_present)
+ISA_FUNCTION(isa_and_nonnull)
+} // namespace llvm
+
+struct A {};
+struct B : A {};
+A &getA();
+
+void testIsa(A& value) {
+ bool b1 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A'
+ (void)b1;
+}
+
+void testIsaAndPresent(A& value) {
+ bool b2 = llvm::isa_and_present<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa_and_present' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:38: note: source expression has type 'A'
+ (void)b2;
+}
+
+void testIsaAndNonnull(A& value) {
+ bool b3 = llvm::isa_and_nonnull<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa_and_nonnull' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:38: note: source expression has type 'A'
+ (void)b3;
+}
+
+void testIsaNonDeclRef() {
+ bool b4 = llvm::isa<A>((getA()));
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'A'
+ (void)b4;
+}
+
+void testUpcast(B& value) {
+ bool b5 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has type 'B', which is a subtype of 'A'
+ (void)b5;
+}
+
+void testDowncast(A& value) {
+ bool b6 = llvm::isa<B>(value);
+ (void)b6;
+}
+
+namespace llvm {
+void testIsaInLLVM(A& value) {
+ bool b7 = isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:20: note: source expression has type 'A'
+ (void)b7;
+}
+} // namespace llvm
+
+void testIsaPointer(A* value) {
+ bool b8 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:26: note: source expression has pointee type 'A'
+ (void)b8;
+}
+
+template <typename T>
+void testIsaTemplateIgnore(T* value) {
+ bool b9 = llvm::isa<A>(value);
+ (void)b9;
+}
+template void testIsaTemplateIgnore<A>(A *value);
+
+template <typename T>
+struct testIsaTemplateIgnoreStruct {
+ void method(T* value) {
+ bool b10 = llvm::isa<A>(value);
+ (void)b10;
+ }
+};
+
+void call(testIsaTemplateIgnoreStruct<A> s, A *a) {
+ s.method(a);
+}
+
+template <typename T>
+void testIsaTemplateTrigger1(T* value) {
+ bool b11 = llvm::isa<T>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:27: note: source expression has pointee type 'T'
+ (void)b11;
+}
+
+template <typename T>
+void testIsaTemplateTrigger2(A* value, T other) {
+ bool b12 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:27: note: source expression has pointee type 'A'
+ (void)b12; (void)other;
+}
+
+void testIsaConst(const A& value) {
+ bool b13 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:27: note: source expression has type 'A'
+ (void)b13;
+}
+
+void testIsaConstPointer(const A* value) {
+ bool b14 = llvm::isa<A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:27: note: source expression has pointee type 'A'
+ (void)b14;
+}
>From e3d5cf0cc145e5a8135daecc2bea8e77df187d0c Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Sun, 29 Mar 2026 16:20:48 -0700
Subject: [PATCH 2/9] [clang-tidy] support isa<> with template pack parameter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The real `isa<>` implementation allows for a variable number of target
types. This means that calls with even a single template parameter will
still have a parameter pack, which would fail to be recognise with the
naïve implemenation which would abort for any template argument that
wasn't a plain type parameter. This generalises the recognised pattern
to detect cases involving multiple target types.
---
.../clang-tidy/llvm/RedundantCastingCheck.cpp | 192 +++++++++++-------
.../checks/llvm/redundant-casting.rst | 10 +-
.../checkers/llvm/redundant-isa.cpp | 26 ++-
3 files changed, 149 insertions(+), 79 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 59808c539419d..f5f05ba1f496b 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -20,6 +20,8 @@
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/ErrorHandling.h"
using namespace clang::ast_matchers;
@@ -36,35 +38,58 @@ AST_MATCHER_P(OverloadExpr, hasAnyUnresolvedName, ArrayRef<StringRef>, Names) {
}
} // namespace
-static constexpr StringRef FunctionNames[] = {
+static constexpr StringRef CastFunctionNames[] = {
"cast", "cast_or_null", "cast_if_present",
- "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present",
- "isa", "isa_and_nonnull", "isa_and_present"};
+ "dyn_cast", "dyn_cast_or_null", "dyn_cast_if_present"};
+
+static constexpr StringRef IsaFunctionNames[] = {"isa", "isa_and_nonnull",
+ "isa_and_present"};
void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
auto IsInLLVMNamespace = hasDeclContext(
namespaceDecl(hasName("llvm"), hasDeclContext(translationUnitDecl())));
- auto AnyCalleeName =
+ auto AnyCastCalleeName =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
callee(expr(ignoringImpCasts(
- declRefExpr(
- to(namedDecl(hasAnyName(FunctionNames), IsInLLVMNamespace)),
- templateArgumentLocCountIs(1))
+ declRefExpr(to(namedDecl(hasAnyName(CastFunctionNames),
+ IsInLLVMNamespace)),
+ templateArgumentLocCountIs(1))
.bind("callee")))));
- auto AnyCalleeNameInUninstantiatedTemplate =
+ auto AnyCastCalleeNameInUninstantiatedTemplate =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
callee(expr(ignoringImpCasts(
- unresolvedLookupExpr(hasAnyUnresolvedName(FunctionNames),
+ unresolvedLookupExpr(hasAnyUnresolvedName(CastFunctionNames),
templateArgumentLocCountIs(1))
.bind("callee")))));
- Finder->addMatcher(callExpr(AnyCalleeName, argumentCountIs(1),
- optionally(hasParent(
- callExpr(AnyCalleeName).bind("parent_cast"))))
+ Finder->addMatcher(
+ callExpr(AnyCastCalleeName, argumentCountIs(1),
+ optionally(
+ hasParent(callExpr(AnyCastCalleeName).bind("parent_cast"))))
+ .bind("call"),
+ this);
+
+ auto AnyIsaCalleeName =
+ allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+ callee(expr(ignoringImpCasts(
+ declRefExpr(
+ to(namedDecl(hasAnyName(IsaFunctionNames), IsInLLVMNamespace)),
+ hasAnyTemplateArgumentLoc(anything()))
+ .bind("callee")))));
+ Finder->addMatcher(callExpr(AnyIsaCalleeName, argumentCountIs(1))
.bind("call"),
this);
+
+ auto AnyIsaCalleeNameInUninstantiatedTemplate =
+ allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
+ callee(expr(ignoringImpCasts(
+ unresolvedLookupExpr(hasAnyUnresolvedName(IsaFunctionNames),
+ hasAnyTemplateArgumentLoc(anything()))
+ .bind("callee")))));
Finder->addMatcher(
callExpr(
- AnyCalleeNameInUninstantiatedTemplate, argumentCountIs(1),
+ anyOf(AnyCastCalleeNameInUninstantiatedTemplate,
+ AnyIsaCalleeNameInUninstantiatedTemplate),
+ argumentCountIs(1),
optionally(hasAncestor(
namespaceDecl(hasName("llvm"), hasParent(translationUnitDecl()))
.bind("llvm_ns"))))
@@ -94,7 +119,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
const auto &Nodes = Result.Nodes;
const auto *Call = Nodes.getNodeAs<CallExpr>("call");
- TemplateArgument TArg;
+ llvm::SmallVector<CanQualType, 4> TargetTypes;
std::string FuncName;
if (const auto *ResolvedCallee = Nodes.getNodeAs<DeclRefExpr>("callee")) {
const auto *F = cast<FunctionDecl>(ResolvedCallee->getDecl());
@@ -102,7 +127,20 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
if (TArgs->size() != 2)
return;
- TArg = TArgs->get(0);
+ TemplateArgument TArg = TArgs->get(0);
+ if (TArg.getKind() == TemplateArgument::Type) {
+ CanQualType TargetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
+ TargetTypes.emplace_back(TargetTy);
+ } else if (TArg.getKind() == TemplateArgument::Pack) {
+ for (const auto &E : TArg.pack_elements()) {
+ if (E.getKind() != TemplateArgument::Type)
+ return;
+ TargetTypes.emplace_back(E.getAsType()->getCanonicalTypeUnqualified());
+ }
+ } else {
+ llvm_unreachable("unexpected template argument");
+ }
+
FuncName = F->getName();
} else if (const auto *UnresolvedCallee =
Nodes.getNodeAs<UnresolvedLookupExpr>("callee")) {
@@ -112,83 +150,89 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
if (!IsExplicitlyLLVM && !CallerNS)
return;
- TArg = UnresolvedCallee->template_arguments()[0].getArgument();
+ for (const auto &TArgLoc : UnresolvedCallee->template_arguments()) {
+ const TemplateArgument TArg = TArgLoc.getArgument();
+ if (TArg.getKind() != TemplateArgument::Type)
+ return;
+ TargetTypes.emplace_back(TArg.getAsType()->getCanonicalTypeUnqualified());
+ }
FuncName = UnresolvedCallee->getName().getAsString();
} else {
llvm_unreachable("unexpected callee kind");
}
- if (TArg.getKind() != TemplateArgument::Type)
- return;
-
- CanQualType RetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
const auto *Arg = Call->getArg(0);
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();
- const bool IsDerived =
- FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
- if (FromTy != RetTy && !IsDerived)
+ const bool IsIsa = StringRef(FuncName).starts_with("isa");
+ if (!IsIsa && TargetTypes.size() != 1)
return;
- const bool IsIsa = StringRef(FuncName).starts_with("isa");
- if (IsIsa) {
- diag(Call->getExprLoc(), "call to '%0' always succeeds")
- << FuncName;
- } else {
- QualType ParentTy;
- if (const auto *ParentCast = Nodes.getNodeAs<Expr>("parent_cast")) {
- ParentTy = ParentCast->getType();
+ for (CanQualType TargetTy : TargetTypes) {
+ const auto *RetDecl = TargetTy->getAsCXXRecordDecl();
+ const bool IsDerived = FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
+ if (FromTy != TargetTy && !IsDerived)
+ continue;
+
+ if (IsIsa) {
+ diag(Call->getExprLoc(), "call to '%0' always succeeds")
+ << FuncName;
} else {
- // IgnoreUnlessSpelledInSource prevents matching implicit casts
- const TraversalKindScope TmpTraversalKind(*Result.Context, TK_AsIs);
- for (const DynTypedNode Parent : Result.Context->getParents(*Call)) {
- if (const auto *ParentCastExpr = Parent.get<CastExpr>()) {
- ParentTy = ParentCastExpr->getType();
- break;
+ QualType ParentTy;
+ if (const auto *ParentCast = Nodes.getNodeAs<Expr>("parent_cast")) {
+ ParentTy = ParentCast->getType();
+ } else {
+ // IgnoreUnlessSpelledInSource prevents matching implicit casts
+ const TraversalKindScope TmpTraversalKind(*Result.Context, TK_AsIs);
+ for (const DynTypedNode Parent : Result.Context->getParents(*Call)) {
+ if (const auto *ParentCastExpr = Parent.get<CastExpr>()) {
+ ParentTy = ParentCastExpr->getType();
+ break;
+ }
}
}
- }
- if (!ParentTy.isNull()) {
- const CXXRecordDecl *ParentDecl = ParentTy->getAsCXXRecordDecl();
- if (FromDecl && ParentDecl) {
- CXXBasePaths Paths(/*FindAmbiguities=*/true,
- /*RecordPaths=*/false,
- /*DetectVirtual=*/false);
- const bool IsDerivedFromParent =
- FromDecl && ParentDecl && FromDecl->isDerivedFrom(ParentDecl, Paths);
- // For the following case a direct `cast<A>(d)` would be ambiguous:
- // struct A {};
- // struct B : A {};
- // struct C : A {};
- // struct D : B, C {};
- // So we should not warn for `A *a = cast<C>(d)`.
- if (IsDerivedFromParent &&
- Paths.isAmbiguous(ParentTy->getCanonicalTypeUnqualified()))
- return;
+ if (!ParentTy.isNull()) {
+ const CXXRecordDecl *ParentDecl = ParentTy->getAsCXXRecordDecl();
+ if (FromDecl && ParentDecl) {
+ CXXBasePaths Paths(/*FindAmbiguities=*/true,
+ /*RecordPaths=*/false,
+ /*DetectVirtual=*/false);
+ const bool IsDerivedFromParent =
+ FromDecl && ParentDecl && FromDecl->isDerivedFrom(ParentDecl, Paths);
+ // For the following case a direct `cast<A>(d)` would be ambiguous:
+ // struct A {};
+ // struct B : A {};
+ // struct C : A {};
+ // struct D : B, C {};
+ // So we should not warn for `A *a = cast<C>(d)`.
+ if (IsDerivedFromParent &&
+ Paths.isAmbiguous(ParentTy->getCanonicalTypeUnqualified()))
+ 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);
+ 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);
+ }
+ // printing the canonical type for a template parameter prints as e.g.
+ // 'type-parameter-0-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",
+ DiagnosticIDs::Note)
+ << Arg->getSourceRange() << ArgTy->isPointerType() << DiagFromTy
+ << (FromTy != TargetTy) << TargetTy;
+ return;
}
- // printing the canonical type for a template parameter prints as e.g.
- // 'type-parameter-0-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",
- DiagnosticIDs::Note)
- << Arg->getSourceRange() << ArgTy->isPointerType() << DiagFromTy
- << (FromTy != RetTy) << RetTy;
}
} // namespace clang::tidy::llvm_check
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
index cc2717f527847..10aaf67892bd7 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/redundant-casting.rst
@@ -16,9 +16,6 @@ type, or a derived type thereof.
// replaced by:
A x = a;
- // Finds:
- bool b = isa<A>(a) // always true
-
struct B : public A {};
B b;
// Finds:
@@ -26,6 +23,13 @@ type, or a derived type thereof.
// replaced by:
A y = b;
+ struct C : public A {};
+ C c;
+ // Finds:
+ bool r1 = isa<A>(a) // always true
+ bool r2 = isa<A>(b) // always true
+ bool r3 = isa<B, C>(c) // always true
+
Supported functions:
- ``llvm::cast``
- ``llvm::cast_or_null``
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
index 9275f659b51a4..715826eee37c2 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
@@ -2,11 +2,11 @@
namespace llvm {
#define ISA_FUNCTION(name) \
-template <typename To, typename From> \
+template <typename ...To, typename From> \
[[nodiscard]] inline bool name(const From &Val) { \
return true; \
} \
-template <typename To, typename From> \
+template <typename ...To, typename From> \
[[nodiscard]] inline bool name(const From *Val) { \
return true; \
}
@@ -124,3 +124,25 @@ void testIsaConstPointer(const A* value) {
// CHECK-MESSAGES: :[[@LINE-2]]:27: note: source expression has pointee type 'A'
(void)b14;
}
+
+void testIsaMulti(A& value) {
+ bool b15 = llvm::isa<int, A>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:32: note: source expression has type 'A'
+ (void)b15;
+}
+
+void testIsaMultiUpcast(B& value) {
+ bool b16 = llvm::isa<int, A, B>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:35: note: source expression has type 'B', which is a subtype of 'A'
+ (void)b16;
+}
+
+template <typename T>
+void testIsaTemplateMulti(const T* value) {
+ bool b17 = llvm::isa<A, T>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:30: note: source expression has pointee type 'T'
+ (void)b17;
+}
>From e9595d1ac93006c953c66547955f6142e750fa73 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Wed, 8 Apr 2026 16:38:01 -0700
Subject: [PATCH 3/9] [clang-tidy] add additional namespace isa tests (NFC)
Bring `isa` test coverage up to that of `cast`.
---
.../checkers/llvm/redundant-isa.cpp | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
index 715826eee37c2..bb6941b81a712 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
@@ -146,3 +146,24 @@ void testIsaTemplateMulti(const T* value) {
// CHECK-MESSAGES: :[[@LINE-2]]:30: note: source expression has pointee type 'T'
(void)b17;
}
+
+namespace llvm {
+namespace magic {
+template <typename T>
+void testIsaImplicitlyLLVMUnresolve(T* value) {
+ bool b18 = isa<T>(value);
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: call to 'isa' always succeeds [llvm-redundant-casting]
+ // CHECK-MESSAGES: :[[@LINE-2]]:21: note: source expression has pointee type 'T'
+ (void)b18;
+}
+} // namespace magic
+} // namespace llvm
+
+
+ISA_FUNCTION(isa)
+
+void testIsaNonLLVM(A& value) {
+ bool b19 = isa<A>(value);
+ (void)b19;
+}
+
>From 4aebea6d6a5997928b2ba957102e302957761376 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Wed, 8 Apr 2026 16:46:25 -0700
Subject: [PATCH 4/9] clang format
---
.../clang-tidy/llvm/RedundantCastingCheck.cpp | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index f5f05ba1f496b..e1162b6a5d59f 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -71,13 +71,12 @@ void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
auto AnyIsaCalleeName =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
callee(expr(ignoringImpCasts(
- declRefExpr(
- to(namedDecl(hasAnyName(IsaFunctionNames), IsInLLVMNamespace)),
- hasAnyTemplateArgumentLoc(anything()))
+ declRefExpr(to(namedDecl(hasAnyName(IsaFunctionNames),
+ IsInLLVMNamespace)),
+ hasAnyTemplateArgumentLoc(anything()))
.bind("callee")))));
- Finder->addMatcher(callExpr(AnyIsaCalleeName, argumentCountIs(1))
- .bind("call"),
- this);
+ Finder->addMatcher(
+ callExpr(AnyIsaCalleeName, argumentCountIs(1)).bind("call"), this);
auto AnyIsaCalleeNameInUninstantiatedTemplate =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
@@ -172,13 +171,13 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
for (CanQualType TargetTy : TargetTypes) {
const auto *RetDecl = TargetTy->getAsCXXRecordDecl();
- const bool IsDerived = FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
+ const bool IsDerived =
+ FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
if (FromTy != TargetTy && !IsDerived)
continue;
if (IsIsa) {
- diag(Call->getExprLoc(), "call to '%0' always succeeds")
- << FuncName;
+ diag(Call->getExprLoc(), "call to '%0' always succeeds") << FuncName;
} else {
QualType ParentTy;
if (const auto *ParentCast = Nodes.getNodeAs<Expr>("parent_cast")) {
@@ -200,7 +199,8 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
/*RecordPaths=*/false,
/*DetectVirtual=*/false);
const bool IsDerivedFromParent =
- FromDecl && ParentDecl && FromDecl->isDerivedFrom(ParentDecl, Paths);
+ FromDecl && ParentDecl &&
+ FromDecl->isDerivedFrom(ParentDecl, Paths);
// For the following case a direct `cast<A>(d)` would be ambiguous:
// struct A {};
// struct B : A {};
@@ -225,10 +225,11 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
// printing the canonical type for a template parameter prints as e.g.
// 'type-parameter-0-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",
- DiagnosticIDs::Note)
+ 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 != TargetTy) << TargetTy;
return;
>From 15cc08b8c42fdde900f5fe995f8086103d8c954b Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Wed, 8 Apr 2026 16:58:18 -0700
Subject: [PATCH 5/9] 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 e1162b6a5d59f..4b7b9ff6785c6 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -126,9 +126,9 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
if (TArgs->size() != 2)
return;
- TemplateArgument TArg = TArgs->get(0);
+ const TemplateArgument TArg = TArgs->get(0);
if (TArg.getKind() == TemplateArgument::Type) {
- CanQualType TargetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
+ const CanQualType TargetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
TargetTypes.emplace_back(TargetTy);
} else if (TArg.getKind() == TemplateArgument::Pack) {
for (const auto &E : TArg.pack_elements()) {
@@ -169,7 +169,7 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
if (!IsIsa && TargetTypes.size() != 1)
return;
- for (CanQualType TargetTy : TargetTypes) {
+ for (const CanQualType TargetTy : TargetTypes) {
const auto *RetDecl = TargetTy->getAsCXXRecordDecl();
const bool IsDerived =
FromDecl && RetDecl && FromDecl->isDerivedFrom(RetDecl);
>From acc4ab9e84e3fdb4bf2d54e4b01791919ee37756 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Wed, 8 Apr 2026 16:59:23 -0700
Subject: [PATCH 6/9] clang format
---
clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 4b7b9ff6785c6..9cc2b0317643d 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -128,7 +128,8 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
const TemplateArgument TArg = TArgs->get(0);
if (TArg.getKind() == TemplateArgument::Type) {
- const CanQualType TargetTy = TArg.getAsType()->getCanonicalTypeUnqualified();
+ const CanQualType TargetTy =
+ TArg.getAsType()->getCanonicalTypeUnqualified();
TargetTypes.emplace_back(TargetTy);
} else if (TArg.getKind() == TemplateArgument::Pack) {
for (const auto &E : TArg.pack_elements()) {
>From a1c1ed6789852b37a4591ed1da3b007fe276b30d Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Thu, 9 Apr 2026 09:13:08 -0700
Subject: [PATCH 7/9] [clang-tidy] remove unnecessary impcast peeks (NFC)
This checker ignores implicit AST nodes, so skipping over implicit casts
is not necessary.
---
.../clang-tidy/llvm/RedundantCastingCheck.cpp | 40 +++++++++----------
1 file changed, 18 insertions(+), 22 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index 9cc2b0317643d..fd96a4f685b29 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -50,17 +50,15 @@ void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
namespaceDecl(hasName("llvm"), hasDeclContext(translationUnitDecl())));
auto AnyCastCalleeName =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
- callee(expr(ignoringImpCasts(
- declRefExpr(to(namedDecl(hasAnyName(CastFunctionNames),
- IsInLLVMNamespace)),
- templateArgumentLocCountIs(1))
- .bind("callee")))));
- auto AnyCastCalleeNameInUninstantiatedTemplate =
- allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
- callee(expr(ignoringImpCasts(
- unresolvedLookupExpr(hasAnyUnresolvedName(CastFunctionNames),
- templateArgumentLocCountIs(1))
- .bind("callee")))));
+ callee(expr(declRefExpr(to(namedDecl(hasAnyName(CastFunctionNames),
+ IsInLLVMNamespace)),
+ templateArgumentLocCountIs(1))
+ .bind("callee"))));
+ auto AnyCastCalleeNameInUninstantiatedTemplate = allOf(
+ unless(isMacroID()), unless(cxxMemberCallExpr()),
+ callee(expr(unresolvedLookupExpr(hasAnyUnresolvedName(CastFunctionNames),
+ templateArgumentLocCountIs(1))
+ .bind("callee"))));
Finder->addMatcher(
callExpr(AnyCastCalleeName, argumentCountIs(1),
optionally(
@@ -70,20 +68,18 @@ void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
auto AnyIsaCalleeName =
allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
- callee(expr(ignoringImpCasts(
- declRefExpr(to(namedDecl(hasAnyName(IsaFunctionNames),
- IsInLLVMNamespace)),
- hasAnyTemplateArgumentLoc(anything()))
- .bind("callee")))));
+ callee(expr(declRefExpr(to(namedDecl(hasAnyName(IsaFunctionNames),
+ IsInLLVMNamespace)),
+ hasAnyTemplateArgumentLoc(anything()))
+ .bind("callee"))));
Finder->addMatcher(
callExpr(AnyIsaCalleeName, argumentCountIs(1)).bind("call"), this);
- auto AnyIsaCalleeNameInUninstantiatedTemplate =
- allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
- callee(expr(ignoringImpCasts(
- unresolvedLookupExpr(hasAnyUnresolvedName(IsaFunctionNames),
- hasAnyTemplateArgumentLoc(anything()))
- .bind("callee")))));
+ auto AnyIsaCalleeNameInUninstantiatedTemplate = allOf(
+ unless(isMacroID()), unless(cxxMemberCallExpr()),
+ callee(expr(unresolvedLookupExpr(hasAnyUnresolvedName(IsaFunctionNames),
+ hasAnyTemplateArgumentLoc(anything()))
+ .bind("callee"))));
Finder->addMatcher(
callExpr(
anyOf(AnyCastCalleeNameInUninstantiatedTemplate,
>From 125ab89260c83f316ad01ab415958a8693150603 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Thu, 9 Apr 2026 12:02:05 -0700
Subject: [PATCH 8/9] [clang-tidy] simplify target type logic (NFC)
This extracts the handling of TemplateArguments from the two branches of
an if statement.
---
.../clang-tidy/llvm/RedundantCastingCheck.cpp | 41 ++++++++-----------
1 file changed, 18 insertions(+), 23 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
index fd96a4f685b29..f850dae3a7f77 100644
--- a/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/RedundantCastingCheck.cpp
@@ -114,15 +114,29 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
const auto &Nodes = Result.Nodes;
const auto *Call = Nodes.getNodeAs<CallExpr>("call");
- llvm::SmallVector<CanQualType, 4> TargetTypes;
+ ArrayRef<TemplateArgumentLoc> TArgLocs;
std::string FuncName;
if (const auto *ResolvedCallee = Nodes.getNodeAs<DeclRefExpr>("callee")) {
+ TArgLocs = ResolvedCallee->template_arguments();
const auto *F = cast<FunctionDecl>(ResolvedCallee->getDecl());
- const auto *TArgs = F->getTemplateSpecializationArgs();
- if (TArgs->size() != 2)
+ FuncName = F->getName();
+ } else if (const auto *UnresolvedCallee =
+ Nodes.getNodeAs<UnresolvedLookupExpr>("callee")) {
+ const bool IsExplicitlyLLVM =
+ isLLVMNamespace(UnresolvedCallee->getQualifier());
+ const auto *CallerNS = Nodes.getNodeAs<NamedDecl>("llvm_ns");
+ if (!IsExplicitlyLLVM && !CallerNS)
return;
- const TemplateArgument TArg = TArgs->get(0);
+ TArgLocs = UnresolvedCallee->template_arguments();
+ FuncName = UnresolvedCallee->getName().getAsString();
+ } else {
+ llvm_unreachable("unexpected callee kind");
+ }
+
+ llvm::SmallVector<CanQualType, 4> TargetTypes;
+ for (const auto &TArgLoc : TArgLocs) {
+ const TemplateArgument TArg = TArgLoc.getArgument();
if (TArg.getKind() == TemplateArgument::Type) {
const CanQualType TargetTy =
TArg.getAsType()->getCanonicalTypeUnqualified();
@@ -136,25 +150,6 @@ void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
} else {
llvm_unreachable("unexpected template argument");
}
-
- FuncName = F->getName();
- } else if (const auto *UnresolvedCallee =
- Nodes.getNodeAs<UnresolvedLookupExpr>("callee")) {
- const bool IsExplicitlyLLVM =
- isLLVMNamespace(UnresolvedCallee->getQualifier());
- const auto *CallerNS = Nodes.getNodeAs<NamedDecl>("llvm_ns");
- if (!IsExplicitlyLLVM && !CallerNS)
- return;
-
- for (const auto &TArgLoc : UnresolvedCallee->template_arguments()) {
- const TemplateArgument TArg = TArgLoc.getArgument();
- if (TArg.getKind() != TemplateArgument::Type)
- return;
- TargetTypes.emplace_back(TArg.getAsType()->getCanonicalTypeUnqualified());
- }
- FuncName = UnresolvedCallee->getName().getAsString();
- } else {
- llvm_unreachable("unexpected callee kind");
}
const auto *Arg = Call->getArg(0);
>From 7ec57650fb9fb30707fe456a9cd77d56403a0fd2 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Thu, 9 Apr 2026 12:03:49 -0700
Subject: [PATCH 9/9] more generic test
---
.../test/clang-tidy/checkers/llvm/redundant-isa.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
index bb6941b81a712..dad41780530c5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/redundant-isa.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 ISA_FUNCTION(name) \
More information about the cfe-commits
mailing list