[clang-tools-extra] [clang-tidy] Fix false positive in misc-redundant-expression with type aliases (PR #198085)
Peiqi Li via cfe-commits
cfe-commits at lists.llvm.org
Sat May 30 22:30:02 PDT 2026
https://github.com/voyager-jhk updated https://github.com/llvm/llvm-project/pull/198085
>From 0f61fa4e9cfeff9a46540f08e9f82dade8c068c6 Mon Sep 17 00:00:00 2001
From: voyager-jhk <voyager.lpq at gmail.com>
Date: Sat, 16 May 2026 20:50:59 +0800
Subject: [PATCH] [clang-tidy] Fix false positive in misc-redundant-expression
with type aliases
The `misc-redundant-expression` check previously flagged expressions as redundant
if their underlying `DeclRefExpr` pointed to the same declaration. This caused
false positives when comparing identical values accessed through distinct type
aliases (sugared types).
This patch uses `printPretty` to compare the source-level spellings of the
expressions, ensuring nested name specifiers and explicit template arguments
are correctly differentiated.
Fixes #145415
---
.../misc/RedundantExpressionCheck.cpp | 353 +++++++++++++++++-
clang-tools-extra/docs/ReleaseNotes.rst | 5 +
.../redundant-expression-type-aliases.cpp | 30 ++
3 files changed, 385 insertions(+), 3 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression-type-aliases.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
index 9db297990b274..78efeeaa3d900 100644
--- a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
@@ -24,6 +24,7 @@
#include <cstdint>
#include <optional>
#include <string>
+#include <tuple>
using namespace clang::ast_matchers;
using namespace clang::tidy::matchers;
@@ -44,6 +45,331 @@ static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) {
return Value < Result;
}
+static bool areEquivalentExpr(const Expr *Left, const Expr *Right);
+
+static bool areEquivalentType(QualType Left, QualType Right);
+static bool
+areEquivalentNestedNameSpecifiers(NestedNameSpecifier Left,
+ NestedNameSpecifier Right);
+static bool
+areEquivalentNestedNameSpecifierLocs(NestedNameSpecifierLoc Left,
+ NestedNameSpecifierLoc Right);
+static bool areEquivalentTemplateNames(TemplateName Left, TemplateName Right);
+static bool areEquivalentTemplateArguments(const TemplateArgument &Left,
+ const TemplateArgument &Right);
+
+static bool areSameDecl(const Decl *Left, const Decl *Right) {
+ if (!Left || !Right)
+ return !Left && !Right;
+ return Left->getCanonicalDecl() == Right->getCanonicalDecl();
+}
+
+static bool areEquivalentTemplateNames(TemplateName Left, TemplateName Right) {
+ if (Left.getKind() != Right.getKind())
+ return false;
+
+ NestedNameSpecifier LeftQualifier;
+ bool LeftHasTemplateKeyword = false;
+ std::tie(LeftQualifier, LeftHasTemplateKeyword) =
+ Left.getQualifierAndTemplateKeyword();
+ NestedNameSpecifier RightQualifier;
+ bool RightHasTemplateKeyword = false;
+ std::tie(RightQualifier, RightHasTemplateKeyword) =
+ Right.getQualifierAndTemplateKeyword();
+ if (LeftHasTemplateKeyword != RightHasTemplateKeyword ||
+ !areEquivalentNestedNameSpecifiers(LeftQualifier, RightQualifier))
+ return false;
+
+ switch (Left.getKind()) {
+ case TemplateName::Template:
+ case TemplateName::UsingTemplate:
+ case TemplateName::QualifiedTemplate:
+ return areSameDecl(Left.getAsTemplateDecl(/*IgnoreDeduced=*/false),
+ Right.getAsTemplateDecl(/*IgnoreDeduced=*/false)) &&
+ areSameDecl(Left.getAsUsingShadowDecl(),
+ Right.getAsUsingShadowDecl());
+ case TemplateName::DependentTemplate: {
+ const auto *LeftDependent = Left.getAsDependentTemplateName();
+ const auto *RightDependent = Right.getAsDependentTemplateName();
+ return LeftDependent->getName() == RightDependent->getName();
+ }
+ case TemplateName::SubstTemplateTemplateParm: {
+ const auto *LeftSubst = Left.getAsSubstTemplateTemplateParm();
+ const auto *RightSubst = Right.getAsSubstTemplateTemplateParm();
+ return LeftSubst->getAssociatedDecl() ==
+ RightSubst->getAssociatedDecl() &&
+ LeftSubst->getIndex() == RightSubst->getIndex() &&
+ LeftSubst->getPackIndex() == RightSubst->getPackIndex() &&
+ LeftSubst->getFinal() == RightSubst->getFinal() &&
+ areEquivalentTemplateNames(LeftSubst->getReplacement(),
+ RightSubst->getReplacement());
+ }
+ case TemplateName::SubstTemplateTemplateParmPack:
+ case TemplateName::OverloadedTemplate:
+ case TemplateName::AssumedTemplate:
+ case TemplateName::DeducedTemplate:
+ return Left == Right;
+ }
+ llvm_unreachable("Unhandled TemplateName kind");
+}
+
+static bool areEquivalentTemplateArguments(const TemplateArgument &Left,
+ const TemplateArgument &Right) {
+ if (Left.getKind() != Right.getKind())
+ return false;
+
+ switch (Left.getKind()) {
+ case TemplateArgument::Null:
+ return true;
+ case TemplateArgument::Type:
+ return areEquivalentType(Left.getAsType(), Right.getAsType());
+ case TemplateArgument::Declaration:
+ return areSameDecl(Left.getAsDecl(), Right.getAsDecl()) &&
+ areEquivalentType(Left.getParamTypeForDecl(),
+ Right.getParamTypeForDecl());
+ case TemplateArgument::NullPtr:
+ return areEquivalentType(Left.getNullPtrType(), Right.getNullPtrType());
+ case TemplateArgument::Integral:
+ return Left.getAsIntegral() == Right.getAsIntegral() &&
+ areEquivalentType(Left.getIntegralType(), Right.getIntegralType());
+ case TemplateArgument::StructuralValue:
+ return Left.structurallyEquals(Right);
+ case TemplateArgument::Template:
+ return areEquivalentTemplateNames(Left.getAsTemplate(),
+ Right.getAsTemplate());
+ case TemplateArgument::TemplateExpansion:
+ return Left.getNumTemplateExpansions() == Right.getNumTemplateExpansions() &&
+ areEquivalentTemplateNames(Left.getAsTemplateOrTemplatePattern(),
+ Right.getAsTemplateOrTemplatePattern());
+ case TemplateArgument::Expression:
+ return areEquivalentExpr(Left.getAsExpr(), Right.getAsExpr());
+ case TemplateArgument::Pack: {
+ ArrayRef<TemplateArgument> LeftPack = Left.pack_elements();
+ ArrayRef<TemplateArgument> RightPack = Right.pack_elements();
+ if (LeftPack.size() != RightPack.size())
+ return false;
+ for (unsigned I = 0, E = LeftPack.size(); I != E; ++I)
+ if (!areEquivalentTemplateArguments(LeftPack[I], RightPack[I]))
+ return false;
+ return true;
+ }
+ }
+ llvm_unreachable("Unhandled TemplateArgument kind");
+}
+
+static bool
+areEquivalentTemplateArgumentLocs(const TemplateArgumentLoc &Left,
+ const TemplateArgumentLoc &Right) {
+ if (Left.getArgument().getKind() != Right.getArgument().getKind())
+ return false;
+
+ switch (Left.getArgument().getKind()) {
+ case TemplateArgument::Type: {
+ const TypeSourceInfo *LeftInfo = Left.getTypeSourceInfo();
+ const TypeSourceInfo *RightInfo = Right.getTypeSourceInfo();
+ if (!LeftInfo || !RightInfo)
+ return LeftInfo == RightInfo &&
+ areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ return areEquivalentType(LeftInfo->getTypeLoc().getType(),
+ RightInfo->getTypeLoc().getType());
+ }
+ case TemplateArgument::Expression:
+ return areEquivalentExpr(Left.getSourceExpression(),
+ Right.getSourceExpression());
+ case TemplateArgument::Declaration:
+ if (!Left.getLocInfo().isTrivial() || !Right.getLocInfo().isTrivial())
+ return areEquivalentExpr(Left.getSourceDeclExpression(),
+ Right.getSourceDeclExpression());
+ return areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ case TemplateArgument::NullPtr:
+ if (!Left.getLocInfo().isTrivial() || !Right.getLocInfo().isTrivial())
+ return areEquivalentExpr(Left.getSourceNullPtrExpression(),
+ Right.getSourceNullPtrExpression());
+ return areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ case TemplateArgument::Integral:
+ if (!Left.getLocInfo().isTrivial() || !Right.getLocInfo().isTrivial())
+ return areEquivalentExpr(Left.getSourceIntegralExpression(),
+ Right.getSourceIntegralExpression());
+ return areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ case TemplateArgument::StructuralValue:
+ if (!Left.getLocInfo().isTrivial() || !Right.getLocInfo().isTrivial())
+ return areEquivalentExpr(Left.getSourceStructuralValueExpression(),
+ Right.getSourceStructuralValueExpression());
+ return areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ case TemplateArgument::Template:
+ case TemplateArgument::TemplateExpansion:
+ return areEquivalentNestedNameSpecifierLocs(Left.getTemplateQualifierLoc(),
+ Right.getTemplateQualifierLoc()) &&
+ areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ case TemplateArgument::Null:
+ case TemplateArgument::Pack:
+ return areEquivalentTemplateArguments(Left.getArgument(),
+ Right.getArgument());
+ }
+ llvm_unreachable("Unhandled TemplateArgument kind");
+}
+
+static bool
+areEquivalentNestedNameSpecifiers(NestedNameSpecifier Left,
+ NestedNameSpecifier Right) {
+ if (!Left || !Right)
+ return !Left && !Right;
+ if (Left.getKind() != Right.getKind())
+ return false;
+
+ switch (Left.getKind()) {
+ case NestedNameSpecifier::Kind::Null:
+ case NestedNameSpecifier::Kind::Global:
+ return true;
+ case NestedNameSpecifier::Kind::Namespace: {
+ NamespaceAndPrefix LeftNamespace = Left.getAsNamespaceAndPrefix();
+ NamespaceAndPrefix RightNamespace = Right.getAsNamespaceAndPrefix();
+ return LeftNamespace.Namespace == RightNamespace.Namespace &&
+ areEquivalentNestedNameSpecifiers(LeftNamespace.Prefix,
+ RightNamespace.Prefix);
+ }
+ case NestedNameSpecifier::Kind::Type:
+ return areEquivalentType(QualType(Left.getAsType(), 0),
+ QualType(Right.getAsType(), 0));
+ case NestedNameSpecifier::Kind::MicrosoftSuper:
+ return areSameDecl(Left.getAsMicrosoftSuper(), Right.getAsMicrosoftSuper());
+ }
+ llvm_unreachable("Unhandled NestedNameSpecifier kind");
+}
+
+static bool
+areEquivalentNestedNameSpecifierLocs(NestedNameSpecifierLoc Left,
+ NestedNameSpecifierLoc Right) {
+ if (!Left || !Right)
+ return !Left && !Right;
+
+ NestedNameSpecifier LeftSpecifier = Left.getNestedNameSpecifier();
+ NestedNameSpecifier RightSpecifier = Right.getNestedNameSpecifier();
+ if (LeftSpecifier.getKind() != RightSpecifier.getKind())
+ return false;
+
+ switch (LeftSpecifier.getKind()) {
+ case NestedNameSpecifier::Kind::Null:
+ case NestedNameSpecifier::Kind::Global:
+ return true;
+ case NestedNameSpecifier::Kind::Namespace: {
+ NamespaceAndPrefixLoc LeftNamespace = Left.getAsNamespaceAndPrefix();
+ NamespaceAndPrefixLoc RightNamespace = Right.getAsNamespaceAndPrefix();
+ return LeftNamespace.Namespace == RightNamespace.Namespace &&
+ areEquivalentNestedNameSpecifierLocs(LeftNamespace.Prefix,
+ RightNamespace.Prefix);
+ }
+ case NestedNameSpecifier::Kind::Type: {
+ TypeLoc LeftLoc = Left.getAsTypeLoc();
+ TypeLoc RightLoc = Right.getAsTypeLoc();
+ if (LeftLoc && RightLoc)
+ return areEquivalentType(LeftLoc.getType(), RightLoc.getType());
+ return !LeftLoc && !RightLoc &&
+ areEquivalentType(QualType(LeftSpecifier.getAsType(), 0),
+ QualType(RightSpecifier.getAsType(), 0));
+ }
+ case NestedNameSpecifier::Kind::MicrosoftSuper:
+ return areSameDecl(LeftSpecifier.getAsMicrosoftSuper(),
+ RightSpecifier.getAsMicrosoftSuper());
+ }
+ llvm_unreachable("Unhandled NestedNameSpecifier kind");
+}
+
+static bool areEquivalentType(QualType Left, QualType Right) {
+ if (Left.isNull() || Right.isNull())
+ return Left.isNull() && Right.isNull();
+ if (Left.getLocalCVRQualifiers() != Right.getLocalCVRQualifiers())
+ return false;
+
+ Left = Left.getLocalUnqualifiedType();
+ Right = Right.getLocalUnqualifiedType();
+ if (Left == Right)
+ return true;
+
+ const Type *LeftType = Left.getTypePtr();
+ const Type *RightType = Right.getTypePtr();
+ if (LeftType->getTypeClass() != RightType->getTypeClass())
+ return false;
+
+ switch (LeftType->getTypeClass()) {
+ case Type::Builtin:
+ return LeftType == RightType;
+ case Type::Typedef: {
+ const auto *LeftTypedef = cast<TypedefType>(LeftType);
+ const auto *RightTypedef = cast<TypedefType>(RightType);
+ return LeftTypedef->getKeyword() == RightTypedef->getKeyword() &&
+ areSameDecl(LeftTypedef->getDecl(), RightTypedef->getDecl()) &&
+ areEquivalentNestedNameSpecifiers(LeftTypedef->getQualifier(),
+ RightTypedef->getQualifier());
+ }
+ case Type::Using: {
+ const auto *LeftUsing = cast<UsingType>(LeftType);
+ const auto *RightUsing = cast<UsingType>(RightType);
+ return LeftUsing->getKeyword() == RightUsing->getKeyword() &&
+ areSameDecl(LeftUsing->getDecl(), RightUsing->getDecl()) &&
+ areEquivalentNestedNameSpecifiers(LeftUsing->getQualifier(),
+ RightUsing->getQualifier());
+ }
+ case Type::TemplateSpecialization: {
+ const auto *LeftTemplate = cast<TemplateSpecializationType>(LeftType);
+ const auto *RightTemplate = cast<TemplateSpecializationType>(RightType);
+ ArrayRef<TemplateArgument> LeftArguments =
+ LeftTemplate->template_arguments();
+ ArrayRef<TemplateArgument> RightArguments =
+ RightTemplate->template_arguments();
+ if (LeftArguments.size() != RightArguments.size())
+ return false;
+ for (unsigned I = 0, E = LeftArguments.size(); I != E; ++I)
+ if (!areEquivalentTemplateArguments(LeftArguments[I],
+ RightArguments[I]))
+ return false;
+ return LeftTemplate->getKeyword() == RightTemplate->getKeyword() &&
+ areEquivalentTemplateNames(LeftTemplate->getTemplateName(),
+ RightTemplate->getTemplateName());
+ }
+ case Type::Pointer:
+ return areEquivalentType(cast<PointerType>(LeftType)->getPointeeType(),
+ cast<PointerType>(RightType)->getPointeeType());
+ case Type::LValueReference:
+ case Type::RValueReference:
+ return areEquivalentType(cast<ReferenceType>(LeftType)->getPointeeType(),
+ cast<ReferenceType>(RightType)->getPointeeType());
+ case Type::Paren:
+ return areEquivalentType(cast<ParenType>(LeftType)->getInnerType(),
+ cast<ParenType>(RightType)->getInnerType());
+ case Type::ConstantArray: {
+ const auto *LeftArray = cast<ConstantArrayType>(LeftType);
+ const auto *RightArray = cast<ConstantArrayType>(RightType);
+ return LeftArray->getSize() == RightArray->getSize() &&
+ LeftArray->getSizeModifier() == RightArray->getSizeModifier() &&
+ LeftArray->getIndexTypeCVRQualifiers() ==
+ RightArray->getIndexTypeCVRQualifiers() &&
+ areEquivalentType(LeftArray->getElementType(),
+ RightArray->getElementType());
+ }
+ case Type::Record:
+ case Type::Enum:
+ case Type::InjectedClassName: {
+ const auto *LeftTag = cast<TagType>(LeftType);
+ const auto *RightTag = cast<TagType>(RightType);
+ return LeftTag->getKeyword() == RightTag->getKeyword() &&
+ areSameDecl(LeftTag->getDecl(), RightTag->getDecl()) &&
+ areEquivalentNestedNameSpecifiers(LeftTag->getQualifier(),
+ RightTag->getQualifier());
+ }
+ default:
+ if (Left != Left.getCanonicalType() || Right != Right.getCanonicalType())
+ return false;
+ return Left.getCanonicalType() == Right.getCanonicalType();
+ }
+}
+
static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
if (!Left || !Right)
return !Left && !Right;
@@ -97,9 +423,30 @@ static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
return false;
return cast<DependentScopeDeclRefExpr>(Left)->getQualifier() ==
cast<DependentScopeDeclRefExpr>(Right)->getQualifier();
- case Stmt::DeclRefExprClass:
- return cast<DeclRefExpr>(Left)->getDecl() ==
- cast<DeclRefExpr>(Right)->getDecl();
+ case Stmt::DeclRefExprClass: {
+ const auto *L = cast<DeclRefExpr>(Left);
+ const auto *R = cast<DeclRefExpr>(Right);
+
+ if (L->getDecl() != R->getDecl() || L->getFoundDecl() != R->getFoundDecl())
+ return false;
+
+ if (!areEquivalentNestedNameSpecifierLocs(L->getQualifierLoc(),
+ R->getQualifierLoc()))
+ return false;
+
+ if (L->hasExplicitTemplateArgs() != R->hasExplicitTemplateArgs())
+ return false;
+ if (L->hasExplicitTemplateArgs()) {
+ if (L->getNumTemplateArgs() != R->getNumTemplateArgs())
+ return false;
+ for (unsigned I = 0, E = L->getNumTemplateArgs(); I != E; ++I) {
+ if (!areEquivalentTemplateArgumentLocs(L->getTemplateArgs()[I],
+ R->getTemplateArgs()[I]))
+ return false;
+ }
+ }
+ return true;
+ }
case Stmt::MemberExprClass:
return cast<MemberExpr>(Left)->getMemberDecl() ==
cast<MemberExpr>(Right)->getMemberDecl();
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 89fb1684bba7c..c4f9b86bdd87c 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -500,6 +500,11 @@ Changes in existing checks
<clang-tidy/checks/misc/multiple-inheritance>` by avoiding false positives when
virtual inheritance causes concrete bases to be counted more than once.
+- Improved :doc:`misc-redundant-expression
+ <clang-tidy/checks/misc/redundant-expression>` check to avoid false
+ positives when comparing expressions that are structurally identical but
+ use different type aliases.
+
- Improved :doc:`misc-throw-by-value-catch-by-reference
<clang-tidy/checks/misc/throw-by-value-catch-by-reference>` check:
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression-type-aliases.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression-type-aliases.cpp
new file mode 100644
index 0000000000000..e61655b38acd0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression-type-aliases.cpp
@@ -0,0 +1,30 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s misc-redundant-expression %t
+
+namespace std {
+template <class T, int N> struct array {};
+template <class T> struct tuple_size;
+template <class T, int N> struct tuple_size<array<T, N>> {
+ static constexpr int value = N;
+};
+template <class T> constexpr int tuple_size_v = tuple_size<T>::value;
+} // namespace std
+
+using MonthArray = std::array<int, 12>;
+using ZodiacArray = std::array<int, 12>;
+
+void test() {
+ // 1. False positive cases (Should NOT warn):
+ bool b1 = std::tuple_size<MonthArray>::value == std::tuple_size<ZodiacArray>::value;
+ bool b2 = std::tuple_size_v<MonthArray> == std::tuple_size_v<ZodiacArray>;
+
+ // 2. True positive cases (Must warn):
+ bool b3 = std::tuple_size<MonthArray>::value == std::tuple_size<MonthArray>::value;
+
+ // 3. Multiline true positive case (Requested by vbvictor to prove format-independence):
+ bool b4 = std::tuple_size_v<
+ MonthArray> ==
+ std::tuple_size_v<MonthArray>;
+}
+
+// CHECK-MESSAGES: :21:48: warning: both sides of operator are equivalent [misc-redundant-expression]
+// CHECK-MESSAGES: :25:29: warning: both sides of operator are equivalent [misc-redundant-expression]
More information about the cfe-commits
mailing list