Skip to content

Commit 166555c

Browse files
committed
[Diagnostics] Better diagnostic for integer used as a boolean condition
1 parent 4aca39e commit 166555c

File tree

14 files changed

+73
-24
lines changed

14 files changed

+73
-24
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3178,6 +3178,9 @@ NOTE(note_explicitly_compare_cftypeid,none,
31783178
ERROR(optional_used_as_boolean,none,
31793179
"optional type %0 cannot be used as a boolean; "
31803180
"test for '%select{!|=}1= nil' instead", (Type, bool))
3181+
ERROR(integer_used_as_boolean,none,
3182+
"type %0 cannot be used as a boolean; "
3183+
"test for '%select{!|=}1= 0' instead", (Type, bool))
31813184

31823185
ERROR(interpolation_missing_proto,none,
31833186
"string interpolation requires the protocol 'ExpressibleByStringInterpolation' to be defined",

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ PROTOCOL_(ErrorCodeProtocol)
6969
PROTOCOL(OptionSet)
7070
PROTOCOL(CaseIterable)
7171
PROTOCOL(SIMDScalar)
72+
PROTOCOL(BinaryInteger)
7273

7374
PROTOCOL_(BridgedNSError)
7475
PROTOCOL_(BridgedStoredNSError)

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4370,6 +4370,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
43704370
case KnownProtocolKind::CaseIterable:
43714371
case KnownProtocolKind::Comparable:
43724372
case KnownProtocolKind::SIMDScalar:
4373+
case KnownProtocolKind::BinaryInteger:
43734374
case KnownProtocolKind::ObjectiveCBridgeable:
43744375
case KnownProtocolKind::DestructorSafeContainer:
43754376
case KnownProtocolKind::SwiftNewtypeWrapper:

lib/Sema/CSDiagnostics.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,37 @@ bool ContextualFailure::diagnoseConversionToBool() const {
23562356
return true;
23572357
}
23582358

2359+
// If we're trying to convert something from optional type to an integer, then
2360+
// a comparison against nil was probably expected.
2361+
auto &cs = getConstraintSystem();
2362+
if (conformsToKnownProtocol(cs, fromType, KnownProtocolKind::BinaryInteger) &&
2363+
conformsToKnownProtocol(cs, fromType,
2364+
KnownProtocolKind::ExpressibleByIntegerLiteral)) {
2365+
StringRef prefix = "((";
2366+
StringRef suffix;
2367+
if (notOperatorLoc.isValid())
2368+
suffix = ") == 0)";
2369+
else
2370+
suffix = ") != 0)";
2371+
2372+
// Check if we need the inner parentheses.
2373+
// Technically we only need them if there's something in 'expr' with
2374+
// lower precedence than '!=', but the code actually comes out nicer
2375+
// in most cases with parens on anything non-trivial.
2376+
if (expr->canAppendPostfixExpression()) {
2377+
prefix = prefix.drop_back();
2378+
suffix = suffix.drop_front();
2379+
}
2380+
// FIXME: The outer parentheses may be superfluous too.
2381+
2382+
emitDiagnostic(expr->getLoc(), diag::integer_used_as_boolean, fromType,
2383+
notOperatorLoc.isValid())
2384+
.fixItInsert(expr->getStartLoc(), prefix)
2385+
.fixItInsertAfter(expr->getEndLoc(), suffix)
2386+
.fixItRemove(notOperatorLoc);
2387+
return true;
2388+
}
2389+
23592390
return false;
23602391
}
23612392

test/ClangImporter/private_frameworks_modules.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import PrivateAsParallel_Private
1313
#error("OLD or NEW must be defined")
1414
#endif
1515

16-
let _: Bool = PSGlobal // expected-error {{cannot convert value of type 'Int32'}}
17-
let _: Bool = PSPrivateGlobal // expected-error {{cannot convert value of type 'Int32'}}
18-
let _: Bool = PPGlobal // expected-error {{cannot convert value of type 'Int32'}}
19-
let _: Bool = PPPrivateGlobal // expected-error {{cannot convert value of type 'Int32'}}
16+
let _: Bool = PSGlobal // expected-error {{type 'Int32' cannot be used as a boolean}}
17+
let _: Bool = PSPrivateGlobal // expected-error {{type 'Int32' cannot be used as a boolean}}
18+
let _: Bool = PPGlobal // expected-error {{type 'Int32' cannot be used as a boolean}}
19+
let _: Bool = PPPrivateGlobal // expected-error {{type 'Int32' cannot be used as a boolean}}

test/Constraints/diagnostics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ func r17224804(_ monthNumber : Int) {
195195

196196
// <rdar://problem/17020197> QoI: Operand of postfix '!' should have optional type; type is 'Int?'
197197
func r17020197(_ x : Int?, y : Int) {
198-
if x! { } // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
198+
if x! { } // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
199199

200200
// <rdar://problem/12939553> QoI: diagnostic for using an integer in a condition is utterly terrible
201-
if y {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
201+
if y {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
202202
}
203203

204204
// <rdar://problem/20714480> QoI: Boolean expr not treated as Bool type when function return type is different

test/Constraints/fixes.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ ciuo ? true : false // expected-error{{optional type 'C?' cannot be used as a bo
135135
!co // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{4-4= == nil)}}
136136
!ciuo // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{6-6= == nil)}}
137137

138+
// Used an integer in a conditional expression
139+
var n1: Int = 1
140+
var n2: UInt8 = 0
141+
var n3: Int16 = 2
142+
143+
if n1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}} {{4-4=(}} {{6-6= != 0)}}
144+
if !n1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '== 0' instead}} {{4-5=}} {{5-5=(}} {{7-7= == 0)}}
145+
n1 ? true : false // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}} {{1-1=(}} {{3-3= != 0)}}
146+
!n1 ? false : true // expected-error {{type 'Int' cannot be used as a boolean; test for '== 0' instead}} {{1-2=}} {{2-2=(}} {{4-4= == 0)}}
147+
!n1 // expected-error {{type 'Int' cannot be used as a boolean; test for '== 0' instead}} {{1-2=}} {{2-2=(}} {{4-4= == 0)}}
148+
if n2 {} // expected-error {{type 'UInt8' cannot be used as a boolean; test for '!= 0' instead}} {{4-4=(}} {{6-6= != 0)}}
149+
!n3 // expected-error {{type 'Int16' cannot be used as a boolean; test for '== 0' instead}} {{1-2=}} {{2-2=(}} {{4-4= == 0)}}
150+
138151
// Forgotten ! or ?
139152
var someInt = co.a // expected-error{{value of optional type 'C?' must be unwrapped to refer to member 'a' of wrapped base type 'C'}}
140153
// expected-note@-1{{chain the optional using '?' to access member 'a' only for non-'nil' base values}}{{17-17=?}}

