[Clang] Warning as error for fold expressions over comparison operators (#136836)

We made chained comparisons an error.
Fold-expressions over a comparison operator produce chained comparisons,
so we should be consistent there too.

We only emit the warning when instantiating the fold expression so as
not to warn on types with user-defined comparisons.

Partially addresses #129570
This commit is contained in:
cor3ntin
2025-05-01 17:00:03 +02:00
committed by GitHub
parent abd2c07e39
commit 7ffaaf4752
7 changed files with 65 additions and 15 deletions

View File

@@ -389,6 +389,7 @@ Improvements to Clang's diagnostics
under the subgroup ``-Wunsafe-buffer-usage-in-libc-call``.
- Diagnostics on chained comparisons (``a < b < c``) are now an error by default. This can be disabled with
``-Wno-error=parentheses``.
- Similarly, fold expressions over a comparison operator are now an error by default.
- Clang now better preserves the sugared types of pointers to member.
- Clang now better preserves the presence of the template keyword with dependent
prefixes.

View File

@@ -7204,6 +7204,11 @@ def warn_consecutive_comparison : Warning<
"chained comparison 'X %0 Y %1 Z' does not behave the same as a mathematical expression">,
InGroup<Parentheses>, DefaultError;
def warn_comparison_in_fold_expression : Warning<
"comparison in fold expression would evaluate to '(X %0 Y) %0 Z' "
"which does not behave the same as a mathematical expression">,
InGroup<Parentheses>, DefaultError;
def warn_enum_constant_in_bool_context : Warning<
"converting the enum constant to a boolean">,
InGroup<IntInBoolContext>, DefaultIgnore;

View File

@@ -7215,13 +7215,15 @@ public:
ExprResult ActOnBinOp(Scope *S, SourceLocation TokLoc, tok::TokenKind Kind,
Expr *LHSExpr, Expr *RHSExpr);
ExprResult BuildBinOp(Scope *S, SourceLocation OpLoc, BinaryOperatorKind Opc,
Expr *LHSExpr, Expr *RHSExpr);
Expr *LHSExpr, Expr *RHSExpr,
bool ForFoldExpression = false);
/// CreateBuiltinBinOp - Creates a new built-in binary operation with
/// operator @p Opc at location @c TokLoc. This routine only supports
/// built-in operations; ActOnBinOp handles overloaded operators.
ExprResult CreateBuiltinBinOp(SourceLocation OpLoc, BinaryOperatorKind Opc,
Expr *LHSExpr, Expr *RHSExpr);
Expr *LHSExpr, Expr *RHSExpr,
bool ForFoldExpression = false);
void LookupBinOp(Scope *S, SourceLocation OpLoc, BinaryOperatorKind Opc,
UnresolvedSetImpl &Functions);

View File

