Skip to content

Commit 81c7be8

Browse files
authored
Merge pull request #27107 from xedin/if-conditional-diag
[Diagnostics] Tailored diagnostic for "condition" expression
2 parents 6f35362 + 232185e commit 81c7be8

File tree

12 files changed

+74
-47
lines changed

12 files changed

+74
-47
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,12 @@ ERROR(cannot_convert_partial_argument_value_protocol,none,
396396
ERROR(cannot_convert_argument_value_nil,none,
397397
"'nil' is not compatible with expected argument type %0", (Type))
398398

399+
ERROR(cannot_convert_condition_value,none,
400+
"cannot convert value of type %0 to expected condition type %1",
401+
(Type, Type))
402+
ERROR(cannot_convert_condition_value_nil,none,
403+
"'nil' is not compatible with expected condition type %0", (Type))
404+
399405
ERROR(cannot_yield_rvalue_by_reference_same_type,none,
400406
"cannot yield immutable value of type %0 as an inout yield", (Type))
401407
ERROR(cannot_yield_rvalue_by_reference,none,

lib/Sema/CSDiagnostics.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,8 @@ Optional<Diag<Type, Type>> GenericArgumentsMismatchFailure::getDiagnosticFor(
747747
return diag::cannot_convert_coerce;
748748
case CTP_SubscriptAssignSource:
749749
return diag::cannot_convert_subscript_assign;
750+
case CTP_Condition:
751+
return diag::cannot_convert_condition_value;
750752

751753
case CTP_ThrowStmt:
752754
case CTP_Unused:
@@ -1347,6 +1349,13 @@ bool RValueTreatedAsLValueFailure::diagnoseAsError() {
13471349
Expr *diagExpr = getRawAnchor();
13481350
SourceLoc loc = diagExpr->getLoc();
13491351

1352+
auto &cs = getConstraintSystem();
1353+
// Assignment is not allowed inside of a condition,
1354+
// so let's not diagnose immutability, because
1355+
// most likely the problem is related to use of `=` itself.
1356+
if (cs.getContextualTypePurpose() == CTP_Condition)
1357+
return false;
1358+
13501359
if (auto assignExpr = dyn_cast<AssignExpr>(diagExpr)) {
13511360
diagExpr = assignExpr->getDest();
13521361
}
@@ -2009,6 +2018,8 @@ getContextualNilDiagnostic(ContextualTypePurpose CTP) {
20092018
return diag::cannot_convert_assign_nil;
20102019
case CTP_SubscriptAssignSource:
20112020
return diag::cannot_convert_subscript_assign_nil;
2021+
case CTP_Condition:
2022+
return diag::cannot_convert_condition_value_nil;
20122023
}
20132024
llvm_unreachable("Unhandled ContextualTypePurpose in switch");
20142025
}
@@ -2746,6 +2757,8 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context,
27462757
case CTP_SubscriptAssignSource:
27472758
return forProtocol ? diag::cannot_convert_subscript_assign_protocol
27482759
: diag::cannot_convert_subscript_assign;
2760+
case CTP_Condition:
2761+
return diag::cannot_convert_condition_value;
27492762

27502763
case CTP_ThrowStmt:
27512764
case CTP_Unused:

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3009,31 +3009,13 @@ bool TypeChecker::typeCheckCondition(Expr *&expr, DeclContext *dc) {
30093009
return !resultTy;
30103010
}
30113011

3012-
/// Expression type checking listener for conditions.
3013-
class ConditionListener : public ExprTypeCheckListener {
3014-
Expr *OrigExpr = nullptr;
3015-
3016-
public:
3017-
// Add the appropriate Boolean constraint.
3018-
bool builtConstraints(ConstraintSystem &cs, Expr *expr) override {
3019-
// Save the original expression.
3020-
OrigExpr = expr;
3021-
3022-
// Otherwise, the result must be convertible to Bool.
3023-
auto boolDecl = cs.getASTContext().getBoolDecl();
3024-
if (!boolDecl)
3025-
return true;
3026-
3027-
// Condition must convert to Bool.
3028-
cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr),
3029-
boolDecl->getDeclaredType(),
3030-
cs.getConstraintLocator(expr));
3031-
return false;
3032-
}
3033-
};
3012+
auto *boolDecl = Context.getBoolDecl();
3013+
if (!boolDecl)
3014+
return true;
30343015

3035-
ConditionListener listener;
3036-
auto resultTy = typeCheckExpression(expr, dc, &listener);
3016+
auto resultTy = typeCheckExpression(
3017+
expr, dc, TypeLoc::withoutLoc(boolDecl->getDeclaredType()),
3018+
CTP_Condition);
30373019
return !resultTy;
30383020
}
30393021

