Skip to content

Commit 1d9fde8

Browse files
committed
Improve diagnostics for comparisons to nil.
As implemented the diagnostic specific to nil was only firing when nil was on the right hand side.
1 parent 8f5742c commit 1d9fde8

File tree

3 files changed

+86
-34
lines changed

3 files changed

+86
-34
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,13 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
18731873
/// Produce a diagnostic for a general conversion failure (irrespective of the
18741874
/// exact expression kind).
18751875
bool diagnoseGeneralConversionFailure(Constraint *constraint);
1876-
1876+
1877+
/// Produce a diagnostic for binary comparisons of the nil literal
1878+
/// to other values.
1879+
bool diagnoseNilLiteralComparison(Expr *lhsExpr, Expr *rhsExpr,
1880+
CalleeCandidateInfo &calleeInfo,
1881+
SourceLoc applyLoc);
1882+
18771883
bool visitExpr(Expr *E);
18781884
bool visitIdentityExpr(IdentityExpr *E);
18791885
bool visitTupleExpr(TupleExpr *E);
@@ -4360,6 +4366,60 @@ static bool isNameOfStandardComparisonOperator(StringRef opName) {
43604366
opName == "<=" || opName == ">=";
43614367
}
43624368

4369+
bool FailureDiagnosis::diagnoseNilLiteralComparison(
4370+
Expr *lhsExpr, Expr *rhsExpr, CalleeCandidateInfo &calleeInfo,
4371+
SourceLoc applyLoc) {
4372+
4373+
auto overloadName = calleeInfo.declName;
4374+
4375+
// Only diagnose for comparison operators.
4376+
if (!isNameOfStandardComparisonOperator(overloadName))
4377+
return false;
4378+
4379+
Expr *otherExpr = lhsExpr;
4380+
Expr *nilExpr = rhsExpr;
4381+
4382+
// Swap if we picked the wrong side as the nil literal.
4383+
if (!isa<NilLiteralExpr>(nilExpr->getValueProvidingExpr()))
4384+
std::swap(otherExpr, nilExpr);
4385+
4386+
// Bail if neither side is a nil literal.
4387+
if (!isa<NilLiteralExpr>(nilExpr->getValueProvidingExpr()))
4388+
return false;
4389+
4390+
// Bail if both sides are a nil literal.
4391+
if (isa<NilLiteralExpr>(otherExpr->getValueProvidingExpr()))
4392+
return false;
4393+
4394+
auto otherType = otherExpr->getType()->getRValueType();
4395+
4396+
// Bail if we were unable to determine the other type.
4397+
if (isUnresolvedOrTypeVarType(otherType))
4398+
return false;
4399+
4400+
// Regardless of whether the type has reference or value semantics,
4401+
// comparison with nil is illegal, albeit for different reasons spelled
4402+
// out by the diagnosis.
4403+
if (otherType->getAnyOptionalObjectType() &&
4404+
(overloadName == "!==" || overloadName == "===")) {
4405+
auto revisedName = overloadName;
4406+
revisedName.pop_back();
4407+
4408+
// If we made it here, then we're trying to perform a comparison with
4409+
// reference semantics rather than value semantics. The fixit will
4410+
// lop off the extra '=' in the operator.
4411+
diagnose(applyLoc,
4412+
diag::value_type_comparison_with_nil_illegal_did_you_mean,
4413+
otherType)
4414+
.fixItReplace(applyLoc, revisedName);
4415+
} else {
4416+
diagnose(applyLoc, diag::value_type_comparison_with_nil_illegal, otherType)
4417+
.highlight(otherExpr->getSourceRange());
4418+
}
4419+
4420+
return true;
4421+
}
4422+
43634423
bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
43644424
// Type check the function subexpression to resolve a type for it if possible.
43654425
auto fnExpr = typeCheckChildIndependently(callExpr->getFn());
@@ -4527,34 +4587,11 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
45274587
auto lhsExpr = argTuple->getElement(0), rhsExpr = argTuple->getElement(1);
45284588
auto lhsType = lhsExpr->getType()->getRValueType();
45294589
auto rhsType = rhsExpr->getType()->getRValueType();
4530-
4531-
// If this is a comparison against nil, then we should produce a specific
4532-
// diagnostic.
4533-
if (isa<NilLiteralExpr>(rhsExpr->getValueProvidingExpr()) &&
4534-
!isUnresolvedOrTypeVarType(lhsType)) {
4535-
if (isNameOfStandardComparisonOperator(overloadName)) {
4536-
// Regardless of whether the type has reference or value semantics,
4537-
// comparison with nil is illegal, albeit for different reasons spelled
4538-
// out by the diagnosis.
4539-
if (lhsType->getAnyOptionalObjectType() &&
4540-
(overloadName == "!==" || overloadName == "===")) {
4541-
auto revisedName = overloadName;
4542-
revisedName.pop_back();
4543-
// If we made it here, then we're trying to perform a comparison with
4544-
// reference semantics rather than value semantics. The fixit will
4545-
// lop off the extra '=' in the operator.
4546-
diagnose(callExpr->getLoc(),
4547-
diag::value_type_comparison_with_nil_illegal_did_you_mean,
4548-
lhsType)
4549-
.fixItReplace(callExpr->getLoc(), revisedName);
4550-
} else {
4551-
diagnose(callExpr->getLoc(),
4552-
diag::value_type_comparison_with_nil_illegal, lhsType)
4553-
.highlight(lhsExpr->getSourceRange());
4554-
}
4555-
return true;
4556-
}
4557-
}
4590+
4591+
// Diagnose any comparisons with the nil literal.
4592+
if (diagnoseNilLiteralComparison(lhsExpr, rhsExpr, calleeInfo,
4593+
callExpr->getLoc()))
4594+
return true;
45584595

