[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
Thu Jun 18 06:18:30 PDT 2026
https://github.com/voyager-jhk updated https://github.com/llvm/llvm-project/pull/198085
>From 3c733ec360faafa4511f72422ec0bf22fe0ea74a 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.
Following Clang's diagnostic `aka` logic, this patch uses the AST Printer
to stringify and compare the `NestedNameSpecifier` and `TemplateArguments`.
This safely preserves sugared types while natively normalizing all whitespaces
and newlines, ensuring robustness against multiline formatting changes.
Fixes #145415
---
.../misc/RedundantExpressionCheck.cpp | 41 ++++++++++++++++-
clang-tools-extra/docs/ReleaseNotes.rst | 9 +++-
.../checkers/misc/redundant-expression.cpp | 45 +++++++++++++++++++
3 files changed, 91 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
index de489da96de3b..b5c9e597669fb 100644
--- a/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
@@ -19,6 +19,7 @@
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
@@ -44,6 +45,42 @@ static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) {
return Value < Result;
}
+static bool areEquivalentDeclRefExpr(const DeclRefExpr *L,
+ const DeclRefExpr *R) {
+ if (L->getDecl() != R->getDecl())
+ return false;
+
+ const PrintingPolicy &Policy =
+ L->getDecl()->getASTContext().getPrintingPolicy();
+
+ if (L->hasQualifier() != R->hasQualifier())
+ return false;
+ if (L->hasQualifier()) {
+ std::string LQual, RQual;
+ llvm::raw_string_ostream LOS(LQual), ROS(RQual);
+ L->getQualifier().print(LOS, Policy);
+ R->getQualifier().print(ROS, Policy);
+ if (LQual != RQual)
+ 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) {
+ std::string LArg, RArg;
+ llvm::raw_string_ostream LOS(LArg), ROS(RArg);
+ L->getTemplateArgs()[I].getArgument().print(Policy, LOS, true);
+ R->getTemplateArgs()[I].getArgument().print(Policy, ROS, true);
+ if (LArg != RArg)
+ return false;
+ }
+ }
+ return true;
+}
+
static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
if (!Left || !Right)
return !Left && !Right;
@@ -98,8 +135,8 @@ static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
return cast<DependentScopeDeclRefExpr>(Left)->getQualifier() ==
cast<DependentScopeDeclRefExpr>(Right)->getQualifier();
case Stmt::DeclRefExprClass:
- return cast<DeclRefExpr>(Left)->getDecl() ==
- cast<DeclRefExpr>(Right)->getDecl();
+ return areEquivalentDeclRefExpr(cast<DeclRefExpr>(Left),
+ cast<DeclRefExpr>(Right));
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 c369b1fd8b373..5b7f305d1ba6f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -549,8 +549,13 @@ Changes in existing checks
virtual inheritance causes concrete bases to be counted more than once.
- Improved :doc:`misc-redundant-expression
- <clang-tidy/checks/misc/redundant-expression>` check by fixing a crash when
- evaluating bitwise comparisons against integer constants wider than 64 bits.
+ <clang-tidy/checks/misc/redundant-expression>` check:
+
+ - Fixed a crash when evaluating bitwise comparisons against integer constants
+ wider than 64 bits.
+
+ - Avoided 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.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression.cpp
index 7ad5ca6d0b996..b5d9cebd579c6 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/redundant-expression.cpp
@@ -1126,3 +1126,48 @@ namespace PR35857 {
decltype(x + y - (x + y)) z = 10;
}
}
+
+namespace GH145415 {
+
+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>;
+
+namespace N {
+ int Value = 0;
+}
+using N::Value;
+
+template <typename T> void myFunc(T) {}
+template <typename T, typename U = int> void myDefaultFunc(T) {}
+using FuncPtr = void (*)(int);
+
+void TestGH145415() {
+
+ bool b1 = std::tuple_size<MonthArray>::value == std::tuple_size<ZodiacArray>::value;
+ bool b2 = std::tuple_size_v<MonthArray> == std::tuple_size_v<ZodiacArray>;
+
+ bool b3 = std::tuple_size<MonthArray>::value == std::tuple_size<MonthArray>::value;
+ // CHECK-MESSAGES: :[[@LINE-1]]:48: warning: both sides of operator are equivalent
+
+ bool b4 = std::tuple_size_v<
+ MonthArray> ==
+ std::tuple_size_v<MonthArray>;
+ // CHECK-MESSAGES: :[[@LINE-2]]:29: warning: both sides of operator are equivalent
+
+ bool b5 = N::Value == Value;
+
+ bool b6 = FuncPtr(myFunc<int>) == FuncPtr(myFunc);
+
+ bool b7 = FuncPtr(myDefaultFunc<int>) == FuncPtr(myDefaultFunc<int, int>);
+}
+
+} // namespace GH145415
More information about the cfe-commits
mailing list