@@ -3165,9 +3147,32 @@ bool TypeChecker::typeCheckExprPattern(ExprPattern *EP, DeclContext *DC,
31653147

31663148
Expr *matchCall = new (Context) BinaryExpr(matchOp, matchArgs,
31673149
/*Implicit=*/true);
3168-
3150+
31693151
// Check the expression as a condition.
3170-
bool hadError = typeCheckCondition(matchCall, DC);
3152+
//
3153+
// TODO: Type-check of `~=` operator can't (yet) use `typeCheckCondition`
3154+
// because that utilizes contextual type which interferes with diagnostics.
3155+
// We don't yet have a full access to pattern-matching context in
3156+
// constraint system, which is required to enable these situations
3157+
// to be properly diagnosed.
3158+
struct ConditionListener : public ExprTypeCheckListener {
3159+
// Add the appropriate Boolean constraint.
3160+
bool builtConstraints(ConstraintSystem &cs, Expr *expr) override {
3161+
// Otherwise, the result must be convertible to Bool.
3162+
auto boolDecl = cs.getASTContext().getBoolDecl();
3163+
if (!boolDecl)
3164+
return true;
3165+
3166+
// Condition must convert to Bool.
3167+
cs.addConstraint(ConstraintKind::Conversion, cs.getType(expr),
3168+
boolDecl->getDeclaredType(),
3169+
cs.getConstraintLocator(expr));
3170+
return false;
3171+
}
3172+
};
3173+
3174+
ConditionListener listener;
3175+
bool hadError = !typeCheckExpression(matchCall, DC, &listener);
31713176
// Save the type-checked expression in the pattern.
31723177
EP->setMatchExpr(matchCall);
31733178
// Set the type on the pattern.

lib/Sema/TypeChecker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ enum ContextualTypePurpose {
219219
CTP_AssignSource, ///< AssignExpr source operand coerced to result type.
220220
CTP_SubscriptAssignSource, ///< AssignExpr source operand coerced to subscript
221221
///< result type.
222+
CTP_Condition, ///< Condition expression of various statements e.g.
223+
///< `if`, `for`, `while` etc.
222224

223225
CTP_CannotFail, ///< Conversion can never fail. abort() if it does.
224226
};

test/Constraints/diagnostics.swift

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

195195
// <rdar://problem/17020197> QoI: Operand of postfix '!' should have optional type; type is 'Int?'
196196
func r17020197(_ x : Int?, y : Int) {
197-
if x! { } // expected-error {{'Int' is not convertible to 'Bool'}}
197+
if x! { } // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
198198

199199
// <rdar://problem/12939553> QoI: diagnostic for using an integer in a condition is utterly terrible
200-
if y {} // expected-error {{'Int' is not convertible to 'Bool'}}
200+
if y {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
201201
}
202202

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

test/Constraints/invalid_logicvalue_coercion.swift

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

33
class C {}
44
var c = C()
5-
if c as C { // expected-error{{'C' is not convertible to 'Bool'}}
5+
if c as C { // expected-error{{cannot convert value of type 'C' to expected condition type 'Bool'}}
66
}
77

8-
if ({1} as () -> Int) { // expected-error{{'() -> Int' is not convertible to 'Bool'}}
8+
if ({1} as () -> Int) { // expected-error{{cannot convert value of type '() -> Int' to expected condition type 'Bool'}}
99
}

test/Misc/misc_diagnostics.swift

Lines changed: 2 additions & 2 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{{'Int' is not convertible to 'Bool'}}
26-
if 1 {} // expected-error {{'Int' is not convertible to 'Bool'}}
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'}}
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'}}

test/Parse/switch.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ case 10,
6262
case _ where x % 2 == 0,
6363
20:
6464
x = 1
65-
case var y where y % 2 == 0: // expected-warning {{variable 'y' was never mutated; consider changing to 'let' constant}}
65+
case var y where y % 2 == 0:
6666
x = y + 1
67-
case _ where 0: // expected-error {{'Int' is not convertible to 'Bool'}}
67+
case _ where 0: // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
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{{'Int' is not convertible to 'Bool'}}
11+
#assert(123) // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}
1212

13-
#assert(123, "error message") // expected-error{{'Int' is not convertible to 'Bool'}}
13+
#assert(123, "error message") // expected-error{{cannot convert value of type 'Int' to expected condition type 'Bool'}}

test/expr/expressions.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -799,14 +799,14 @@ func testOptionalTypeParsing(_ a : AnyObject) -> String {
799799
func testParenExprInTheWay() {
800800
let x = 42
801801

802-
if x & 4.0 {} // expected-error {{binary operator '&' cannot be applied to operands of type 'Int' and 'Double'}} expected-note {{expected an argument list of type '(Int, Int)'}}
803-
802+
if x & 4.0 {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
803+
// expected-error@-1 {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
804804
if (x & 4.0) {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
805-
805+
// expected-error@-1 {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
806806
if !(x & 4.0) {} // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
807807
// expected-error@-1 {{cannot convert value of type 'Int' to expected argument type 'Bool'}}
808-
809-
if x & x {} // expected-error {{'Int' is not convertible to 'Bool'}}
808+
809+
if x & x {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
810810
}
811811

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

test/stmt/if_while_var.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ if 1 != 2, case let x? : Int? = 42 {} // expected-warning {{immutable value 'x'
8888

8989
// Test error recovery.
9090
// <rdar://problem/19939746> Improve error recovery for malformed if statements
91-
if 1 != 2, { // expected-error {{'() -> ()' is not convertible to 'Bool'}}
91+
if 1 != 2, { // expected-error {{cannot convert value of type '() -> ()' to expected condition type 'Bool'}}
9292
} // expected-error {{expected '{' after 'if' condition}}
9393
if 1 != 2, 4 == 57 {}
9494
if 1 != 2, 4 == 57, let x = opt {} // expected-warning {{immutable value 'x' was never used; consider replacing with '_' or removing it}}

test/stmt/statements.swift

Lines changed: 6 additions & 5 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 {{'Int' is not convertible to 'Bool'}}
62+
if x {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
6363

6464
if true {
6565
if (B) {
@@ -99,7 +99,7 @@ struct infloopbool {
9999
}
100100

101101
func infloopbooltest() {
102-
if (infloopbool()) {} // expected-error {{'infloopbool' is not convertible to 'Bool'}}
102+
if (infloopbool()) {} // expected-error {{cannot convert value of type 'infloopbool' to expected condition type 'Bool'}}
103103
}
104104

105105
// test "builder" API style
@@ -566,9 +566,10 @@ func fn(x: Int) {
566566
}
567567

568568
func bad_if() {
569-
if 1 {} // expected-error {{'Int' is not convertible to 'Bool'}}
570-
if (x: false) {} // expected-error {{'(x: Bool)' is not convertible to 'Bool'}}
571-
if (x: 1) {} // expected-error {{'(x: Int)' is not convertible to 'Bool'}}
569+
if 1 {} // expected-error {{cannot convert value of type 'Int' to expected condition type 'Bool'}}
570+
if (x: false) {} // expected-error {{cannot convert value of type '(x: Bool)' to expected condition type 'Bool'}}
571+
if (x: 1) {} // expected-error {{cannot convert value of type '(x: Int)' to expected condition type 'Bool'}}
572+
if nil {} // expected-error {{'nil' is not compatible with expected condition type 'Bool'}}
572573
}
573574

574575
// Typo correction for loop labels

0 commit comments

Comments
 (0)