Skip to content

Commit 202234f

Browse files
committed
[CSDiagnostics] Diagnose invalid optional unwrap via fixes
Detect and fix situations when (force) unwrap is used on a non-optional type, this helps to diagnose invalid unwraps precisely and provide fix-its. Resolves: [SR-8977](https://bugs.swift.org/browse/SR-8977) Resolves: rdar://problem/45218255
1 parent e043e2b commit 202234f

File tree

8 files changed

+64
-5
lines changed

8 files changed

+64
-5
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,3 +1226,17 @@ bool AutoClosureForwardingFailure::diagnoseAsError() {
12261226
.fixItInsertAfter(argExpr->getEndLoc(), "()");
12271227
return true;
12281228
}
1229+
1230+
bool NonOptionalUnwrapFailure::diagnoseAsError() {
1231+
auto *anchor = getAnchor();
1232+
1233+
auto diagnostic = diag::invalid_optional_chain;
1234+
if (isa<ForceValueExpr>(anchor))
1235+
diagnostic = diag::invalid_force_unwrap;
1236+
1237+
emitDiagnostic(anchor->getLoc(), diagnostic, BaseType)
1238+
.highlight(anchor->getSourceRange())
1239+
.fixItRemove(anchor->getEndLoc());
1240+
1241+
return true;
1242+
}

lib/Sema/CSDiagnostics.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,29 @@ class AutoClosureForwardingFailure final : public FailureDiagnostic {
607607
bool diagnoseAsError() override;
608608
};
609609

610+
/// Diagnose situations when there was an attempt to unwrap entity
611+
/// of non-optional type e.g.
612+
///
613+
/// ```swift
614+
/// let i: Int = 0
615+
/// _ = i!
616+
///
617+
/// struct A { func foo() {} }
618+
/// func foo(_ a: A) {
619+
/// a?.foo()
620+
/// }
621+
/// ```
622+
class NonOptionalUnwrapFailure final : public FailureDiagnostic {
623+
Type BaseType;
624+
625+
public:
626+
NonOptionalUnwrapFailure(Expr *root, ConstraintSystem &cs, Type baseType,
627+
ConstraintLocator *locator)
628+
: FailureDiagnostic(root, cs, locator), BaseType(baseType) {}
629+
630+
bool diagnoseAsError() override;
631+
};
632+
610633
} // end namespace constraints
611634
} // end namespace swift
612635

lib/Sema/CSFix.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ AutoClosureForwarding *AutoClosureForwarding::create(ConstraintSystem &cs,
215215
}
216216

217217
bool RemoveUnwrap::diagnose(Expr *root, bool asNote) const {
218-
return false;
218+
auto failure = NonOptionalUnwrapFailure(root, getConstraintSystem(), BaseType,
219+
getLocator());
220+
return failure.diagnose(asNote);
219221
}
220222

221223
RemoveUnwrap *RemoveUnwrap::create(ConstraintSystem &cs, Type baseType,

test/Constraints/diagnostics.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,14 @@ func rdar17170728() {
11831183

11841184
let _ = [i, j, k].reduce(0 as Int?) {
11851185
$0 && $1 ? $0! + $1! : ($0 ? $0! : ($1 ? $1! : nil))
1186+
// expected-error@-1 {{cannot force unwrap value of non-optional type 'Bool'}} {{18-19=}}
1187+
// expected-error@-2 {{cannot force unwrap value of non-optional type 'Bool'}} {{24-25=}}
1188+
// expected-error@-3 {{cannot force unwrap value of non-optional type 'Bool'}} {{36-37=}}
1189+
// expected-error@-4 {{cannot force unwrap value of non-optional type 'Bool'}} {{48-49=}}
1190+
}
1191+
1192+
let _ = [i, j, k].reduce(0 as Int?) {
1193+
$0 && $1 ? $0 + $1 : ($0 ? $0 : ($1 ? $1 : nil))
11861194
// expected-error@-1 {{ambiguous use of operator '+'}}
11871195
}
11881196
}

test/Constraints/dynamic_lookup.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ type(of: obj).foo!(obj)(5) // expected-error{{instance member 'foo' cannot be us
210210

211211
// Checked casts to AnyObject
212212
var p: P = Y()
213-
// expected-warning @+1 {{forced cast from 'P' to 'AnyObject' always succeeds; did you mean to use 'as'?}}
214213
var obj3 : AnyObject = (p as! AnyObject)! // expected-error{{cannot force unwrap value of non-optional type 'AnyObject'}} {{41-42=}}
215214

216215
// Implicit force of an implicitly unwrapped optional

test/Constraints/optional.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,13 @@ func se0213() {
292292
_ = Q("who")!.foo // Ok
293293
_ = Q?("how") // Ok
294294
}
295+
296+
func rdar45218255(_ i: Int) {
297+
struct S<T> {
298+
init(_:[T]) {}
299+
}
300+
301+
_ = i! // expected-error {{cannot force unwrap value of non-optional type 'Int'}} {{8-9=}}
302+
_ = [i!] // expected-error {{cannot force unwrap value of non-optional type 'Int'}} {{9-10=}}
303+
_ = S<Int>([i!]) // expected-error {{cannot force unwrap value of non-optional type 'Int'}} {{16-17=}}
304+
}

test/Parse/operators.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ infix operator !!
116116
func !!(x: Man, y: Man) {}
117117
let foo = Man()
118118
let bar = TheDevil()
119-
foo!!foo // expected-error{{cannot force unwrap value of non-optional type 'Man'}} {{4-5=}} expected-error{{consecutive statements}} {{6-6=;}}
120-
// expected-warning @-1 {{expression of type 'Man' is unused}}
119+
foo!!foo
120+
// expected-error@-1 {{cannot force unwrap value of non-optional type 'Man'}} {{4-5=}}
121+
// expected-error@-2 {{cannot force unwrap value of non-optional type 'Man'}} {{5-6=}}
122+
// expected-error@-3 {{consecutive statements}} {{6-6=;}}
123+
// expected-warning@-4 {{expression of type 'Man' is unused}}
121124

122125
foo??bar // expected-error{{broken standard library}} expected-error{{consecutive statements}} {{6-6=;}}
123126
// expected-warning @-1 {{expression of type 'TheDevil' is unused}}

test/decl/init/failable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Sub : Super {
6060
}
6161

6262
convenience init(forceNonfail: Int) {
63-
self.init(nonfail: forceNonfail)! // expected-error{{cannot force unwrap value of non-optional type '()'}} {{37-38=}}
63+
self.init(nonfail: forceNonfail)! // expected-error{{cannot force unwrap value of non-optional type 'Sub'}} {{37-38=}}
6464
}
6565

6666
init(nonfail2: Int) { // okay, traps on nil

0 commit comments

Comments
 (0)