Skip to content

Commit 7ffaaf4

Browse files
authored
[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
1 parent abd2c07 commit 7ffaaf4

File tree

7 files changed

+65
-15
lines changed

7 files changed

+65
-15
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ Improvements to Clang's diagnostics
389389
under the subgroup ``-Wunsafe-buffer-usage-in-libc-call``.
390390
- Diagnostics on chained comparisons (``a < b < c``) are now an error by default. This can be disabled with
391391
``-Wno-error=parentheses``.
392+
- Similarly, fold expressions over a comparison operator are now an error by default.
392393
- Clang now better preserves the sugared types of pointers to member.
393394
- Clang now better preserves the presence of the template keyword with dependent
394395
prefixes.

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7204,6 +7204,11 @@ def warn_consecutive_comparison : Warning<
72047204
"chained comparison 'X %0 Y %1 Z' does not behave the same as a mathematical expression">,
72057205
InGroup<Parentheses>, DefaultError;
72067206

7207+
def warn_comparison_in_fold_expression : Warning<
7208+
"comparison in fold expression would evaluate to '(X %0 Y) %0 Z' "
7209+
"which does not behave the same as a mathematical expression">,
7210+
InGroup<Parentheses>, DefaultError;
7211+
72077212
def warn_enum_constant_in_bool_context : Warning<
72087213
"converting the enum constant to a boolean">,
72097214
InGroup<IntInBoolContext>, DefaultIgnore;

clang/include/clang/Sema/Sema.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7215,13 +7215,15 @@ class Sema final : public SemaBase {
72157215
ExprResult ActOnBinOp(Scope *S, SourceLocation TokLoc, tok::TokenKind Kind,
72167216
Expr *LHSExpr, Expr *RHSExpr);
72177217
ExprResult BuildBinOp(Scope *S, SourceLocation OpLoc, BinaryOperatorKind Opc,
7218-
Expr *LHSExpr, Expr *RHSExpr);
7218+
Expr *LHSExpr, Expr *RHSExpr,
7219+
bool ForFoldExpression = false);
72197220

72207221
/// CreateBuiltinBinOp - Creates a new built-in binary operation with
72217222
/// operator @p Opc at location @c TokLoc. This routine only supports
72227223
/// built-in operations; ActOnBinOp handles overloaded operators.
72237224
ExprResult CreateBuiltinBinOp(SourceLocation OpLoc, BinaryOperatorKind Opc,
7224-
Expr *LHSExpr, Expr *RHSExpr);
7225+
Expr *LHSExpr, Expr *RHSExpr,
7226+
bool ForFoldExpression = false);
72257227
void LookupBinOp(Scope *S, SourceLocation OpLoc, BinaryOperatorKind Opc,
72267228
UnresolvedSetImpl &Functions);
72277229

clang/lib/Sema/SemaExpr.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14947,8 +14947,8 @@ static bool needsConversionOfHalfVec(bool OpRequiresConversion, ASTContext &Ctx,
1494714947
}
1494814948

1494914949
ExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc,
14950-
BinaryOperatorKind Opc,
14951-
Expr *LHSExpr, Expr *RHSExpr) {
14950+
BinaryOperatorKind Opc, Expr *LHSExpr,
14951+
Expr *RHSExpr, bool ForFoldExpression) {
1495214952
if (getLangOpts().CPlusPlus11 && isa<InitListExpr>(RHSExpr)) {
1495314953
// The syntax only allows initializer lists on the RHS of assignment,
1495414954
// so we don't need to worry about accepting invalid code for
@@ -15079,7 +15079,7 @@ ExprResult Sema::CreateBuiltinBinOp(SourceLocation OpLoc,
1507915079
ResultTy = CheckCompareOperands(LHS, RHS, OpLoc, Opc);
1508015080

1508115081
if (const auto *BI = dyn_cast<BinaryOperator>(LHSExpr);
15082-
BI && BI->isComparisonOp())
15082+
!ForFoldExpression && BI && BI->isComparisonOp())
1508315083
Diag(OpLoc, diag::warn_consecutive_comparison)
1508415084
<< BI->getOpcodeStr() << BinaryOperator::getOpcodeStr(Opc);
1508515085

@@ -15490,8 +15490,8 @@ static ExprResult BuildOverloadedBinOp(Sema &S, Scope *Sc, SourceLocation OpLoc,
1549015490
}
1549115491

1549215492
ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
15493-
BinaryOperatorKind Opc,
15494-
Expr *LHSExpr, Expr *RHSExpr) {
15493+
BinaryOperatorKind Opc, Expr *LHSExpr,
15494+
Expr *RHSExpr, bool ForFoldExpression) {
1549515495
ExprResult LHS, RHS;
1549615496
std::tie(LHS, RHS) = CorrectDelayedTyposInBinOp(*this, Opc, LHSExpr, RHSExpr);
1549715497
if (!LHS.isUsable() || !RHS.isUsable())
@@ -15565,7 +15565,8 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
1556515565
LHSExpr->getType()->isOverloadableType()))
1556615566
return BuildOverloadedBinOp(*this, S, OpLoc, Opc, LHSExpr, RHSExpr);
1556715567

