Skip to content

Commit 5faa8bf

Browse files
committed
Don't offer force-unwrap of the base as a possible fixit for optional
member access if optional chaining is sure to be valid.
1 parent 401be14 commit 5faa8bf

File tree

7 files changed

+124
-14
lines changed

7 files changed

+124
-14
lines changed

lib/Sema/CSApply.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8003,9 +8003,25 @@ bool ConstraintSystem::applySolutionFix(
80038003
case FixKind::UnwrapOptionalBase: {
80048004
auto type = solution.simplifyType(getType(affected))
80058005
->getRValueObjectType();
8006+
bool resultOptional = fix.first.isUnwrapOptionalBaseByOptionalChaining(*this);
8007+
8008+
// If we've resolved the member overload to one that returns an optional
8009+
// type, then the result of the expression is optional (and we want to offer
8010+
// only a '?' fixit) even though the constraint system didn't need to add any
8011+
// additional optionality.
8012+
auto resolvedOverload = getResolvedOverloadSets();
8013+
while (resolvedOverload) {
8014+
if (resolvedOverload->Locator == fix.second) {
8015+
if (resolvedOverload->ImpliedType->getOptionalObjectType())
8016+
resultOptional = true;
8017+
break;
8018+
}
8019+
resolvedOverload = resolvedOverload->Previous;
8020+
}
8021+
80068022
DeclName memberName = fix.first.getDeclNameArgument(*this);
80078023
return diagnoseBaseUnwrapForMemberAccess(affected, type, memberName,
8008-
SourceRange());
8024+
resultOptional, SourceRange());
80098025
}
80108026

