Skip to content

Commit 5297ca2

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. (cherry picked from commit e7eac0a)
1 parent 692bf6f commit 5297ca2

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
@@ -8107,7 +8107,7 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
81078107
Type unwrappedType = type->getOptionalObjectType();
81088108
if (!unwrappedType)
81098109
return false;
8110-
8110+
81118111
TC.diagnose(affected->getLoc(), diag::optional_not_unwrapped, type,
81128112
unwrappedType);
81138113

@@ -8156,13 +8156,12 @@ bool ConstraintSystem::applySolutionFix(Expr *expr,
81568156
return true;
81578157
}
81588158

8159-
case FixKind::OptionalChaining: {
8159+
case FixKind::UnwrapOptionalBase: {
81608160
auto type = solution.simplifyType(getType(affected))
81618161
->getRValueObjectType();
8162-
auto diag = TC.diagnose(affected->getLoc(),
8163-
diag::missing_unwrap_optional, type);
8164-
diag.fixItInsertAfter(affected->getEndLoc(), "?");
8165-
return true;
8162+
DeclName memberName = fix.first.getDeclNameArgument(*this);
8163+
return diagnoseBaseUnwrapForMemberAccess(affected, type, memberName,
8164+
SourceRange());
81668165
}
81678166

81688167
case FixKind::ForceDowncast: {

lib/Sema/CSDiag.cpp

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

2265-
// If we're not passing down contextual type information this time, but the
2266-
// original failure had type info that wasn't an optional type,
2267-
// then set the flag to prefer fixits with force unwrapping.
2268-
if (!convertType) {
2269-
auto previousType = CS.getContextualType();
2270-
if (previousType && previousType->getOptionalObjectType().isNull())
2271-
TCEOptions |= TypeCheckExprFlags::PreferForceUnwrapToOptional;
2272-
}
2273-
22742265
auto resultTy = CS.TC.typeCheckExpression(
22752266
subExpr, CS.DC, TypeLoc::withoutLoc(convertType), convertTypePurpose,
22762267
TCEOptions, listener, &CS);
@@ -7751,15 +7742,9 @@ bool FailureDiagnosis::diagnoseMemberFailures(
77517742
}
77527743

77537744
if (!optionalResult.ViableCandidates.empty()) {
7754-
diagnose(BaseLoc, diag::optional_base_not_unwrapped,
7755-
baseObjTy, memberName, OT->getOptionalObjectType())
7756-
.highlight(memberRange);
7757-
7758-
diagnose(BaseLoc, diag::optional_base_chain, memberName)
7759-
.fixItInsertAfter(baseExpr->getEndLoc(), "?");
7760-
diagnose(BaseLoc, diag::unwrap_with_force_value)
7761-
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
7762-
return true;
7745+
if (diagnoseBaseUnwrapForMemberAccess(baseExpr, baseObjTy, memberName,
7746+
memberRange))
7747+
return true;
77637748
}
77647749
}
77657750

@@ -8666,3 +8651,22 @@ bool ConstraintSystem::salvage(SmallVectorImpl<Solution> &viable, Expr *expr) {
86668651
diagnoseFailureForExpr(expr);
86678652
return true;
86688653
}
8654+
8655+
bool swift::diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
8656+
DeclName memberName,
8657+
SourceRange memberRange) {
8658+
auto unwrappedBaseType = baseType->getOptionalObjectType();
8659+
if (!unwrappedBaseType)
8660+
return false;
8661+
8662+
ASTContext &ctx = baseType->getASTContext();
8663+
DiagnosticEngine &diags = ctx.Diags;
8664+
diags.diagnose(baseExpr->getLoc(), diag::optional_base_not_unwrapped,
8665+
baseType, memberName, unwrappedBaseType);
8666+
8667+
diags.diagnose(baseExpr->getLoc(), diag::optional_base_chain, memberName)
8668+
.fixItInsertAfter(baseExpr->getEndLoc(), "?");
8669+
diags.diagnose(baseExpr->getLoc(), diag::unwrap_with_force_value)
8670+
.fixItInsertAfter(baseExpr->getEndLoc(), "!");
8671+
return true;
8672+
}

lib/Sema/CSSimplify.cpp

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

