Skip to content

Commit e819360

Browse files
committed
[clang] Added warn-assignment-bool-context
This warning is issued if an assignment is used in a context where a boolean result is required.
1 parent f87484d commit e819360

File tree

6 files changed

+143
-1
lines changed

6 files changed

+143
-1
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8477,6 +8477,9 @@ def err_incomplete_object_call : Error<
84778477
def warn_condition_is_assignment : Warning<"using the result of an "
84788478
"assignment as a condition without parentheses">,
84798479
InGroup<Parentheses>;
8480+
def warn_assignment_bool_context : Warning<"suggest parentheses around assignment used as truth value">,
8481+
InGroup<Parentheses>;
8482+
84808483
def warn_free_nonheap_object
84818484
: Warning<"attempt to call %0 on non-heap %select{object %2|object: block expression|object: lambda-to-function-pointer conversion}1">,
84828485
InGroup<FreeNonHeapObject>;

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7232,6 +7232,8 @@ class Sema final : public SemaBase {
72327232
/// being used as a boolean condition, warn if it's an assignment.
72337233
void DiagnoseAssignmentAsCondition(Expr *E);
72347234

7235+
void DiagnoseAssignmentBoolContext(Expr *E, QualType ToType);
7236+
72357237
/// Redundant parentheses over an equality comparison can indicate
72367238
/// that the user intended an assignment used as condition.
72377239
void DiagnoseEqualityWithExtraParens(ParenExpr *ParenE);

clang/lib/Sema/Sema.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,48 @@ void Sema::diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E) {
687687
<< FixItHint::CreateReplacement(E->getSourceRange(), "nullptr");
688688
}
689689

690+
void Sema::DiagnoseAssignmentBoolContext(Expr *E, QualType Ty) {
691+
// Use copy to not alter original expression.
692+
Expr *ECopy = E;
693+
694+
if (Ty->isBooleanType()) {
695+
// `bool(x=0)` and if (x=0){} emit:
696+
// - ImplicitCastExpr bool IntegralToBoolean
697+
// -- ImplicitCastExpr int LValueToRValue
698+
// --- Assignment ...
699+
// But should still emit this warning (at least gcc does), even if bool-cast
700+
// is not directly followed by assignment.
701+
// NOTE: Is this robust enough or can there be other semantic expression
702+
// until the assignment?
703+
while (ImplicitCastExpr *ICE = dyn_cast<ImplicitCastExpr>(ECopy)) {
704+
// If there is another implicit cast to bool then this warning would have
705+
// been already emitted.
706+
if (ICE->getType()->isBooleanType())
707+
return;
708+
ECopy = ICE->getSubExpr();
709+
}
710+
711+
if (BinaryOperator *Op = dyn_cast<BinaryOperator>(ECopy)) {
712+
// Should only be issued for regular assignment `=`,
713+
// not for compound-assign like `+=`.
714+
// NOTE: Might make sense to emit for all assignments even if gcc
715+
// only does for regular assignment.
716+
if (Op->getOpcode() == BO_Assign) {
717+
SourceLocation Loc = Op->getOperatorLoc();
718+
Diag(Loc, diag::warn_assignment_bool_context)
719+
<< ECopy->getSourceRange();
720+
721+
SourceLocation Open = ECopy->getBeginLoc();
722+
SourceLocation Close =
723+
getLocForEndOfToken(ECopy->getSourceRange().getEnd());
724+
Diag(Loc, diag::note_condition_assign_silence)
725+
<< FixItHint::CreateInsertion(Open, "(")
726+
<< FixItHint::CreateInsertion(Close, ")");
727+
}
728+
}
729+
}
730+
}
731+
690732
/// ImpCastExprToType - If Expr is not of type 'Type', insert an implicit cast.
691733
/// If there is already an implicit cast, merge into the existing one.
692734
/// The result is of the given category.
@@ -761,6 +803,17 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
761803
}
762804
}
763805