15568-
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr);
15568+
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr,
15569+
ForFoldExpression);
1556915570
}
1557015571

1557115572
// Don't resolve overloads if the other type is overloadable.
@@ -15629,7 +15630,7 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
1562915630
}
1563015631

1563115632
// Build a built-in binary operation.
15632-
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr);
15633+
return CreateBuiltinBinOp(OpLoc, Opc, LHSExpr, RHSExpr, ForFoldExpression);
1563315634
}
1563415635

1563515636
static bool isOverflowingIntegerType(ASTContext &Ctx, QualType T) {

clang/lib/Sema/TreeTransform.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,10 +2967,11 @@ class TreeTransform {
29672967
///
29682968
/// By default, performs semantic analysis to build the new expression.
29692969
/// Subclasses may override this routine to provide different behavior.
2970-
ExprResult RebuildBinaryOperator(SourceLocation OpLoc,
2971-
BinaryOperatorKind Opc,
2972-
Expr *LHS, Expr *RHS) {
2973-
return getSema().BuildBinOp(/*Scope=*/nullptr, OpLoc, Opc, LHS, RHS);
2970+
ExprResult RebuildBinaryOperator(SourceLocation OpLoc, BinaryOperatorKind Opc,
2971+
Expr *LHS, Expr *RHS,
2972+
bool ForFoldExpression = false) {
2973+
return getSema().BuildBinOp(/*Scope=*/nullptr, OpLoc, Opc, LHS, RHS,
2974+
ForFoldExpression);
29742975
}
29752976

29762977
/// Build a new rewritten operator expression.
@@ -16408,6 +16409,7 @@ TreeTransform<Derived>::TransformCXXFoldExpr(CXXFoldExpr *E) {
1640816409
return true;
1640916410
}
1641016411

16412+
bool WarnedOnComparison = false;
1641116413
for (unsigned I = 0; I != *NumExpansions; ++I) {
1641216414
Sema::ArgPackSubstIndexRAII SubstIndex(
1641316415
getSema(), LeftFold ? I : *NumExpansions - I - 1);
@@ -16435,7 +16437,17 @@ TreeTransform<Derived>::TransformCXXFoldExpr(CXXFoldExpr *E) {
1643516437
Functions, LHS, RHS);
1643616438
} else {
1643716439
Result = getDerived().RebuildBinaryOperator(E->getEllipsisLoc(),
16438-
E->getOperator(), LHS, RHS);
16440+
E->getOperator(), LHS, RHS,
16441+
/*ForFoldExpresion=*/true);
16442+
if (!WarnedOnComparison && Result.isUsable()) {
16443+
if (auto *BO = dyn_cast<BinaryOperator>(Result.get());
16444+
BO && BO->isComparisonOp()) {
16445+
WarnedOnComparison = true;
16446+
SemaRef.Diag(BO->getBeginLoc(),
16447+
diag::warn_comparison_in_fold_expression)
16448+
<< BO->getOpcodeStr();
16449+
}
16450+
}
1643916451
}
1644016452
} else
1644116453
Result = Out;

clang/test/Parser/cxx1z-fold-expressions.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ template<typename ...T> void as_operand_of_cast(int a, T ...t) {
5252

5353
// fold-operator can be '>' or '>>'.
5454
template <int... N> constexpr bool greaterThan() { return (N > ...); }
55+
// expected-error@-1 {{comparison in fold expression}}
56+
5557
template <int... N> constexpr int rightShift() { return (N >> ...); }
5658

57-
static_assert(greaterThan<2, 1>());
59+
static_assert(greaterThan<2, 1>()); // expected-note {{in instantiation}}
5860
static_assert(rightShift<10, 1>() == 5);
5961

6062
template <auto V> constexpr auto Identity = V;

clang/test/SemaTemplate/cxx1z-fold-expressions.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,30 @@ bool f();
132132
template <typename... T>
133133
void g(bool = (f<T>() || ...));
134134
}
135+
136+
137+
namespace comparison_warning {
138+
struct S {
139+
bool operator<(const S&) const;
140+
bool operator<(int) const;
141+
bool operator==(const S&) const;
142+
};
143+
144+
template <typename...T>
145+
void f(T... ts) {
146+
(void)(ts == ...);
147+
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X == Y) == Z'}}
148+
(void)(ts < ...);
149+
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X < Y) < Z'}}
150+
(void)(... < ts);
151+
// expected-error@-1 2{{comparison in fold expression would evaluate to '(X < Y) < Z'}}
152+
}
153+
154+
void test() {
155+
f(0, 1, 2); // expected-note{{in instantiation}}
156+
f(0, 1); // expected-note{{in instantiation}}
157+
f(S{}, S{});
158+
f(0);
159+
}
160+
161+
};

0 commit comments

Comments
 (0)