Skip to content

Commit f6c5981

Browse files
committed
Merge pull request swiftlang#1504 from gregomni/sr-839
[SR-839][Sema] Better fixits for optional expressions
2 parents ce3ed51 + 098f8e0 commit f6c5981

File tree

10 files changed

+70
-7
lines changed

10 files changed

+70
-7
lines changed

lib/Sema/CSApply.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5893,6 +5893,15 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
58935893
}
58945894
return true;
58955895
}
5896+
5897+
case FixKind::OptionalChaining: {
5898+
auto type = solution.simplifyType(TC, affected->getType())
5899+
->getRValueObjectType();
5900+
auto diag = TC.diagnose(affected->getLoc(),
5901+
diag::missing_unwrap_optional, type);
5902+
diag.fixItInsertAfter(affected->getEndLoc(), "?");
5903+
return true;
5904+
}
58965905

58975906
case FixKind::ForceDowncast: {
58985907
auto fromType = solution.simplifyType(TC, affected->getType())

lib/Sema/CSDiag.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,6 +2837,15 @@ typeCheckChildIndependently(Expr *subExpr, Type convertType,
28372837
// UnresolvedType and we'll deal with it.
28382838
if (!convertType || options.contains(TCC_AllowUnresolvedTypeVariables))
28392839
TCEOptions |= TypeCheckExprFlags::AllowUnresolvedTypeVariables;
2840+
2841+
// If we're not passing down contextual type information this time, but the
2842+
// original failure had type info that wasn't an optional type,
2843+
// then set the flag to prefer fixits with force unwrapping.
2844+
if (!convertType) {
2845+
auto previousType = CS->getContextualType();
2846+
if (previousType && previousType->getOptionalObjectType().isNull())
2847+
TCEOptions |= TypeCheckExprFlags::PreferForceUnwrapToOptional;
2848+
}
28402849

28412850
bool hadError = CS->TC.typeCheckExpression(subExpr, CS->DC, convertType,
28422851
convertTypePurpose, TCEOptions,

lib/Sema/CSSimplify.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3120,8 +3120,17 @@ ConstraintSystem::simplifyMemberConstraint(const Constraint &constraint) {
31203120
if (constraint.getKind() == ConstraintKind::TypeMember) {
31213121
// If the base type was an optional, try to look through it.
31223122
if (shouldAttemptFixes() && baseObjTy->getOptionalObjectType()) {
3123+
// Determine whether or not we want to provide an optional chaining fixit or
3124+
// a force unwrap fixit.
3125+
bool optionalChain;
3126+
if (!contextualType)
3127+
optionalChain = !(Options & ConstraintSystemFlags::PreferForceUnwrapToOptional);
3128+
else
3129+
optionalChain = !contextualType->getOptionalObjectType().isNull();
3130+
auto fixKind = optionalChain ? FixKind::OptionalChaining : FixKind::ForceOptional;
3131+
31233132
// Note the fix.
3124-
if (recordFix(FixKind::ForceOptional, constraint.getLocator()))
3133+
if (recordFix(fixKind, constraint.getLocator()))
31253134
return SolutionKind::Error;
31263135

31273136
// Look through one level of optional.
@@ -3144,8 +3153,17 @@ ConstraintSystem::simplifyMemberConstraint(const Constraint &constraint) {
31443153
if (shouldAttemptFixes() && baseObjTy->getOptionalObjectType()) {
31453154
// If the base type was an optional, look through it.
31463155

3156+
// Determine whether or not we want to provide an optional chaining fixit or
3157+
// a force unwrap fixit.
3158+
bool optionalChain;
3159+
if (!contextualType)
3160+
optionalChain = !(Options & ConstraintSystemFlags::PreferForceUnwrapToOptional);
3161+
else
3162+
optionalChain = !contextualType->getOptionalObjectType().isNull();
3163+
auto fixKind = optionalChain ? FixKind::OptionalChaining : FixKind::ForceOptional;
3164+
31473165
// Note the fix.
3148-
if (recordFix(FixKind::ForceOptional, constraint.getLocator()))
3166+
if (recordFix(fixKind, constraint.getLocator()))
31493167
return SolutionKind::Error;
31503168

31513169
// Look through one level of optional.
@@ -4142,6 +4160,7 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
41424160
return matchTypes(type1, type2, matchKind, subFlags, locator);
41434161

41444162
case FixKind::ForceOptional:
4163+
case FixKind::OptionalChaining:
41454164
// Assume that '!' was applied to the first type.
41464165
return matchTypes(type1->getRValueObjectType()->getOptionalObjectType(),
41474166
type2, matchKind, subFlags, locator);

lib/Sema/Constraint.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ StringRef Fix::getName(FixKind kind) {
427427
return "prevent fixes";
428428
case FixKind::ForceOptional:
429429
return "fix: force optional";
430+
case FixKind::OptionalChaining:
431+
return "fix: optional chaining";
430432
case FixKind::ForceDowncast:
431433
return "fix: force downcast";
432434
case FixKind::AddressOf:

lib/Sema/Constraint.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ enum class FixKind : uint8_t {
234234

235235
/// Introduce a '!' to force an optional unwrap.
236236
ForceOptional,
237+
238+
/// Introduce a '?.' to begin optional chaining.
239+
OptionalChaining,
237240

238241
/// Append 'as! T' to force a downcast to the specified type.
239242
ForceDowncast,

lib/Sema/ConstraintSystem.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,11 @@ typedef llvm::ilist<Constraint> ConstraintList;
779779

780780
enum class ConstraintSystemFlags {
781781
/// Whether we allow the solver to attempt fixes to the system.
782-
AllowFixes = 0x01
782+
AllowFixes = 0x01,
783+
784+
/// Set if the client prefers fixits to be in the form of force unwrapping
785+
/// or optional chaining to return an optional.
786+
PreferForceUnwrapToOptional = 0x02,
783787
};
784788

785789
/// Options that affect the constraint system as a whole.

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,10 @@ bool TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
12761276
PrettyStackTraceExpr stackTrace(Context, "type-checking", expr);
12771277

12781278
// Construct a constraint system from this expression.
1279-
ConstraintSystem cs(*this, dc, ConstraintSystemFlags::AllowFixes);
1279+
ConstraintSystemOptions csOptions = ConstraintSystemFlags::AllowFixes;
1280+
if (options.contains(TypeCheckExprFlags::PreferForceUnwrapToOptional))
1281+
csOptions |= ConstraintSystemFlags::PreferForceUnwrapToOptional;
1282+
ConstraintSystem cs(*this, dc, csOptions);
12801283
CleanupIllFormedExpressionRAII cleanup(Context, expr);
12811284
ExprCleanser cleanup2(expr);
12821285

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ enum class TypeCheckExprFlags {
199199
/// If set, this expression is being re-type checked as part of diagnostics,
200200
/// and so we should not visit bodies of non-single expression closures.
201201
SkipMultiStmtClosures = 0x40,
202+
203+
/// Set if the client prefers fixits to be in the form of force unwrapping
204+
/// or optional chaining to return an optional.
205+
PreferForceUnwrapToOptional = 0x80,
202206
};
203207

204208
typedef OptionSet<TypeCheckExprFlags> TypeCheckExprOptions;

test/Constraints/diagnostics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,15 +668,15 @@ func someFunction() -> () {
668668
// <rdar://problem/23560128> QoI: trying to mutate an optional dictionary result produces bogus diagnostic
669669
func r23560128() {
670670
var a : (Int,Int)?
671-
a.0 = 42 // expected-error {{value of optional type '(Int, Int)?' not unwrapped; did you mean to use '!' or '?'?}} {{4-4=!}}
671+
a.0 = 42 // expected-error {{value of optional type '(Int, Int)?' not unwrapped; did you mean to use '!' or '?'?}} {{4-4=?}}
672672
}
673673

674674
// <rdar://problem/21890157> QoI: wrong error message when accessing properties on optional structs without unwrapping
675675
struct ExampleStruct21890157 {
676676
var property = "property"
677677
}
678678
var example21890157: ExampleStruct21890157?
679-
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' not unwrapped; did you mean to use '!' or '?'?}} {{16-16=!}}
679+
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' not unwrapped; did you mean to use '!' or '?'?}} {{16-16=?}}
680680

681681

682682
struct UnaryOp {}

test/Constraints/fixes.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,14 @@ ciuo ? true : false // expected-error{{optional type 'C!' cannot be used as a bo
124124
!ciuo // expected-error{{optional type 'C!' cannot be used as a boolean; test for '== nil' instead}}{{1-2=}} {{2-2=(}} {{6-6= == nil)}}
125125

126126
// Forgotten ! or ?
127-
var someInt = co.a // expected-error{{value of optional type 'C?' not unwrapped; did you mean to use '!' or '?'?}} {{17-17=!}}
127+
var someInt = co.a // expected-error{{value of optional type 'C?' not unwrapped; did you mean to use '!' or '?'?}} {{17-17=?}}
128+
129+
// SR-839
130+
struct Q {
131+
let s: String?
132+
}
133+
let q = Q(s: nil)
134+
let a: Int? = q.s.utf8 // expected-error{{value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?}} {{18-18=?}}
135+
let b: Int = q.s.utf8 // expected-error{{value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?}} {{17-17=!}}
136+
let d: Int! = q.s.utf8 // expected-error{{value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?}} {{18-18=!}}
137+
let c = q.s.utf8 // expected-error{{value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?}} {{12-12=?}}

0 commit comments

Comments
 (0)