80118027
case FixKind::ForceDowncast: {

lib/Sema/CSDiag.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8150,6 +8150,7 @@ bool FailureDiagnosis::diagnoseMemberFailures(
81508150

81518151
if (!optionalResult.ViableCandidates.empty()) {
81528152
if (diagnoseBaseUnwrapForMemberAccess(baseExpr, baseObjTy, memberName,
8153+
/* additionalOptional= */ false,
81538154
memberRange))
81548155
return true;
81558156
}
@@ -9115,6 +9116,7 @@ bool swift::diagnoseUnwrap(TypeChecker &TC, DeclContext *DC,
91159116

91169117
bool swift::diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
91179118
DeclName memberName,
9119+
bool resultOptional,
91189120
SourceRange memberRange) {
91199121
auto unwrappedBaseType = baseType->getOptionalObjectType();
91209122
if (!unwrappedBaseType)
@@ -9125,9 +9127,16 @@ bool swift::diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
91259127
diags.diagnose(baseExpr->getLoc(), diag::optional_base_not_unwrapped,
91269128
baseType, memberName, unwrappedBaseType);
91279129

9130+
// FIXME: It would be nice to immediately offer "base?.member ?? defaultValue"
9131+
// for non-optional results where that would be appropriate. For the moment
9132+
// always offering "?" means that if the user chooses chaining, we'll end up
9133+
// in diagnoseUnwrap() to offer a default value during the next compile.
91289134
diags.diagnose(baseExpr->getLoc(), diag::optional_base_chain, memberName)
91299135
.fixItInsertAfter(baseExpr->getEndLoc(), "?");
9130-
diags.diagnose(baseExpr->getLoc(), diag::unwrap_with_force_value)
9131-
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
9136+
9137+
if (!resultOptional) {
9138+
diags.diagnose(baseExpr->getLoc(), diag::unwrap_with_force_value)
9139+
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
9140+
}
91329141
return true;
91339142
}

lib/Sema/CSSimplify.cpp

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3562,7 +3562,6 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
35623562
ConstraintLocatorBuilder locatorB) {
35633563
// Resolve the base type, if we can. If we can't resolve the base type,
35643564
// then we can't solve this constraint.
3565-
// FIXME: simplifyType() call here could be getFixedTypeRecursive?
35663565
baseTy = simplifyType(baseTy, flags);
35673566
Type baseObjTy = baseTy->getRValueType();
35683567

@@ -3610,14 +3609,43 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
36103609
if (shouldAttemptFixes() && baseObjTy->getOptionalObjectType()) {
36113610
// If the base type was an optional, look through it.
36123611

3613-
// We're unwrapping the base to perform a member access.
3614-
if (recordFix(Fix::getUnwrapOptionalBase(*this, member), locator))
3615-
return SolutionKind::Error;
3616-
3612+
// If the base type is optional because we haven't chosen to force an
3613+
// implicit optional, don't try to fix it. The IUO will be forced instead.
3614+
if (auto dotExpr = dyn_cast<UnresolvedDotExpr>(locator->getAnchor())) {
3615+
auto baseExpr = dotExpr->getBase();
3616+
auto resolvedOverload = getResolvedOverloadSets();
3617+
while (resolvedOverload) {
3618+
if (resolvedOverload->Locator->getAnchor() == baseExpr) {
3619+
if (resolvedOverload->Choice.isImplicitlyUnwrappedValueOrReturnValue())
3620+
return SolutionKind::Error;
3621+
break;
3622+
}
3623+
resolvedOverload = resolvedOverload->Previous;
3624+
}
3625+
}
3626+
3627+
// The result of the member access can either be the expected member type
3628+
// (for '!' or optional members with '?'), or the original member type with
3629+
// one extra level of optionality ('?' with non-optional members).
3630+
auto innerTV =
3631+
createTypeVariable(locator, TVO_CanBindToLValue);
3632+
Type optTy = getTypeChecker().getOptionalType(
3633+
locator->getAnchor()->getSourceRange().Start, innerTV);
3634+
SmallVector<Constraint *, 2> optionalities;
3635+
auto nonoptionalResult = Constraint::createFixed(
3636+
*this, ConstraintKind::Bind, Fix::getUnwrapOptionalBase(*this, member),
3637+
innerTV, memberTy, locator);
3638+
auto optionalResult = Constraint::createFixed(
3639+
*this, ConstraintKind::Bind, Fix::getUnwrapOptionalBase(*this, member),
3640+
optTy, memberTy, locator);
3641+
optionalities.push_back(nonoptionalResult);
3642+
optionalities.push_back(optionalResult);
3643+
addDisjunctionConstraint(optionalities, locator);
3644+
36173645
// Look through one level of optional.
3618-
addValueMemberConstraint(baseObjTy->getOptionalObjectType(),
3619-
member, memberTy, useDC, functionRefKind,
3620-
outerAlternatives, locator);
3646+
addValueMemberConstraint(baseObjTy->getOptionalObjectType(), member,
3647+
innerTV, useDC, functionRefKind, outerAlternatives,
3648+
locator);
36213649
return SolutionKind::Solved;
36223650
}
36233651
return SolutionKind::Error;
@@ -4919,8 +4947,7 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
49194947
TypeMatchOptions subflags =
49204948
getDefaultDecompositionOptions(flags) | TMF_ApplyingFix;
49214949
switch (fix.getKind()) {
4922-
case FixKind::ForceOptional:
4923-
case FixKind::UnwrapOptionalBase: {
4950+
case FixKind::ForceOptional: {
49244951
// Assume that we've unwrapped the first type.
49254952
auto result =
49264953
matchTypes(type1->getRValueObjectType()->getOptionalObjectType(), type2,
@@ -4931,6 +4958,15 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
49314958

49324959
return result;
49334960
}
4961+
4962+
case FixKind::UnwrapOptionalBase: {
4963+
if (recordFix(fix, locator))
4964+
return SolutionKind::Error;
4965+
4966+
// First type already appropriately set.
4967+
return matchTypes(type1, type2, matchKind, subflags, locator);
4968+
}
4969+
49344970
case FixKind::ForceDowncast:
49354971
// These work whenever they are suggested.
49364972
if (recordFix(fix, locator))

lib/Sema/Constraint.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,15 @@ ArrayRef<Identifier> Fix::getArgumentLabels(ConstraintSystem &cs) const {
516516
return cs.FixedArgLabels[Data];
517517
}
518518

519+
/// If this fix has optional result info, retrieve it.
520+
bool Fix::isUnwrapOptionalBaseByOptionalChaining(ConstraintSystem &cs) const {
521+
assert(getKind() == FixKind::UnwrapOptionalBase);
522+
523+
// Assumes that these fixes are always created in pairs, with the first
524+
// created non-optional and the second with an added optional.
525+
return (Data % 2) == 1;
526+
}
527+
519528
StringRef Fix::getName(FixKind kind) {
520529
switch (kind) {
521530
case FixKind::ForceOptional:

lib/Sema/Constraint.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ class Fix {
297297
/// If this fix is an argument re-labeling, retrieve new labels.
298298
ArrayRef<Identifier> getArgumentLabels(ConstraintSystem &cs) const;
299299

300+
/// If this fix has optional result info, retrieve it.
301+
bool isUnwrapOptionalBaseByOptionalChaining(ConstraintSystem &cs) const;
302+
300303
/// Return a string representation of a fix.
301304
static llvm::StringRef getName(FixKind kind);
302305

lib/Sema/ConstraintSystem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3578,7 +3578,7 @@ bool diagnoseUnwrap(TypeChecker &TC, DeclContext *DC, Expr *expr, Type type);
35783578
///
35793579
/// \returns true if a diagnostic was produced.
35803580
bool diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
3581-
DeclName memberName,
3581+
DeclName memberName, bool resultOptional,
35823582
SourceRange memberRange);
35833583

35843584
// Return true if, when replacing "<expr>" with "<expr> ?? T", parentheses need

test/Constraints/fixes.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,40 @@ struct S1116 {
170170

171171
let a1116: [S1116] = []
172172
var s1116 = Set(1...10).subtracting(a1116.map({ $0.s })) // expected-error {{cannot convert value of type '[Int?]' to expected argument type 'Set<Int>'}}
173+
174+
175+
func moreComplexUnwrapFixes() {
176+
struct S { let value: Int }
177+
struct T {
178+
let s: S
179+
let optS: S?
180+
}
181+
func takeNon(_ x: Int) -> Void {}
182+
func takeOpt(_ x: Int?) -> Void {}
183+
184+
let s = S(value: 0)
185+
let t: T? = T(s: s, optS: nil)
186+
let os: S? = s
187+
188+
takeOpt(os.value) // expected-error{{value of optional type 'S?' must be unwrapped to refer to member 'value' of wrapped base type 'S'}}
189+
// expected-note@-1{{chain the optional using '?'}}{{13-13=?}}
190+
takeNon(os.value) // expected-error{{value of optional type 'S?' must be unwrapped to refer to member 'value' of wrapped base type 'S'}}
191+
// expected-note@-1{{chain the optional using '?'}}{{13-13=?}}
192+
// expected-note@-2{{force-unwrap using '!'}}{{13-13=!}}
193+
194+
// FIXME: Ideally we'd recurse evaluating chaining fixits instead of only offering just the unwrap of t
195+
takeOpt(t.s.value) // expected-error{{value of optional type 'T?' must be unwrapped to refer to member 's' of wrapped base type 'T'}}
196+
// expected-note@-1{{chain the optional using '?'}}{{12-12=?}}
197+
// expected-note@-2{{force-unwrap using '!'}}{{12-12=!}}
198+
199+
takeOpt(t.optS.value) // expected-error{{value of optional type 'T?' must be unwrapped to refer to member 'optS' of wrapped base type 'T'}}
200+
// expected-note@-1{{chain the optional using '?'}}{{12-12=?}}
201+
// expected-error@-2{{value of optional type 'S?' must be unwrapped to refer to member 'value' of wrapped base type 'S'}}
202+
// expected-note@-3{{chain the optional using '?'}}{{17-17=?}}
203+
204+
takeNon(t.optS.value) // expected-error{{value of optional type 'T?' must be unwrapped to refer to member 'optS' of wrapped base type 'T'}}
205+
// expected-note@-1{{chain the optional using '?'}}{{12-12=?}}
206+
// expected-error@-2{{value of optional type 'S?' must be unwrapped to refer to member 'value' of wrapped base type 'S'}}
207+
// expected-note@-3{{chain the optional using '?'}}{{17-17=?}}
208+
// expected-note@-4{{force-unwrap using '!'}}{{17-17=!}}
209+
}

0 commit comments

Comments
 (0)