Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,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.
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7138,6 +7138,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' "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to print out the expression than X, Y, Z

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I'm thinking if there's a case where it actually evaluates to X %0 (Y %0 Z)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We added that discussion for the previous PR, we concluded it was really not useful!

"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;
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -7182,13 +7182,15 @@ class Sema final : public SemaBase {
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);

Expand Down
15 changes: 8 additions & 7 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14918,8 +14918,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
Expand Down Expand Up @@ -15050,7 +15050,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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right fold cases are handled by this branch, right? So shall we rename ForFoldExpression to ForLeftFold or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just a side effect of looking at the LHS first - I don't think the distinction matters here

Diag(OpLoc, diag::warn_consecutive_comparison)
<< BI->getOpcodeStr() << BinaryOperator::getOpcodeStr(Opc);

Expand Down Expand Up @@ -15460,8 +15460,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())
Expand Down Expand Up @@ -15535,7 +15535,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.
Expand Down Expand Up @@ -15599,7 +15600,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) {
Expand Down
22 changes: 17 additions & 5 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -2967,10 +2967,11 @@ class TreeTransform {
///
/// 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.
Expand Down Expand Up @@ -16411,6 +16412,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);
Expand Down Expand Up @@ -16438,7 +16440,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;
Expand Down
4 changes: 3 additions & 1 deletion clang/test/Parser/cxx1z-fold-expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 27 additions & 0 deletions clang/test/SemaTemplate/cxx1z-fold-expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. It looks strange to me if we warn for '0 < 1'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended -, ideally, we would even warn when n <= 1 (ie, the size of the pack should not affect the warning) - but that would be annoying because there could be user-defined operators, and we only discover that for n >=2.

f(S{}, S{});
f(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you test f(0, 0)?

}

};