Skip to content

Commit e7eac0a

Browse files
committed
[Type checker] Extend the diagnostics for unwrapping the base of a member access.
Introduce a new fix kind into the constraint solver to cover unwrapping the base of a member access so we can refer to the a member of the unwrapped base. Wire this fix kind to the just-added diagnostic that suggests either the chaining ‘?’ or the force-unwrap ‘!’ via separate, descriptive Fix-Its. Example: error: value of optional type 'X?' must be unwrapped to refer to member 'f' of wrapped base type 'X' let _: Int = x.f() ^ note: chain the optional using '?' to access member 'f' only for non-'nil' base values let _: Int = x.f() ^ ? note: force-unwrap using '!' to abort execution if the optional value contains 'nil' let _: Int = x.f() ^ ! Before this, we would sometimes get a Fix-It for just ‘?’ and sometimes get a Fix-It for the coalescing ‘??’, neither of which is likely to be right. More work on rdar://problem/42081852.
1 parent 9ec3b00 commit e7eac0a

File tree

10 files changed

+81
-56
lines changed

10 files changed

+81
-56
lines changed

lib/Sema/CSApply.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7995,7 +7995,7 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
79957995
Type unwrappedType = type->getOptionalObjectType();
79967996
if (!unwrappedType)
79977997
return false;
7998-
7998+
79997999
TC.diagnose(affected->getLoc(), diag::optional_not_unwrapped, type,
80008000
unwrappedType);
80018001

@@ -8044,13 +8044,12 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
80448044
return true;
80458045
}
80468046

8047-
case FixKind::OptionalChaining: {
8047+
case FixKind::UnwrapOptionalBase: {
80488048
auto type = solution.simplifyType(getType(affected))
80498049
->getRValueObjectType();
8050-
auto diag = TC.diagnose(affected->getLoc(),
8051-
diag::missing_unwrap_optional, type);
8052-
diag.fixItInsertAfter(affected->getEndLoc(), "?");
8053-
return true;
8050+
DeclName memberName = fix.first.getDeclNameArgument(*this);
8051+
return diagnoseBaseUnwrapForMemberAccess(affected, type, memberName,
8052+
SourceRange());
80548053
}
80558054

80568055
case FixKind::ForceDowncast: {

lib/Sema/CSDiag.cpp

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,15 +2419,6 @@ Expr *FailureDiagnosis::typeCheckChildIndependently(
24192419
allowFreeTypeVariables)
24202420
TCEOptions |= TypeCheckExprFlags::AllowUnresolvedTypeVariables;
24212421

2422-
// If we're not passing down contextual type information this time, but the
2423-
// original failure had type info that wasn't an optional type,
2424-
// then set the flag to prefer fixits with force unwrapping.
2425-
if (!convertType) {
2426-
auto previousType = CS.getContextualType();
2427-
if (previousType && previousType->getOptionalObjectType().isNull())
2428-
TCEOptions |= TypeCheckExprFlags::PreferForceUnwrapToOptional;
2429-
}
2430-
24312422
auto resultTy = CS.TC.typeCheckExpression(
24322423
subExpr, CS.DC, TypeLoc::withoutLoc(convertType), convertTypePurpose,
24332424
TCEOptions, listener, &CS);
@@ -8067,15 +8058,9 @@ bool FailureDiagnosis::diagnoseMemberFailures(
80678058
}
80688059

80698060
if (!optionalResult.ViableCandidates.empty()) {
8070-
diagnose(BaseLoc, diag::optional_base_not_unwrapped,
8071-
baseObjTy, memberName, OT->getOptionalObjectType())
8072-
.highlight(memberRange);
8073-
8074-
diagnose(BaseLoc, diag::optional_base_chain, memberName)
8075-
.fixItInsertAfter(baseExpr->getEndLoc(), "?");
8076-
diagnose(BaseLoc, diag::unwrap_with_force_value)
8077-
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
8078-
return true;
8061+
if (diagnoseBaseUnwrapForMemberAccess(baseExpr, baseObjTy, memberName,
8062+
memberRange))
8063+
return true;
80798064
}
80808065
}
80818066

@@ -8982,3 +8967,22 @@ bool ConstraintSystem::salvage(SmallVectorImpl<Solution> &viable, Expr *expr) {
89828967
diagnoseFailureForExpr(expr);
89838968
return true;
89848969
}
8970+
8971+
bool swift::diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
8972+
DeclName memberName,
8973+
SourceRange memberRange) {
8974+
auto unwrappedBaseType = baseType->getOptionalObjectType();
8975+
if (!unwrappedBaseType)
8976+
return false;
8977+
8978+
ASTContext &ctx = baseType->getASTContext();
8979+
DiagnosticEngine &diags = ctx.Diags;
8980+
diags.diagnose(baseExpr->getLoc(), diag::optional_base_not_unwrapped,
8981+
baseType, memberName, unwrappedBaseType);
8982+
8983+
diags.diagnose(baseExpr->getLoc(), diag::optional_base_chain, memberName)
8984+
.fixItInsertAfter(baseExpr->getEndLoc(), "?");
8985+
diags.diagnose(baseExpr->getLoc(), diag::unwrap_with_force_value)
8986+
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
8987+
return true;
8988+
}

