Skip to content

Commit 956b9f2

Browse files
authored
Merge pull request #29628 from owenv/condition_diags
[Diagnostics] Improve diagnostics for optional/integer as boolean condition
2 parents 713e636 + 166555c commit 956b9f2

15 files changed

+111
-36
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3175,7 +3175,10 @@ NOTE(note_explicitly_compare_cftypeid,none,
31753175

31763176
ERROR(optional_used_as_boolean,none,
31773177
"optional type %0 cannot be used as a boolean; "
3178-
"test for '!= nil' instead", (Type))
3178+
"test for '%select{!|=}1= nil' instead", (Type, bool))
3179+
ERROR(integer_used_as_boolean,none,
3180+
"type %0 cannot be used as a boolean; "
3181+
"test for '%select{!|=}1= 0' instead", (Type, bool))
31793182

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

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ IDENTIFIER_WITH_NAME(NativeClassLayout, "_NativeClass")
143143
// Operators
144144
IDENTIFIER_WITH_NAME(MatchOperator, "~=")
145145
IDENTIFIER_WITH_NAME(EqualsOperator, "==")
146+
IDENTIFIER_WITH_NAME(NegationOperator, "!")
146147
IDENTIFIER_WITH_NAME(derived_enum_equals, "__derived_enum_equals")
147148
IDENTIFIER_WITH_NAME(derived_struct_equals, "__derived_struct_equals")
148149

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: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,14 +2308,68 @@ bool ContextualFailure::diagnoseConversionToBool() const {
23082308
return true;
23092309
}
23102310