45594596
if (callExpr->isImplicit() && overloadName == "~=") {
45604597
// This binop was synthesized when typechecking an expression pattern.

test/Constraints/diagnostics.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,24 @@ class SR1594 {
777777
}
778778
}
779779

780+
func nilComparison(i: Int, o: AnyObject) {
781+
_ = i == nil // expected-error {{type 'Int' is not optional, value can never be nil}}
782+
_ = nil == i // expected-error {{type 'Int' is not optional, value can never be nil}}
783+
_ = i != nil // expected-error {{type 'Int' is not optional, value can never be nil}}
784+
_ = nil != i // expected-error {{type 'Int' is not optional, value can never be nil}}
785+
_ = i < nil // expected-error {{type 'Int' is not optional, value can never be nil}}
786+
_ = nil < i // expected-error {{type 'Int' is not optional, value can never be nil}}
787+
_ = i <= nil // expected-error {{type 'Int' is not optional, value can never be nil}}
788+
_ = nil <= i // expected-error {{type 'Int' is not optional, value can never be nil}}
789+
_ = i > nil // expected-error {{type 'Int' is not optional, value can never be nil}}
790+
_ = nil > i // expected-error {{type 'Int' is not optional, value can never be nil}}
791+
_ = i >= nil // expected-error {{type 'Int' is not optional, value can never be nil}}
792+
_ = nil >= i // expected-error {{type 'Int' is not optional, value can never be nil}}
793+
794+
_ = o === nil // expected-error {{type 'AnyObject' is not optional, value can never be nil}}
795+
_ = o !== nil // expected-error {{type 'AnyObject' is not optional, value can never be nil}}
796+
}
797+
780798
// FIXME: Bad diagnostic
781799
func secondArgumentNotLabeled(a:Int, _ b: Int) { }
782800
secondArgumentNotLabeled(10, 20)

test/expr/expressions.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -704,12 +704,9 @@ func invalidDictionaryLiteral() {
704704
// nil/metatype comparisons
705705
//===----------------------------------------------------------------------===//
706706
Int.self == nil // expected-error {{type 'Int.Type' is not optional, value can never be nil}}
707-
nil == Int.self // expected-error {{binary operator '==' cannot be applied to operands}}
708-
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists}}
707+
nil == Int.self // expected-error {{type 'Int.Type' is not optional, value can never be nil}}
709708
Int.self != nil // expected-error {{type 'Int.Type' is not optional, value can never be nil}}
710-
nil != Int.self // expected-error {{binary operator '!=' cannot be applied to operands}}
711-
// expected-note @-1 {{overloads for '!=' exist with these partially matching parameter lists}}
712-
709+
nil != Int.self // expected-error {{type 'Int.Type' is not optional, value can never be nil}}
713710

714711
// <rdar://problem/19032294> Disallow postfix ? when not chaining
715712
func testOptionalChaining(_ a : Int?, b : Int!, c : Int??) {

0 commit comments

Comments
 (0)