Skip to content

Commit 4aca39e

Browse files
committed
[Diagnostics] Provide a better fix-it when trying to boolean-negate an optional
1 parent 5661bff commit 4aca39e

File tree

5 files changed

+38
-12
lines changed

5 files changed

+38
-12
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3177,7 +3177,7 @@ NOTE(note_explicitly_compare_cftypeid,none,
31773177

31783178
ERROR(optional_used_as_boolean,none,
31793179
"optional type %0 cannot be used as a boolean; "
3180-
"test for '!= nil' instead", (Type))
3180+
"test for '%select{!|=}1= nil' instead", (Type, bool))
31813181

31823182
ERROR(interpolation_missing_proto,none,
31833183
"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

lib/Sema/CSDiagnostics.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,14 +2306,37 @@ bool ContextualFailure::diagnoseConversionToBool() const {
23062306
return true;
23072307
}
23082308

2309+
// Determine if the boolean negation operator was applied to the anchor. This
2310+
// upwards traversal of the AST is somewhat fragile, but enables much better
2311+
// diagnostics if someone attempts to use an optional or integer as a boolean
2312+
// condition.
2313+
SourceLoc notOperatorLoc;
2314+
if (Expr *parent = findParentExpr(getAnchor())) {
2315+
if (isa<ParenExpr>(parent) && parent->isImplicit()) {
2316+
if ((parent = findParentExpr(parent))) {
2317+
auto parentOperatorApplication = dyn_cast<PrefixUnaryExpr>(parent);
2318+
if (parentOperatorApplication) {
2319+
auto operatorRefExpr =
2320+
dyn_cast<DeclRefExpr>(parentOperatorApplication->getFn());
2321+
if (operatorRefExpr && operatorRefExpr->getDecl()->getBaseName() ==
2322+
getASTContext().Id_NegationOperator) {
2323+
notOperatorLoc = operatorRefExpr->getLoc();
2324+
}
2325+
}
2326+
}
2327+
}
2328+
}
2329+
23092330
// If we're trying to convert something from optional type to Bool, then a
23102331
// comparison against nil was probably expected.
2311-
// TODO: It would be nice to handle "!x" --> x == false, but we have no way
2312-
// to get to the parent expr at present.
23132332
auto fromType = getFromType();
23142333
if (fromType->getOptionalObjectType()) {
23152334
StringRef prefix = "((";
2316-
StringRef suffix = ") != nil)";
2335+
StringRef suffix;
2336+
if (notOperatorLoc.isValid())
2337+
suffix = ") == nil)";
2338+
else
2339+
suffix = ") != nil)";
23172340

23182341
// Check if we need the inner parentheses.
23192342
// Technically we only need them if there's something in 'expr' with
@@ -2325,9 +2348,11 @@ bool ContextualFailure::diagnoseConversionToBool() const {
23252348
}
23262349
// FIXME: The outer parentheses may be superfluous too.
23272350

2328-
emitDiagnostic(expr->getLoc(), diag::optional_used_as_boolean, fromType)
2351+
emitDiagnostic(expr->getLoc(), diag::optional_used_as_boolean, fromType,
2352+
notOperatorLoc.isValid())
23292353
.fixItInsert(expr->getStartLoc(), prefix)
2330-
.fixItInsertAfter(expr->getEndLoc(), suffix);
2354+
.fixItInsertAfter(expr->getEndLoc(), suffix)
2355+
.fixItRemove(notOperatorLoc);
23312356
return true;
23322357
}
23332358

test/Constraints/diagnostics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ 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)}}
137137

138138
// Forgotten ! or ?
139139
var someInt = co.a // expected-error{{value of optional type 'C?' must be unwrapped to refer to member 'a' of wrapped base type 'C'}}

0 commit comments

Comments
 (0)