test/Constraints/if_expr.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ useDouble(c)
2828
useDouble(d)
2929

3030
var z = true ? a : b // expected-error{{result values in '? :' expression have mismatching types 'Int' and 'Double'}}
31-
var _ = a ? b : b // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
31+
var _ = a ? b : b // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
3232

3333

3434

@@ -51,9 +51,9 @@ useD1(i) // expected-error{{cannot convert value of type 'B' to expected argumen
5151
useD2(i) // expected-error{{cannot convert value of type 'B' to expected argument type 'D2'}}
5252

5353
var x = true ? 1 : 0
54-
var y = 22 ? 1 : 0 // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
54+
var y = 22 ? 1 : 0 // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
5555

56-
_ = x ? x : x // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
56+
_ = x ? x : x // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
5757
_ = true ? x : 1.2 // expected-error {{result values in '? :' expression have mismatching types 'Int' and 'Double'}}
5858

5959
_ = (x: true) ? true : false // expected-error {{cannot convert value of type '(x: Bool)' to expected condition type 'Bool'}}

test/Misc/misc_diagnostics.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ let total = 15.0
2222
let count = 7
2323
let median = total / count // expected-error {{binary operator '/' cannot be applied to operands of type 'Double' and 'Int'}} expected-note {{overloads for '/' exist with these partially matching parameter lists:}}
2424

