Skip to content

Commit 86c97df

Browse files
authored
Merge pull request #3483 from rudkx/fix-nil-comparisons
Improve diagnostics for comparisons to nil.
2 parents 61d1c59 + 1d9fde8 commit 86c97df

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)