806+
// FIXME: Doesn't include C89, so this warning isn't emitted when passing
807+
// `std=c89`.
808+
auto isC = getLangOpts().C99 || getLangOpts().C11 || getLangOpts().C17 ||
809+
getLangOpts().C23;
810+
// Do not emit this warning for Objective-C, since it's a common idiom.
811+
// NOTE: Are there other languages that this could affect besides C and C++?
812+
// Ideally would check `getLangOpts().Cplusplus || getLangOpts().C` but there
813+
// is no option for C (only C99 etc.).
814+
if ((getLangOpts().CPlusPlus || isC) && !getLangOpts().ObjC)
815+
DiagnoseAssignmentBoolContext(E, Ty);
816+
764817
if (ImplicitCastExpr *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
765818
if (ImpCast->getCastKind() == Kind && (!BasePath || BasePath->empty())) {
766819
ImpCast->setType(Ty);

clang/lib/Sema/SemaExpr.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20287,7 +20287,11 @@ void Sema::DiagnoseEqualityWithExtraParens(ParenExpr *ParenE) {
2028720287

2028820288
ExprResult Sema::CheckBooleanCondition(SourceLocation Loc, Expr *E,
2028920289
bool IsConstexpr) {
20290-
DiagnoseAssignmentAsCondition(E);
20290+
// This warning is already covered by `warn_assignment_bool_context` in C++.
20291+
// NOTE: Ideally both warnings would be combined
20292+
if (!getLangOpts().CPlusPlus || getLangOpts().ObjC)
20293+
DiagnoseAssignmentAsCondition(E);
20294+
2029120295
if (ParenExpr *parenE = dyn_cast<ParenExpr>(E))
2029220296
DiagnoseEqualityWithExtraParens(parenE);
2029320297

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %clang_cc1 -x c -fsyntax-only -Wparentheses -verify %s
2+
3+
// NOTE: Don't know if tests allow includes.
4+
#include <stdbool.h>
5+
6+
// Do not emit the warning for compound-assignments.
7+
bool f(int x) { return x = 0; } // expected-warning {{suggest parentheses around assignment used as truth value}}\
8+
// expected-note{{place parentheses around the assignment to silence this warning}}
9+
bool f2(int x) { return x += 0; }
10+
11+
bool f3(bool x) { return x = 0; }
12+
13+
void test() {
14+
int x;
15+
16+
// This should emit the `warn_condition_is_assignment` warning, since
17+
// C doesn't do implicit conversion booleans for conditions.
18+
if (x = 0) {} // expected-warning {{using the result of an assignment as a condition without parentheses}} \
19+
// expected-note{{place parentheses around the assignment to silence this warning}}\
20+
// expected-note{{use '==' to turn this assignment into an equality comparison}}
21+
if (x = 4 && x){} // expected-warning {{using the result of an assignment as a condition without parentheses}} \
22+
// expected-note{{place parentheses around the assignment to silence this warning}}\
23+
// expected-note{{use '==' to turn this assignment into an equality comparison}}
24+
25+
(void)(bool)(x = 1);
26+
(void)(bool)(int)(x = 1);
27+
28+
29+
bool _a = x = 3; // expected-warning {{suggest parentheses around assignment used as truth value}}\
30+
// expected-note{{place parentheses around the assignment to silence this warning}}
31+
32+
// Shouldn't warn for above cases if parentheses were provided.
33+
if ((x = 0)) {}
34+
bool _b = (x = 3);
35+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %clang_cc1 -x c++ -fsyntax-only -Wparentheses -verify %s
2+
3+
// Do not emit the warning for compound-assignments.
4+
bool f(int x) { return x = 0; } // expected-warning {{suggest parentheses around assignment used as truth value}} \
5+
// expected-note{{place parentheses around the assignment to silence this warning}}
6+
bool f2(int x) { return x += 0; }
7+
8+
bool f3(bool x) { return x = 0; }
9+
10+
void test() {
11+
int x;
12+
13+
// Assignemnts inside of conditions should still emit the more specific `warn_condition_is_assignment` warning.
14+
if (x = 0) {} // expected-warning {{using the result of an assignment as a condition without parentheses}} \
15+
// expected-note{{use '==' to turn this assignment into an equality comparison}} \
16+
// expected-note{{place parentheses around the assignment to silence this warning}}
17+
if (x = 4 && x){} // expected-warning {{using the result of an assignment as a condition without parentheses}} \
18+
// expected-note{{use '==' to turn this assignment into an equality comparison}} \
19+
// expected-note{{place parentheses around the assignment to silence this warning}}
20+
21+
22+
(void)bool(x = 1); // expected-warning {{suggest parentheses around assignment used as truth value}}\
23+
// expected-note{{place parentheses around the assignment to silence this warning}}
24+
(void)(bool)(x = 1);
25+
26+
// This should still emit since the RHS is casted to `int` before being casted back to `bool`.
27+
(void)bool(x = false); // expected-warning {{suggest parentheses around assignment used as truth value}} \
28+
// expected-note{{place parentheses around the assignment to silence this warning}}
29+
30+
// Should only issue warning once, even if multiple implicit casts.
31+
// FIXME: This only checks that warning occurs not how often.
32+
(void)bool(bool(x = 1)); // expected-warning {{suggest parentheses around assignment used as truth value}} \
33+
// expected-note{{place parentheses around the assignment to silence this warning}}
34+
(void)bool(int(bool(x = 1))); // expected-warning {{suggest parentheses around assignment used as truth value}} \
35+
// expected-note{{place parentheses around the assignment to silence this warning}}
36+
(void)bool(int(x = 1));
37+
38+
bool _a = x = 3; // expected-warning {{suggest parentheses around assignment used as truth value}} \
39+
// expected-note{{place parentheses around the assignment to silence this warning}}
40+
41+
// Shouldn't warn for above cases if parentheses were provided.
42+
if ((x = 0)) {}
43+
(void)bool((x = 1));
44+
bool _b= (x = 3);
45+
}

0 commit comments

Comments
 (0)