25-
if (1) {} // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
26-
if 1 {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
25+
if (1) {} // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
26+
if 1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
2727

2828
var a: [String] = [1] // expected-error{{cannot convert value of type 'Int' to expected element type 'String'}}
2929
var b: Int = [1, 2, 3] // expected-error{{cannot convert value of type '[Int]' to specified type 'Int'}}
@@ -34,7 +34,7 @@ var f2: Float = 3.0
3434
var dd: Double = f1 - f2 // expected-error{{cannot convert value of type 'Float' to specified type 'Double'}}
3535

3636
func f() -> Bool {
37-
return 1 + 1 // expected-error{{cannot convert return expression of type 'Int' to return type 'Bool'}}
37+
return 1 + 1 // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
3838
}
3939

4040
// Test that nested diagnostics are properly surfaced.

test/NameBinding/import-resolution-overload.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ ambiguousWithVar(true) // no-warning
3232

3333
var localVar : Bool
3434
localVar = false
35-
localVar = 42 // expected-error {{cannot assign value of type 'Int' to type 'Bool'}}
35+
localVar = 42 // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
3636
localVar(42) // expected-error {{cannot call value of non-function type 'Bool'}}
3737
var _ : localVar // should still work
3838

test/Parse/switch.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ case _ where x % 2 == 0,
6464
x = 1
6565
case var y where y % 2 == 0:
6666
x = y + 1
67-
case _ where 0: // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
67+
case _ where 0: // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
6868
x = 0
6969
default:
7070
x = 1

test/Sema/pound_assert.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
#assert(false, "error message")
1010

11-
#assert(123) // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
11+
#assert(123) // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
1212

13-
#assert(123, "error message") // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
13+
#assert(123, "error message") // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}

test/expr/expressions.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func basictest() {
3636

3737
var x4 : Bool = true
3838
var x5 : Bool =
39-
4 // expected-error {{cannot convert value of type 'Int' to specified type 'Bool'}}
39+
4 // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
4040

4141
//var x6 : Float = 4+5
4242

@@ -295,7 +295,7 @@ func fib(_ n: Int) -> Int {
295295

296296
// FIXME: Should warn about integer constants being too large <rdar://problem/14070127>
297297
var
298-
il_a: Bool = 4 // expected-error {{cannot convert value of type 'Int' to specified type 'Bool'}}
298+
il_a: Bool = 4 // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
299299
var il_b: Int8
300300
= 123123
301301
var il_c: Int8 = 4 // ok
@@ -804,13 +804,13 @@ func testParenExprInTheWay() {
804804
let x = 42
805805

806806
if x & 4.0 {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
807-
// expected-error@-1 {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
807+
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
808808
if (x & 4.0) {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
809-
// expected-error@-1 {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
809+
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
810810
if !(x & 4.0) {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
811-
// expected-error@-1 {{cannot convert value of type 'Int' to expected argument type 'Bool'}}
811+
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
812812

813-
if x & x {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
813+
if x & x {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
814814
}
815815

816816
// <rdar://problem/21352576> Mixed method/property overload groups can cause a crash during constraint optimization

test/stmt/statements.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func funcdecl5(_ a: Int, y: Int) {
5959
}
6060

6161
// This diagnostic is terrible - rdar://12939553
62-
if x {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
62+
if x {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
6363

6464
if true {
6565
if (B) {
@@ -583,7 +583,7 @@ func fn(x: Int) {
583583
}
584584

585585
func bad_if() {
586-
if 1 {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
586+
if 1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
587587
if (x: false) {} // expected-error {{cannot convert value of type '(x: Bool)' to expected condition type 'Bool'}}
588588
if (x: 1) {} // expected-error {{cannot convert value of type '(x: Int)' to expected condition type 'Bool'}}
589589
if nil {} // expected-error {{'nil' is not compatible with expected condition type 'Bool'}}

0 commit comments

Comments
 (0)