2311+
// Determine if the boolean negation operator was applied to the anchor. This
2312+
// upwards traversal of the AST is somewhat fragile, but enables much better
2313+
// diagnostics if someone attempts to use an optional or integer as a boolean
2314+
// condition.
2315+
SourceLoc notOperatorLoc;
2316+
if (Expr *parent = findParentExpr(getAnchor())) {
2317+
if (isa<ParenExpr>(parent) && parent->isImplicit()) {
2318+
if ((parent = findParentExpr(parent))) {
2319+
auto parentOperatorApplication = dyn_cast<PrefixUnaryExpr>(parent);
2320+
if (parentOperatorApplication) {
2321+
auto operatorRefExpr =
2322+
dyn_cast<DeclRefExpr>(parentOperatorApplication->getFn());
2323+
if (operatorRefExpr && operatorRefExpr->getDecl()->getBaseName() ==
2324+
getASTContext().Id_NegationOperator) {
2325+
notOperatorLoc = operatorRefExpr->getLoc();
2326+
}
2327+
}
2328+
}
2329+
}
2330+
}
2331+
23112332
// If we're trying to convert something from optional type to Bool, then a
23122333
// comparison against nil was probably expected.
2313-
// TODO: It would be nice to handle "!x" --> x == false, but we have no way
2314-
// to get to the parent expr at present.
23152334
auto fromType = getFromType();
23162335
if (fromType->getOptionalObjectType()) {
23172336
StringRef prefix = "((";
2318-
StringRef suffix = ") != nil)";
2337+
StringRef suffix;
2338+
if (notOperatorLoc.isValid())
2339+
suffix = ") == nil)";
2340+
else
2341+
suffix = ") != nil)";
2342+
2343+
// Check if we need the inner parentheses.
2344+
// Technically we only need them if there's something in 'expr' with
2345+
// lower precedence than '!=', but the code actually comes out nicer
2346+
// in most cases with parens on anything non-trivial.
2347+
if (expr->canAppendPostfixExpression()) {
2348+
prefix = prefix.drop_back();
2349+
suffix = suffix.drop_front();
2350+
}
2351+
// FIXME: The outer parentheses may be superfluous too.
2352+
2353+
emitDiagnostic(expr->getLoc(), diag::optional_used_as_boolean, fromType,
2354+
notOperatorLoc.isValid())
2355+
.fixItInsert(expr->getStartLoc(), prefix)
2356+
.fixItInsertAfter(expr->getEndLoc(), suffix)
2357+
.fixItRemove(notOperatorLoc);
2358+
return true;
2359+
}
2360+
2361+
// If we're trying to convert something from optional type to an integer, then
2362+
// a comparison against nil was probably expected.
2363+
auto &cs = getConstraintSystem();
2364+
if (conformsToKnownProtocol(cs, fromType, KnownProtocolKind::BinaryInteger) &&
2365+
conformsToKnownProtocol(cs, fromType,
2366+
KnownProtocolKind::ExpressibleByIntegerLiteral)) {
2367+
StringRef prefix = "((";
2368+
StringRef suffix;
2369+
if (notOperatorLoc.isValid())
2370+
suffix = ") == 0)";
2371+
else
2372+
suffix = ") != 0)";
23192373

23202374
// Check if we need the inner parentheses.
23212375
// Technically we only need them if there's something in 'expr' with
@@ -2327,9 +2381,11 @@ bool ContextualFailure::diagnoseConversionToBool() const {
23272381
}
23282382
// FIXME: The outer parentheses may be superfluous too.
23292383

2330-
emitDiagnostic(expr->getLoc(), diag::optional_used_as_boolean, fromType)
2384+
emitDiagnostic(expr->getLoc(), diag::integer_used_as_boolean, fromType,
2385+
notOperatorLoc.isValid())
23312386
.fixItInsert(expr->getStartLoc(), prefix)
2332-
.fixItInsertAfter(expr->getEndLoc(), suffix);
2387+
.fixItInsertAfter(expr->getEndLoc(), suffix)
2388+
.fixItRemove(notOperatorLoc);
23332389
return true;
23342390
}
23352391

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: 4 additions & 4 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
@@ -634,11 +634,11 @@ func r18397777(_ d : r21447318?) {
634634
if d { // expected-error {{optional type 'r21447318?' cannot be used as a boolean; test for '!= nil' instead}} {{6-6=(}} {{7-7= != nil)}}
635635
}
636636

637-
if !d { // expected-error {{optional type 'r21447318?' cannot be used as a boolean; test for '!= nil' instead}} {{7-7=(}} {{8-8= != nil)}}
637+
if !d { // expected-error {{optional type 'r21447318?' cannot be used as a boolean; test for '== nil' instead}} {{6-7=}} {{7-7=(}} {{8-8= == nil)}}
638638

639639
}
640640

641-
if !Optional(c) { // expected-error {{optional type 'Optional<r21447318>' cannot be used as a boolean; test for '!= nil' instead}} {{7-7=(}} {{18-18= != nil)}}
641+
if !Optional(c) { // expected-error {{optional type 'Optional<r21447318>' cannot be used as a boolean; test for '== nil' instead}} {{6-7=}} {{7-7=(}} {{18-18= == nil)}}
642642
}
643643
}
644644

test/Constraints/fixes.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,24 @@ var ciuo: C! = nil
129129
if co {} // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{4-4=(}} {{6-6= != nil)}}
130130
if ciuo {} // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{4-4=(}} {{8-8= != nil)}}
131131
co ? true : false // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{1-1=(}} {{3-3= != nil)}}
132-
!co ? false : true // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{2-2=(}} {{4-4= != nil)}}
132+
!co ? false : true // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{4-4= == nil)}}
133133
ciuo ? true : false // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{1-1=(}} {{5-5= != nil)}}
134-
!ciuo ? false : true // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{2-2=(}} {{6-6= != nil)}}
135-
!co // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{2-2=(}} {{4-4= != nil)}}
136-
!ciuo // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{2-2=(}} {{6-6= != nil)}}
134+
!ciuo ? false : true // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{6-6= == nil)}}
135+
!co // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{4-4= == nil)}}
136+
!ciuo // expected-error{{optional type 'C?' cannot be used as a boolean; test for '== nil' instead}} {{1-2=}} {{2-2=(}} {{6-6= == nil)}}
137+
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)}}
137150

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'}}

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)