35653556
// Look through one level of optional.
@@ -4871,8 +4862,8 @@ ConstraintSystem::simplifyFixConstraint(Fix fix, Type type1, Type type2,
48714862
getDefaultDecompositionOptions(flags) | TMF_ApplyingFix;
48724863
switch (fix.getKind()) {
48734864
case FixKind::ForceOptional:
4874-
case FixKind::OptionalChaining: {
4875-
// Assume that '!' was applied to the first type.
4865+
case FixKind::UnwrapOptionalBase: {
4866+
// Assume that we've unwrapped the first type.
48764867
auto result =
48774868
matchTypes(type1->getRValueObjectType()->getOptionalObjectType(), type2,
48784869
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
@@ -796,10 +796,6 @@ enum class ConstraintSystemFlags {
796796
/// Whether we allow the solver to attempt fixes to the system.
797797
AllowFixes = 0x01,
798798

799-
/// Set if the client prefers fixits to be in the form of force unwrapping
800-
/// or optional chaining to return an optional.
801-
PreferForceUnwrapToOptional = 0x02,
802-
803799
/// If set, this is going to prevent constraint system from erasing all
804800
/// discovered solutions except the best one.
805801
ReturnAllDiscoveredSolutions = 0x04,
@@ -1001,6 +997,9 @@ class ConstraintSystem {
1001997
/// Types used in fixes.
1002998
std::vector<Type> FixedTypes;
1003999

1000+
/// Declaration names used in fixes.
1001+
std::vector<DeclName> FixedDeclNames;
1002+
10041003
/// \brief The set of remembered disjunction choices used to reach
10051004
/// the current constraint system.
10061005
SmallVector<std::pair<ConstraintLocator*, unsigned>, 32>
@@ -3508,6 +3507,15 @@ class OverloadSetCounter : public ASTWalker {
35083507
return { true, expr };
35093508
}
35103509
};
3510+
3511+
/// Diagnose an attempt to recover from a member access into a value of
3512+
/// optional type which needed to be unwrapped for the member to be found.
3513+
///
3514+
/// \returns true if a diagnostic was produced.
3515+
bool diagnoseBaseUnwrapForMemberAccess(Expr *baseExpr, Type baseType,
3516+
DeclName memberName,
3517+
SourceRange memberRange);
3518+
35113519
} // end namespace swift
35123520

35133521
#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
@@ -1856,8 +1856,6 @@ Type TypeChecker::typeCheckExpression(Expr *&expr, DeclContext *dc,
18561856

18571857
// Construct a constraint system from this expression.
18581858
ConstraintSystemOptions csOptions = ConstraintSystemFlags::AllowFixes;
1859-
if (options.contains(TypeCheckExprFlags::PreferForceUnwrapToOptional))
1860-
csOptions |= ConstraintSystemFlags::PreferForceUnwrapToOptional;
18611859
ConstraintSystem cs(*this, dc, csOptions);
18621860
cs.baseCS = baseCS;
18631861
CleanupIllFormedExpressionRAII cleanup(Context, expr);

lib/Sema/TypeChecker.h

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

251-
/// Set if the client prefers fixits to be in the form of force unwrapping
252-
/// or optional chaining to return an optional.
253-
PreferForceUnwrapToOptional = 0x80,
254-
255251
/// If set, don't apply a solution.
256252
SkipApplyingSolution = 0x100,
257253
};

test/Constraints/diagnostics.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,15 +603,19 @@ func someFunction() -> () {
603603
// <rdar://problem/23560128> QoI: trying to mutate an optional dictionary result produces bogus diagnostic
604604
func r23560128() {
605605
var a : (Int,Int)?
606-
a.0 = 42 // expected-error {{value of optional type '(Int, Int)?' not unwrapped; did you mean to use '!' or '?'?}} {{4-4=?}}
606+
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)'}}
607+
// expected-note@-1{{chain the optional }}
608+
// expected-note@-2{{force-unwrap using '!'}}
607609
}
608610

609611
// <rdar://problem/21890157> QoI: wrong error message when accessing properties on optional structs without unwrapping
610612
struct ExampleStruct21890157 {
611613
var property = "property"
612614
}
613615
var example21890157: ExampleStruct21890157?
614-
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' not unwrapped; did you mean to use '!' or '?'?}} {{16-16=?}}
616+
example21890157.property = "confusing" // expected-error {{value of optional type 'ExampleStruct21890157?' must be unwrapped to refer to member 'property' of wrapped base type 'ExampleStruct21890157'}}
617+
// expected-note@-1{{chain the optional }}
618+
// expected-note@-2{{force-unwrap using '!'}}
615619

616620

617621
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)