lib/Sema/CSSimplify.cpp

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3554,18 +3554,9 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
35543554
// Value member lookup has some hacks too.
35553555
if (shouldAttemptFixes() && baseObjTy->getOptionalObjectType()) {
35563556
// If the base type was an optional, look through it.
3557-
3558-
// Determine whether or not we want to provide an optional chaining fixit or
3559-
// a force unwrap fixit.
3560-
bool optionalChain;
3561-
if (!getContextualType())
3562-
optionalChain = !(Options & ConstraintSystemFlags::PreferForceUnwrapToOptional);
3563-
else
3564-
optionalChain = !getContextualType()->getOptionalObjectType().isNull();
3565-
auto fixKind = optionalChain ? FixKind::OptionalChaining : FixKind::ForceOptional;
3566-
3567-
// Note the fix.
3568-
if (recordFix(fixKind, locator))
3557+
3558+
// We're unwrapping the base to perform a member access.
3559+
if (recordFix(Fix::getUnwrapOptionalBase(*this, member), locator))
35693560
return SolutionKind::Error;
35703561

35713562
// Look through one level of optional.
@@ -4877,8 +4868,8 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
48774868
getDefaultDecompositionOptions(flags) | TMF_ApplyingFix;
48784869
switch (fix.getKind()) {
48794870
case FixKind::ForceOptional:
4880-
case FixKind::OptionalChaining: {
4881-
// Assume that '!' was applied to the first type.
4871+
case FixKind::UnwrapOptionalBase: {
4872+
// Assume that we've unwrapped the first type.
48824873
auto result =
48834874
matchTypes(type1->getRValueObjectType()->getOptionalObjectType(), type2,
48844875
matchKind, subflags, locator);

lib/Sema/Constraint.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,17 +487,29 @@ Fix Fix::getForcedDowncast(ConstraintSystem &cs, Type toType) {
487487
return Fix(FixKind::ForceDowncast, index);
488488
}
489489

490+
Fix Fix::getUnwrapOptionalBase(ConstraintSystem &cs, DeclName memberName) {
491+
unsigned index = cs.FixedDeclNames.size();
492+
cs.FixedDeclNames.push_back(memberName);
493+
return Fix(FixKind::UnwrapOptionalBase, index);
494+
}
495+
490496
Type Fix::getTypeArgument(ConstraintSystem &cs) const {
491497
assert(getKind() == FixKind::ForceDowncast);
492498
return cs.FixedTypes[Data];
493499
}
494500

501+
/// If this fix has a name argument, retrieve it.
502+
DeclName Fix::getDeclNameArgument(ConstraintSystem &cs) const {
503+
assert(getKind() == FixKind::UnwrapOptionalBase);
504+
return cs.FixedDeclNames[Data];
505+
}
506+
495507
StringRef Fix::getName(FixKind kind) {
496508
switch (kind) {
497509
case FixKind::ForceOptional:
498510
return "fix: force optional";
499-
case FixKind::OptionalChaining:
500-
return "fix: optional chaining";
511+
case FixKind::UnwrapOptionalBase:
512+
return "fix: unwrap optional base of member lookup";
501513
case FixKind::ForceDowncast:
502514
return "fix: force downcast";
503515
case FixKind::AddressOf:

lib/Sema/Constraint.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ enum class FixKind : uint8_t {
233233
/// Introduce a '!' to force an optional unwrap.
234234
ForceOptional,
235235

236-
/// Introduce a '?.' to begin optional chaining.
237-
OptionalChaining,
236+
/// Unwrap an optional base when we have a member access.
237+
UnwrapOptionalBase,
238238

239239
/// Append 'as! T' to force a downcast to the specified type.
240240
ForceDowncast,
@@ -265,17 +265,26 @@ class Fix {
265265
public:
266266
Fix(FixKind kind) : Kind(kind), Data(0) {
267267
assert(kind != FixKind::ForceDowncast && "Use getForceDowncast()");
268+
assert(kind != FixKind::UnwrapOptionalBase &&
269+
"Use getUnwrapOptionalBase()");
268270
}
269271

270272
/// Produce a new fix that performs a forced downcast to the given type.
271273
static Fix getForcedDowncast(ConstraintSystem &cs, Type toType);
272274

275+
/// Produce a new fix that unwraps an optional base for an access to a member
276+
/// with the given name.
277+
static Fix getUnwrapOptionalBase(ConstraintSystem &cs, DeclName memberName);
278+
273279
/// Retrieve the kind of fix.
274280
FixKind getKind() const { return Kind; }
275281

276282
/// If this fix has a type argument, retrieve it.
277283
Type getTypeArgument(ConstraintSystem &cs) const;
278284

285+
/// If this fix has a name argument, retrieve it.
286+
DeclName getDeclNameArgument(ConstraintSystem &cs) const;
287+
279288
/// Return a string representation of a fix.
280289
static llvm::StringRef getName(FixKind kind);
281290

lib/Sema/ConstraintSystem.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -787,10 +787,6 @@ enum class ConstraintSystemFlags {
787787
/// Whether we allow the solver to attempt fixes to the system.
788788
AllowFixes = 0x01,
789789

790-
/// Set if the client prefers fixits to be in the form of force unwrapping
791-
/// or optional chaining to return an optional.
792-
PreferForceUnwrapToOptional = 0x02,
793-
794790
/// If set, this is going to prevent constraint system from erasing all
795791
/// discovered solutions except the best one.
796792
ReturnAllDiscoveredSolutions = 0x04,
@@ -992,6 +988,9 @@ class ConstraintSystem {
992988
/// Types used in fixes.
993989
std::vector<Type> FixedTypes;
994990

991+
/// Declaration names used in fixes.
992+
std::vector<DeclName> FixedDeclNames;
993+
995994
/// \brief The set of remembered disjunction choices used to reach
996995
/// the current constraint system.
997996
SmallVector<std::pair<ConstraintLocator*, unsigned>, 32>
@@ -3528,6 +3527,15 @@ class InputMatcher {
35283527

35293528
size_t getNumSkippedParameters() const { return NumSkippedParameters; }
35303529
};
3530+
3531+
/// Diagnose an attempt to recover from a member access into a value of
3532+
/// optional type which needed to be unwrapped for the member to be found.
3533+
///
3534+
/// \returns true if a diagnostic was produced.
3535+
bool diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
3536+
DeclName memberName,
3537+
SourceRange memberRange);
3538+
35313539
} // end namespace swift
35323540

35333541
#endif // LLVM_SWIFT_SEMA_CONSTRAINT_SYSTEM_H

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,8 +1953,6 @@ Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
19531953

19541954
// Construct a constraint system from this expression.
19551955
ConstraintSystemOptions csOptions = ConstraintSystemFlags::AllowFixes;
1956-
if (options.contains(TypeCheckExprFlags::PreferForceUnwrapToOptional))
1957-
csOptions |= ConstraintSystemFlags::PreferForceUnwrapToOptional;
19581956
ConstraintSystem cs(*this, dc, csOptions);
19591957
cs.baseCS = baseCS;
19601958
CleanupIllFormedExpressionRAII cleanup(expr);

lib/Sema/TypeChecker.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,6 @@ enum class TypeCheckExprFlags {
260260
/// and so we should not visit bodies of non-single expression closures.
261261
SkipMultiStmtClosures = 0x40,
262262

263-
/// Set if the client prefers fixits to be in the form of force unwrapping
264-
/// or optional chaining to return an optional.
265-
PreferForceUnwrapToOptional = 0x80,
266-
267263
/// If set, don't apply a solution.
268264
SkipApplyingSolution = 0x100,
269265
};

test/Constraints/diagnostics.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,15 +638,19 @@ func someFunction() -> () {
638638
// <rdar://problem/23560128> QoI: trying to mutate an optional dictionary result produces bogus diagnostic
639639
func r23560128() {
640640
var a : (Int,Int)?
641-
a.0 = 42 // expected-error {{value of optional type '(Int, Int)?' not unwrapped; did you mean to use '!' or '?'?}} {{4-4=?}}
641+
a.0 = 42 // expected-error{{value of optional type '(Int, Int)?' must be unwrapped to refer to member '0' of wrapped base type '(Int, Int)'}}
642+
// expected-note@-1{{chain the optional }}
643+
// expected-note@-2{{force-unwrap using '!'}}
642644
}
643645

644646
// <rdar://problem/21890157> QoI: wrong error message when accessing properties on optional structs without unwrapping
645647
struct ExampleStruct21890157 {
646648
var property = "property"
647649
}
648650
var example21890157: ExampleStruct21890157?
649-
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' not unwrapped; did you mean to use '!' or '?'?}} {{16-16=?}}
651+
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' must be unwrapped to refer to member 'property' of wrapped base type 'ExampleStruct21890157'}}
652+
// expected-note@-1{{chain the optional }}
653+
// expected-note@-2{{force-unwrap using '!'}}
650654

651655

652656
struct UnaryOp {}

test/Constraints/fixes.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ ciuo ? true : false // expected-error{{optional type 'C?' cannot be used as a bo
141141
!ciuo // expected-error{{optional type 'C?' cannot be used as a boolean; test for '!= nil' instead}}{{2-2=(}} {{6-6= != nil)}}
142142

143143
// Forgotten ! or ?
144-
var someInt = co.a // expected-error{{value of optional type 'C?' not unwrapped; did you mean to use '!' or '?'?}} {{17-17=?}}
144+
var someInt = co.a // expected-error{{value of optional type 'C?' must be unwrapped to refer to member 'a' of wrapped base type 'C'}}
145+
// expected-note@-1{{chain the optional using '?' to access member 'a' only for non-'nil' base values}}{{17-17=?}}
146+
// expected-note@-2{{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}{{17-17=!}}
145147

146148
// SR-839
147149
struct Q {
@@ -157,7 +159,9 @@ let b: Int = q.s.utf8 // expected-error{{value of optional type 'String?' must b
157159
let d: Int! = q.s.utf8 // expected-error{{value of optional type 'String?' must be unwrapped to refer to member 'utf8' of wrapped base type 'String'}}
158160
// expected-note@-1{{chain the optional using '?'}}{{18-18=?}}
159161
// expected-note@-2{{force-unwrap using '!'}}{{18-18=!}}
160-
let c = q.s.utf8 // expected-error{{value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?}} {{12-12=?}}
162+
let c = q.s.utf8 // expected-error{{value of optional type 'String?' must be unwrapped to refer to member 'utf8' of wrapped base type 'String'}}
163+
// expected-note@-1{{chain the optional using '?' to access member 'utf8' only for non-'nil' base values}}{{12-12=?}}
164+
// expected-note@-2{{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}{{12-12=!}}
161165

162166
// SR-1116
163167
struct S1116 {

0 commit comments

Comments
 (0)