@@ -14947,8 +14947,8 @@ static bool needsConversionOfHalfVec(bool OpRequiresConversion, ASTContext &Ctx,
}
ExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc,
BinaryOperatorKind Opc,
Expr *LHSExpr, Expr *RHSExpr) {
BinaryOperatorKind Opc, Expr *LHSExpr,
Expr *RHSExpr, bool ForFoldExpression) {
if (getLangOpts().CPlusPlus11 && isa<InitListExpr>(RHSExpr)) {
// The syntax only allows initializer lists on the RHS of assignment,
// so we don't need to worry about accepting invalid code for
@@ -15079,7 +15079,7 @@ ExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc,
ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc);
if (const auto *BI = dyn_cast<BinaryOperator>(LHSExpr);
BI && BI->isComparisonOp())
!ForFoldExpression && BI && BI->isComparisonOp())
Diag(OpLoc, diag::warn_consecutive_comparison)
<< BI->getOpcodeStr() << BinaryOperator::getOpcodeStr(Opc);
@@ -15490,8 +15490,8 @@ static ExprResult BuildOverloadedBinOp(Sema &S, Scope *Sc, SourceLocation OpLoc,
}
ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
BinaryOperatorKind Opc,
Expr *LHSExpr, Expr *RHSExpr) {
BinaryOperatorKind Opc, Expr *LHSExpr,
Expr *RHSExpr, bool ForFoldExpression) {
ExprResult LHS, RHS;
std::tie(LHS, RHS) = CorrectDelayedTyposInBinOp(*this, Opc, LHSExpr, RHSExpr);
if (!LHS.isUsable() || !RHS.isUsable())
@@ -15565,7 +15565,8 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
LHSExpr->getType()->isOverloadableType()))
return BuildOverloadedBinOp(*this, S, OpLoc, Opc, LHSExpr, RHSExpr);
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr);
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr,
ForFoldExpression);
}
// Don't resolve overloads if the other type is overloadable.
@@ -15629,7 +15630,7 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
}
// Build a built-in binary operation.
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr);
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr, ForFoldExpression);
}
static bool isOverflowingIntegerType(ASTContext &Ctx, QualType T) {

View File

@@ -2967,10 +2967,11 @@ public:
///
/// By default, performs semantic analysis to build the new expression.
/// Subclasses may override this routine to provide different behavior.
ExprResult RebuildBinaryOperator(SourceLocation OpLoc,
BinaryOperatorKind Opc,
Expr *LHS, Expr *RHS) {
return getSema().BuildBinOp(/*Scope=*/nullptr, OpLoc, Opc, LHS, RHS);
ExprResult RebuildBinaryOperator(SourceLocation OpLoc, BinaryOperatorKind Opc,
Expr *LHS, Expr *RHS,
bool ForFoldExpression = false) {
return getSema().BuildBinOp(/*Scope=*/nullptr, OpLoc, Opc, LHS, RHS,
ForFoldExpression);
}
/// Build a new rewritten operator expression.
@@ -16408,6 +16409,7 @@ TreeTransform<Derived>::TransformCXXFoldExpr(CXXFoldExpr *E) {
return true;
}
bool WarnedOnComparison = false;
for (unsigned I = 0; I != *NumExpansions; ++I) {
Sema::ArgPackSubstIndexRAII SubstIndex(
getSema(), LeftFold ? I : *NumExpansions - I - 1);
@@ -16435,7 +16437,17 @@ TreeTransform<Derived>::TransformCXXFoldExpr(CXXFoldExpr *E) {
Functions, LHS, RHS);
} else {
Result = getDerived().RebuildBinaryOperator(E->getEllipsisLoc(),
E->getOperator(), LHS, RHS);
E->getOperator(), LHS, RHS,
/*ForFoldExpresion=*/true);
if (!WarnedOnComparison && Result.isUsable()) {
if (auto *BO = dyn_cast<BinaryOperator>(Result.get());
BO && BO->isComparisonOp()) {
WarnedOnComparison = true;
SemaRef.Diag(BO->getBeginLoc(),
diag::warn_comparison_in_fold_expression)
<< BO->getOpcodeStr();
}
}
}
} else
Result = Out;

View File

@@ -52,9 +52,11 @@ template<typename ...T> void as_operand_of_cast(int a, T ...t) {
// fold-operator can be '>' or '>>'.
template <int... N> constexpr bool greaterThan() { return (N > ...); }
// expected-error@-1 {{comparison in fold expression}}
template <int... N> constexpr int rightShift() { return (N >> ...); }
static_assert(greaterThan<2, 1>());
static_assert(greaterThan<2, 1>()); // expected-note {{in instantiation}}
static_assert(rightShift<10, 1>() == 5);
template <auto V> constexpr auto Identity = V;

View File

@@ -132,3 +132,30 @@ bool f();
template <typename... T>
void g(bool = (f<T>() || ...));
}
namespace comparison_warning {
struct S {
bool operator<(const S&) const;
bool operator<(int) const;
bool operator==(const S&) const;
};
template <typename...T>
void f(T... ts) {
(void)(ts == ...);
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X == Y) == Z'}}
(void)(ts < ...);
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X < Y) < Z'}}
(void)(... < ts);
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X < Y) < Z'}}
}
void test() {
f(0, 1, 2); // expected-note{{in instantiation}}
f(0, 1); // expected-note{{in instantiation}}
f(S{}, S{});
f(0);
}
};