Skip to content

Commit 5db1901

Browse files
committed
[Type checker] Emit coalesce-or-force-unwrap Fix-Its for necessary unwraps more consistently.
Replace the last (and most obscure) use of the poor “use ‘?’ or ‘!’” diagnostic with the new, more explanatory version that provides separate notes with Fix-Its for coalescing or force-unwrapping the value. Finishes rdar://problem/42081852.
1 parent e7eac0a commit 5db1901

File tree

6 files changed

+85
-65
lines changed

6 files changed

+85
-65
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -902,10 +902,6 @@ NOTE(note_init_parameter,none,
902902
ERROR(missing_nullary_call,none,
903903
"function produces expected type %0; did you mean to call it with '()'?",
904904
(Type))
905-
ERROR(missing_unwrap_optional,none,
906-
"value of optional type %0 not unwrapped; did you mean to use '!' "
907-
"or '?'?",
908-
(Type))
909905
ERROR(optional_not_unwrapped,none,
910906
"value of optional type %0 must be unwrapped to a value of type %1",
911907
(Type, Type))

lib/Sema/CSApply.cpp

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7794,9 +7794,7 @@ static bool exprNeedsParensAfterAddingAs(TypeChecker &TC, DeclContext *DC,
77947794
return exprNeedsParensOutsideFollowingOperator(TC, DC, expr, rootExpr, asPG);
77957795
}
77967796

7797-
// Return true if, when replacing "<expr>" with "<expr> ?? T", parentheses need
7798-
// to be added around <expr> first in order to maintain the correct precedence.
7799-
static bool exprNeedsParensBeforeAddingNilCoalescing(TypeChecker &TC,
7797+
bool swift::exprNeedsParensBeforeAddingNilCoalescing(TypeChecker &TC,
78007798
DeclContext *DC,
78017799
Expr *expr) {
78027800
auto asPG =
@@ -7806,10 +7804,7 @@ static bool exprNeedsParensBeforeAddingNilCoalescing(TypeChecker &TC,
78067804
return exprNeedsParensInsideFollowingOperator(TC, DC, expr, asPG);
78077805
}
78087806

7809-
// Return true if, when replacing "<expr>" with "<expr> as T", parentheses need
7810-
// to be added around the new expression in order to maintain the correct
7811-
// precedence.
7812-
static bool exprNeedsParensAfterAddingNilCoalescing(TypeChecker &TC,
7807+
bool swift::exprNeedsParensAfterAddingNilCoalescing(TypeChecker &TC,
78137808
DeclContext *DC,
78147809
Expr *expr,
78157810
Expr *rootExpr) {
@@ -7981,7 +7976,7 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
79817976

79827977
switch (fix.first.getKind()) {
79837978
case FixKind::ForceOptional: {
7984-
const Expr *unwrapped = affected->getValueProvidingExpr();
7979+
Expr *unwrapped = affected->getValueProvidingExpr();
79857980
auto type = solution.simplifyType(getType(affected))
79867981
->getRValueObjectType();
79877982

@@ -7992,54 +7987,7 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
79927987
"try!");
79937988

79947989
} else {
7995-
Type unwrappedType = type->getOptionalObjectType();
7996-
if (!unwrappedType)
7997-
return false;
7998-
7999-
TC.diagnose(affected->getLoc(), diag::optional_not_unwrapped, type,
8000-
unwrappedType);
8001-
8002-
// Suggest a default value via ?? <default value>
8003-
{
8004-
auto diag =
8005-
TC.diagnose(affected->getLoc(), diag::unwrap_with_default_value);
8006-
8007-
// Figure out what we need to parenthesize.
8008-
bool needsParensInside =
8009-
exprNeedsParensBeforeAddingNilCoalescing(TC, DC, affected);
8010-
bool needsParensOutside =
8011-
exprNeedsParensAfterAddingNilCoalescing(TC, DC, affected, expr);
8012-
8013-
llvm::SmallString<2> insertBefore;
8014-
llvm::SmallString<32> insertAfter;
8015-
if (needsParensOutside) {
8016-
insertBefore += "(";
8017-
}
8018-
if (needsParensInside) {
8019-
insertBefore += "(";
8020-
insertAfter += ")";
8021-
}
8022-
insertAfter += " ?? <" "#default value#" ">";
8023-
if (needsParensOutside)
8024-
insertAfter += ")";
8025-
8026-
if (!insertBefore.empty()) {
8027-
diag.fixItInsert(affected->getStartLoc(), insertBefore);
8028-
}
8029-
diag.fixItInsertAfter(affected->getEndLoc(), insertAfter);
8030-
}
8031-
8032-
// Suggest a force-unwrap.
8033-
{
8034-
auto diag =
8035-
TC.diagnose(affected->getLoc(), diag::unwrap_with_force_value);
8036-
if (affected->canAppendPostfixExpression(true)) {
8037-
diag.fixItInsertAfter(affected->getEndLoc(), "!");
8038-
} else {
8039-
diag.fixItInsert(affected->getStartLoc(), "(")
8040-
.fixItInsertAfter(affected->getEndLoc(), ")!");
8041-
}
8042-
}
7990+
return diagnoseUnwrap(TC, DC, unwrapped, type);
80437991
}
80447992
return true;
80457993
}

lib/Sema/CSDiag.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8968,6 +8968,60 @@ bool ConstraintSystem::salvage(SmallVectorImpl<Solution> &viable, Expr *expr) {
89688968
return true;
89698969
}
89708970

8971+
bool swift::diagnoseUnwrap(TypeChecker &TC, DeclContext *DC,
8972+
Expr *expr, Type type) {
8973+
Type unwrappedType = type->getOptionalObjectType();
8974+
if (!unwrappedType)
8975+
return false;
8976+
8977+
TC.diagnose(expr->getLoc(), diag::optional_not_unwrapped, type,
8978+
unwrappedType);
8979+
8980+
// Suggest a default value via ?? <default value>
8981+
{
8982+
auto diag =
8983+
TC.diagnose(expr->getLoc(), diag::unwrap_with_default_value);
8984+
8985+
// Figure out what we need to parenthesize.
8986+
bool needsParensInside =
8987+
exprNeedsParensBeforeAddingNilCoalescing(TC, DC, expr);
8988+
bool needsParensOutside =
8989+
exprNeedsParensAfterAddingNilCoalescing(TC, DC, expr, expr);
8990+
8991+
llvm::SmallString<2> insertBefore;
8992+
llvm::SmallString<32> insertAfter;
8993+
if (needsParensOutside) {
8994+
insertBefore += "(";
8995+
}
8996+
if (needsParensInside) {
8997+
insertBefore += "(";
8998+
insertAfter += ")";
8999+
}
9000+
insertAfter += " ?? <" "#default value#" ">";
9001+
if (needsParensOutside)
9002+
insertAfter += ")";
9003+
9004+
if (!insertBefore.empty()) {
9005+
diag.fixItInsert(expr->getStartLoc(), insertBefore);
9006+
}
9007+
diag.fixItInsertAfter(expr->getEndLoc(), insertAfter);
9008+
}
9009+
9010+
// Suggest a force-unwrap.
9011+
{
9012+
auto diag =
9013+
TC.diagnose(expr->getLoc(), diag::unwrap_with_force_value);
9014+
if (expr->canAppendPostfixExpression(true)) {
9015+
diag.fixItInsertAfter(expr->getEndLoc(), "!");
9016+
} else {
9017+
diag.fixItInsert(expr->getStartLoc(), "(")
9018+
.fixItInsertAfter(expr->getEndLoc(), ")!");
9019+
}
9020+
}
9021+
9022+
return true;
9023+
}
9024+
89719025
bool swift::diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
89729026
DeclName memberName,
89739027
SourceRange memberRange) {

lib/Sema/CalleeCandidateInfo.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,10 +1012,10 @@ bool CalleeCandidateInfo::diagnoseGenericParameterErrors(Expr *badArgExpr) {
10121012
// Check for optional near miss.
10131013
if (auto argOptType = substitution->getOptionalObjectType()) {
10141014
if (isSubstitutableFor(argOptType, paramArchetype, CS.DC)) {
1015-
CS.TC.diagnose(badArgExpr->getLoc(), diag::missing_unwrap_optional,
1016-
argType);
1017-
foundFailure = true;
1018-
break;
1015+
if (diagnoseUnwrap(CS.TC, CS.DC, badArgExpr, substitution)) {
1016+
foundFailure = true;
1017+
break;
1018+
}
10191019
}
10201020
}
10211021

lib/Sema/ConstraintSystem.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3528,6 +3528,12 @@ class InputMatcher {
35283528
size_t getNumSkippedParameters() const { return NumSkippedParameters; }
35293529
};
35303530

3531+
/// Diagnose an attempt to recover when we have a value of optional type
3532+
/// that needs to be unwrapped.
3533+
///
3534+
/// \returns true if a diagnostic was produced.
3535+
bool diagnoseUnwrap(TypeChecker &TC, DeclContext *DC, Expr *expr, Type type);
3536+
35313537
/// Diagnose an attempt to recover from a member access into a value of
35323538
/// optional type which needed to be unwrapped for the member to be found.
35333539
///
@@ -3536,6 +3542,20 @@ bool diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
35363542
DeclName memberName,
35373543
SourceRange memberRange);
35383544

3545+
// Return true if, when replacing "<expr>" with "<expr> ?? T", parentheses need
3546+
// to be added around <expr> first in order to maintain the correct precedence.
3547+
bool exprNeedsParensBeforeAddingNilCoalescing(TypeChecker &TC,
3548+
DeclContext *DC,
3549+
Expr *expr);
3550+
3551+
// Return true if, when replacing "<expr>" with "<expr> as T", parentheses need
3552+
// to be added around the new expression in order to maintain the correct
3553+
// precedence.
3554+
bool exprNeedsParensAfterAddingNilCoalescing(TypeChecker &TC,
3555+
DeclContext *DC,
3556+
Expr *expr,
3557+
Expr *rootExpr);
3558+
35393559
} // end namespace swift
35403560

35413561
#endif // LLVM_SWIFT_SEMA_CONSTRAINT_SYSTEM_H

test/Constraints/closures.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ struct S<T> {
281281

282282
func subscribe<Object: AnyObject>(object: Object?, method: (Object, T) -> ()) where Object: Hashable {
283283
let wrappedMethod = { (object: AnyObject, value: T) in }
284-
// expected-error @+1 {{value of optional type 'Object?' not unwrapped; did you mean to use '!' or '?'?}}
284+
// expected-error @+3 {{value of optional type 'Object?' must be unwrapped to a value of type 'Object'}}
285+
// expected-note @+2{{coalesce using '??' to provide a default when the optional value contains 'nil'}}
286+
// expected-note @+1{{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
285287
cs.forEach { $0.w.append(value: wrappedMethod, forKey: object) }
286288
}
287289
}

0 commit comments

Comments
 (0)