Skip to content

Commit 098f8e0

Browse files
committed
[SR-839][Sema] Better fixits for optional expressions
In member ref expressions, if the base is optional, and the expected expression result is either optional or unknown, suggest a fixit that makes it into an optional chain expr rather than force unwrapping. Since in many cases the actual fixit is emitted during diagnosis, and thus, while type checking sub exprs with no contextual type specified (so nothing to check for preferring optionality), we also need an additional flag to pass down from FailureDiagnosis for whether we prefer to fix as force unwrapping or optional chaining. I attempted to do this same job via providing a convert type but setting the ConvertTypeIsOnlyAHint flag on the type checker, but unfortunately there are a lot of other moving parts that look at that type, even if it is only supposed to be a hint, so an additional flag to the CS ended up being cleaner.
1 parent 5176173 commit 098f8e0

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)