Skip to content

[Sema] Improving integer literal as boolean diagnostic #63799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 21, 2023
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3893,6 +3893,9 @@ ERROR(optional_used_as_boolean,none,
ERROR(integer_used_as_boolean,none,
"type %0 cannot be used as a boolean; "
"test for '%select{!|=}1= 0' instead", (Type, bool))
ERROR(integer_used_as_boolean_literal,none,
"integer literal value '%0' cannot be used as a boolean; "
"did you mean '%select{false|true}1'?", (StringRef, bool))

ERROR(interpolation_missing_proto,none,
"string interpolation requires the protocol 'ExpressibleByStringInterpolation' to be defined",
Expand Down
31 changes: 31 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2930,6 +2930,21 @@ bool ContextualFailure::diagnoseConversionToBool() const {
// comparison against nil was probably expected.
auto fromType = getFromType();
if (fromType->getOptionalObjectType()) {
if (auto *OE = getAsExpr<OptionalEvaluationExpr>(
anchor->getValueProvidingExpr())) {
auto *subExpr = OE->getSubExpr();
while (auto *BOE = getAsExpr<BindOptionalExpr>(subExpr)) {
subExpr = BOE->getSubExpr();
}
// The contextual mismatch is anchored in an optional evaluation
// expression wrapping a literal expression e.g. `0?` suggesting to use
// `!= nil` will not be accurate in this case, so let's fallback to
// default mismatch diagnostic.
if (isa<LiteralExpr>(subExpr)) {
return false;
}
}

StringRef prefix = "((";
StringRef suffix;
if (notOperatorLoc.isValid())
Expand Down Expand Up @@ -2960,6 +2975,22 @@ bool ContextualFailure::diagnoseConversionToBool() const {
if (conformsToKnownProtocol(fromType, KnownProtocolKind::BinaryInteger) &&
conformsToKnownProtocol(fromType,
KnownProtocolKind::ExpressibleByIntegerLiteral)) {
if (auto *IL =
getAsExpr<IntegerLiteralExpr>(anchor->getValueProvidingExpr())) {
// If integer literal value is either zero or one, let's suggest replacing
// with boolean literal `true` or `false`. Otherwise fallback to generic
// type mismatch diagnostic.
const auto value = IL->getRawValue();
if (value.isOne() || value.isZero()) {
StringRef boolLiteral = value.isZero() ? "false" : "true";
emitDiagnostic(diag::integer_used_as_boolean_literal,
IL->getDigitsText(), value.isOne())
.fixItReplace(IL->getSourceRange(), boolLiteral);
return true;
}
return false;
}

StringRef prefix = "((";
StringRef suffix;
if (notOperatorLoc.isValid())
Expand Down
26 changes: 13 additions & 13 deletions test/Constraints/argument_matching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -531,13 +531,13 @@ struct PositionsAroundDefaultsAndVariadics {
// expected-error@-1 {{cannot convert value of type '[Int]' to expected argument type 'Bool'}}

f1(b: "2", 1) // expected-error {{incorrect argument labels in call (have 'b:_:', expected '_:_:c:_:')}}
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
// expected-error@-1 {{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}}

f1(b: "2", [3], 1) // expected-error {{incorrect argument labels in call (have 'b:_:_:', expected '_:_:c:_:')}}
// expected-error@-1 {{cannot convert value of type '[Int]' to expected argument type 'Bool'}}

f1(b: "2", 1, [3]) // expected-error {{incorrect argument labels in call (have 'b:_:_:', expected '_:_:c:_:')}}
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
// expected-error@-1 {{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}}
// expected-error@-2 {{cannot convert value of type '[Int]' to expected argument type 'Int'}}
}

Expand Down Expand Up @@ -574,11 +574,11 @@ struct PositionsAroundDefaultsAndVariadics {
f2(true, 21, 22, [4]) // expected-error {{cannot pass array of type '[Int]' as variadic arguments of type 'Int'}}
// expected-note@-1 {{remove brackets to pass array elements directly}}

f2(21, 22, 23, c: "3", [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f2(21, 22, 23, c: "3", [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f2(21, 22, c: "3", [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f2(21, 22, c: "3", [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f2(21, c: "3", [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f2(21, c: "3", [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f2(c: "3", [4])
f2(c: "3")
Expand All @@ -591,15 +591,15 @@ struct PositionsAroundDefaultsAndVariadics {
// expected-error@-2 {{cannot convert value of type '[Int]' to expected argument type 'Bool'}}

f2(c: "3", [4], 21) // expected-error {{incorrect argument labels in call (have 'c:_:_:', expected '_:_:c:_:')}}
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
// expected-error@-1 {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f2([4]) // expected-error {{cannot convert value of type '[Int]' to expected argument type 'Bool'}}

f2(21, [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f2(21, [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}
// expected-error@-1 {{cannot pass array of type '[Int]' as variadic arguments of type 'Int'}}
// expected-note@-2 {{remove brackets to pass array elements directly}}

f2(21, 22, [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f2(21, 22, [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}
// expected-error@-1 {{cannot pass array of type '[Int]' as variadic arguments of type 'Int'}}
// expected-note@-2 {{remove brackets to pass array elements directly}}
}
Expand Down Expand Up @@ -690,7 +690,7 @@ struct PositionsAroundDefaultsAndVariadics {
f4(b: "2", 31, d: [4])
f4(b: "2", d: [4])

f4(31, b: "2", d: [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f4(31, b: "2", d: [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f4(b: "2", d: [4], 31) // expected-error {{unnamed argument #3 must precede argument 'b'}}

Expand All @@ -700,13 +700,13 @@ struct PositionsAroundDefaultsAndVariadics {

f4()

f4(31) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f4(31) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f4(31, d: [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f4(31, d: [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f4(31, 32) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f4(31, 32) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}

f4(31, 32, d: [4]) // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
f4(31, 32, d: [4]) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Bool'}}
}

// labeled variadics after labeled parameter
Expand Down
4 changes: 2 additions & 2 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1535,13 +1535,13 @@ func testUnwrapFixIts(x: Int?) throws {
func issue63746() {
let fn1 = {
switch 0 {
case 1 where 0: // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
case 1 where 0: // expected-error {{integer literal value '0' cannot be used as a boolean; did you mean 'false'?}}
()
}
}
let fn2 = {
switch 0 {
case 1 where 0: // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
case 1 where 0: // expected-error {{integer literal value '0' cannot be used as a boolean; did you mean 'false'?}}
break
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/ternary_expr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ useD1(i) // expected-error{{cannot convert value of type 'B' to expected argumen
useD2(i) // expected-error{{cannot convert value of type 'B' to expected argument type 'D2'}}

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

_ = x ? x : x // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
_ = true ? x : 1.2 // expected-error {{result values in '? :' expression have mismatching types 'Int' and 'Double'}}
Expand Down
2 changes: 1 addition & 1 deletion test/ImportResolution/import-resolution-overload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ambiguousWithVar(true) // no-warning

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

Expand Down
4 changes: 2 additions & 2 deletions test/Misc/misc_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ let total = 15.0
let count = 7
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:}}

if (1) {} // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
if 1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
if (1) {} // expected-error{{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}} {{5-6=true}}
if 1 {} // expected-error {{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}} {{4-5=true}}

var a: [String] = [1] // expected-error{{cannot convert value of type 'Int' to expected element type 'String'}}
var b: Int = [1, 2, 3] // expected-error{{cannot convert value of type '[Int]' to specified type 'Int'}}
Expand Down
2 changes: 1 addition & 1 deletion test/Parse/switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ case _ where x % 2 == 0,
x = 1
case var y where y % 2 == 0:
x = y + 1
case _ where 0: // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
case _ where 0: // expected-error {{integer literal value '0' cannot be used as a boolean; did you mean 'false'?}}
x = 0
default:
x = 1
Expand Down
21 changes: 21 additions & 0 deletions test/Sema/diag_integer_literals.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %target-typecheck-verify-swift

// https://github.com/apple/swift/issues/63753

if 0 {} // expected-error{{integer literal value '0' cannot be used as a boolean; did you mean 'false'?}} {{4-5=false}}
if (0) {} // expected-error{{integer literal value '0' cannot be used as a boolean; did you mean 'false'?}} {{5-6=false}}
if 1 {} // expected-error{{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}} {{4-5=true}}
if (1) {} // expected-error{{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}} {{5-6=true}}
if 12 {} // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
if (12) {} // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}

if 0? {} // expected-error {{cannot use optional chaining on non-optional value of type 'Int'}}
// expected-error@-1{{cannot convert value of type 'Int?' to expected condition type 'Bool'}}
if (0?) {} // expected-error {{cannot use optional chaining on non-optional value of type 'Int'}}
// expected-error@-1{{cannot convert value of type 'Int?' to expected condition type 'Bool'}}
if 0?? {} // expected-error 2{{cannot use optional chaining on non-optional value of type 'Int'}}
// expected-error@-1{{cannot convert value of type 'Int?' to expected condition type 'Bool'}}

let _ = 0? as Int? // expected-error {{cannot use optional chaining on non-optional value of type 'Int'}}
let _ = nil? as Int? // expected-error {{'?' must be followed by a call, member lookup, or subscript}}
let _ = ""? as String? // expected-error {{cannot use optional chaining on non-optional value of type 'String'}}
4 changes: 2 additions & 2 deletions test/Sema/pound_assert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@

#assert(false, "error message")

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

#assert(123, "error message") // expected-error{{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
#assert(123, "error message") // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
4 changes: 2 additions & 2 deletions test/expr/expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func basictest() {

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

//var x6 : Float = 4+5

Expand Down Expand Up @@ -297,7 +297,7 @@ func fib(_ n: Int) -> Int {

// FIXME: Should warn about integer constants being too large <rdar://problem/14070127>
var
il_a: Bool = 4 // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
il_a: Bool = 4 // expected-error {{cannot convert value of type 'Int' to specified type 'Bool'}}
var il_b: Int8
= 123123
var il_c: Int8 = 4 // ok
Expand Down
2 changes: 1 addition & 1 deletion test/stmt/statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ func fn(x: Int) {
}

func bad_if() {
if 1 {} // expected-error {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
if 1 {} // expected-error {{integer literal value '1' cannot be used as a boolean; did you mean 'true'?}} {{6-7=true}}
if (x: false) {} // expected-error {{cannot convert value of type '(x: Bool)' to expected condition type 'Bool'}}
if (x: 1) {} // expected-error {{cannot convert value of type '(x: Int)' to expected condition type 'Bool'}}
if nil {} // expected-error {{'nil' is not compatible with expected condition type 'Bool'}}
Expand Down