Skip to content

Commit d2f131f

Browse files
Merge pull request swiftlang#32843 from LucianoPAlmeida/keypath-root-diagnostic-improvement
[Diagnostics][Sema] Improving diagnostics for optional key path application root/base mismatch
2 parents d525d17 + 9371ef0 commit d2f131f

File tree

9 files changed

+148
-7
lines changed

9 files changed

+148
-7
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,9 @@ NOTE(optional_base_chain,none,
10771077
"base values", (DeclNameRef))
10781078
NOTE(optional_base_remove_optional_for_keypath_root, none,
10791079
"use unwrapped type %0 as key path root", (Type))
1080-
1080+
NOTE(optional_keypath_application_base, none,
1081+
"use '?' to access key path subscript only for non-'nil' base values", ())
1082+
10811083
ERROR(missing_unwrap_optional_try,none,
10821084
"value of optional type %0 not unwrapped; did you mean to use 'try!' "
10831085
"or chain with '?'?",

lib/Sema/CSDiagnostics.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6493,3 +6493,19 @@ void MissingRawValueFailure::fixIt(InFlightDiagnostic &diagnostic) const {
64936493

64946494
diagnostic.fixItInsertAfter(range.End, fix);
64956495
}
6496+
6497+
bool MissingOptionalUnwrapKeyPathFailure::diagnoseAsError() {
6498+
emitDiagnostic(diag::optional_not_unwrapped, getFromType(),
6499+
getFromType()->lookThroughSingleOptionalType());
6500+
6501+
emitDiagnostic(diag::optional_keypath_application_base)
6502+
.fixItInsertAfter(getLoc(), "?");
6503+
emitDiagnostic(diag::unwrap_with_force_value)
6504+
.fixItInsertAfter(getLoc(), "!");
6505+
return true;
6506+
}
6507+
6508+
SourceLoc MissingOptionalUnwrapKeyPathFailure::getLoc() const {
6509+
auto *SE = castToExpr<SubscriptExpr>(getAnchor());
6510+
return SE->getBase()->getEndLoc();
6511+
}

lib/Sema/CSDiagnostics.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,24 @@ class MissingRawValueFailure final : public AbstractRawRepresentableFailure {
21912191
void fixIt(InFlightDiagnostic &diagnostic) const override;
21922192
};
21932193

2194+
/// Diagnose a key path optional base that should be unwraped in order to
2195+
/// apply key path subscript.
2196+
///
2197+
/// \code
2198+
/// func f(_ bar: Bar? , keyPath: KeyPath<Bar, Int>) {
2199+
/// bar[keyPath: keyPath]
2200+
/// }
2201+
/// \endcode
2202+
class MissingOptionalUnwrapKeyPathFailure final : public ContextualFailure {
2203+
public:
2204+
MissingOptionalUnwrapKeyPathFailure(const Solution &solution, Type lhs,
2205+
Type rhs, ConstraintLocator *locator)
2206+
: ContextualFailure(solution, lhs, rhs, locator) {}
2207+
2208+
bool diagnoseAsError() override;
2209+
SourceLoc getLoc() const override;
2210+
};
2211+
21942212
} // end namespace constraints
21952213
} // end namespace swift
21962214

lib/Sema/CSFix.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,3 +1508,36 @@ bool SpecifyKeyPathRootType::diagnose(const Solution &solution,
15081508

15091509
return failure.diagnose(asNote);
15101510
}
1511+
1512+
bool UnwrapOptionalBaseKeyPathApplication::diagnose(const Solution &solution,
1513+
bool asNote) const {
1514+
MissingOptionalUnwrapKeyPathFailure failure(solution, getFromType(),
1515+
getToType(), getLocator());
1516+
return failure.diagnose(asNote);
1517+
}
1518+
1519+
UnwrapOptionalBaseKeyPathApplication *
1520+
UnwrapOptionalBaseKeyPathApplication::attempt(ConstraintSystem &cs, Type baseTy,
1521+
Type rootTy,
1522+
ConstraintLocator *locator) {
1523+
if(baseTy->hasTypeVariable() || rootTy->hasTypeVariable())
1524+
return nullptr;
1525+
1526+
if (!isExpr<SubscriptExpr>(locator->getAnchor()))
1527+
return nullptr;
1528+
1529+
// Only diagnose this if base is an optional type and we only have a
1530+
// single level of optionality so we can safely suggest unwrapping.
1531+
auto nonOptionalTy = baseTy->getOptionalObjectType();
1532+
if (!nonOptionalTy || nonOptionalTy->getOptionalObjectType())
1533+
return nullptr;
1534+
1535+
auto result =
1536+
cs.matchTypes(nonOptionalTy, rootTy, ConstraintKind::Subtype,
1537+
ConstraintSystem::TypeMatchFlags::TMF_ApplyingFix, locator);
1538+
if (result.isFailure())
1539+
return nullptr;
1540+
1541+
return new (cs.getAllocator())
1542+
UnwrapOptionalBaseKeyPathApplication(cs, baseTy, rootTy, locator);
1543+
}

lib/Sema/CSFix.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ enum class FixKind : uint8_t {
268268

269269
/// Specify key path root type when it cannot be infered from context.
270270
SpecifyKeyPathRootType,
271-
271+
272+
/// Unwrap optional base on key path application.
273+
UnwrapOptionalBaseKeyPathApplication,
272274
};
273275

274276
class ConstraintFix {
@@ -1918,6 +1920,32 @@ class SpecifyKeyPathRootType final : public ConstraintFix {
19181920
ConstraintLocator *locator);
19191921
};
19201922

1923+
/// Diagnose missing unwrap of optional base type on key path application.
1924+
///
1925+
/// \code
1926+
/// func f(_ bar: Bar? , keyPath: KeyPath<Bar, Int>) {
1927+
/// bar[keyPath: keyPath]
1928+
/// }
1929+
/// \endcode
1930+
class UnwrapOptionalBaseKeyPathApplication final : public ContextualMismatch {
1931+
protected:
1932+
UnwrapOptionalBaseKeyPathApplication(ConstraintSystem &cs, Type lhs, Type rhs,
1933+
ConstraintLocator *locator)
1934+
: ContextualMismatch(cs, FixKind::UnwrapOptionalBaseKeyPathApplication,
1935+
lhs, rhs, locator) {}
1936+
1937+
public:
1938+
std::string getName() const override {
1939+
return "force unwrap base on key path application";
1940+
}
1941+
1942+
bool diagnose(const Solution &solution, bool asNote = false) const override;
1943+
1944+
static UnwrapOptionalBaseKeyPathApplication *
1945+
attempt(ConstraintSystem &cs, Type baseTy, Type rootTy,
1946+
ConstraintLocator *locator);
1947+
};
1948+
19211949
} // end namespace constraints
19221950
} // end namespace swift
19231951

lib/Sema/CSSimplify.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3774,6 +3774,14 @@ bool ConstraintSystem::repairFailures(
37743774
}
37753775

37763776
case ConstraintLocator::KeyPathRoot: {
3777+
// The root mismatch is from base U? to U or a subtype of U in keypath
3778+
// application so let's suggest an unwrap the optional fix.
3779+
if (auto unwrapFix = UnwrapOptionalBaseKeyPathApplication::attempt(
3780+
*this, lhs, rhs, getConstraintLocator(locator))) {
3781+
conversionsOrFixes.push_back(unwrapFix);
3782+
break;
3783+
}
3784+
37773785
conversionsOrFixes.push_back(AllowKeyPathRootTypeMismatch::create(
37783786
*this, lhs, rhs, getConstraintLocator(locator)));
37793787

@@ -9742,6 +9750,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
97429750
case FixKind::CoerceToCheckedCast:
97439751
case FixKind::SpecifyObjectLiteralTypeImport:
97449752
case FixKind::AllowKeyPathRootTypeMismatch:
9753+
case FixKind::UnwrapOptionalBaseKeyPathApplication:
97459754
case FixKind::AllowCoercionToForceCast:
97469755
case FixKind::SpecifyKeyPathRootType: {
97479756
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;

localization/diagnostics/en.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4292,6 +4292,10 @@
42924292
msg: >-
42934293
use unwrapped type %0 as key path root
42944294
4295+
- id: optional_keypath_application_base
4296+
msg: >-
4297+
use '?' to access key path subscript only for non-'nil' base values
4298+
42954299
- id: missing_unwrap_optional_try
42964300
msg: >-
42974301
value of optional type %0 not unwrapped; did you mean to use 'try!' or

test/Constraints/keypath.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,30 @@ func fSR11184_O(_ c: SR11184!, _ kp: ReferenceWritableKeyPath<SR11184, String?>,
152152
c![keyPath: kp] = str // OK
153153
c?[keyPath: kp] = str // OK
154154
}
155+
156+
class KeyPathBase {}
157+
class KeyPathBaseSubtype: KeyPathBase {}
158+
class AnotherBase {}
159+
class AnotherComposeBase {
160+
var member: KeyPathBase?
161+
}
162+
163+
func key_path_root_mismatch<T>(_ base: KeyPathBase?, subBase: KeyPathBaseSubtype?, _ abase: AnotherComposeBase,
164+
_ kp: KeyPath<KeyPathBase, T>, _ kpa: KeyPath<AnotherBase, T>) {
165+
let _ : T = base[keyPath: kp] // expected-error {{value of optional type 'KeyPathBase?' must be unwrapped to a value of type 'KeyPathBase'}}
166+
// expected-note@-1 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{19-19=!}}
167+
// expected-note@-2 {{use '?' to access key path subscript only for non-'nil' base values}} {{19-19=?}}
168+
let _ : T = base[keyPath: kpa] // expected-error {{key path with root type 'AnotherBase' cannot be applied to a base of type 'KeyPathBase?'}}
169+
170+
// Chained root mismatch
171+
let _ : T = abase.member[keyPath: kp] // expected-error {{value of optional type 'KeyPathBase?' must be unwrapped to a value of type 'KeyPathBase'}}
172+
// expected-note@-1 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{27-27=!}}
173+
// expected-note@-2 {{use '?' to access key path subscript only for non-'nil' base values}} {{27-27=?}}
174+
let _ : T = abase.member[keyPath: kpa] // expected-error {{key path with root type 'AnotherBase' cannot be applied to a base of type 'KeyPathBase?'}}
175+
176+
let _ : T = subBase[keyPath: kp] // expected-error {{value of optional type 'KeyPathBaseSubtype?' must be unwrapped to a value of type 'KeyPathBaseSubtype'}}
177+
// expected-note@-1 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{22-22=!}}
178+
// expected-note@-2 {{use '?' to access key path subscript only for non-'nil' base values}} {{22-22=?}}
179+
let _ : T = subBase[keyPath: kpa] // expected-error {{key path with root type 'AnotherBase' cannot be applied to a base of type 'KeyPathBaseSubtype?'}}
180+
181+
}

test/expr/unary/keypath/keypath.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,20 +452,24 @@ func testKeyPathSubscriptExistentialBase(concreteBase: inout B,
452452
_ = concreteBase[keyPath: rkp]
453453
_ = concreteBase[keyPath: pkp]
454454

455-
concreteBase[keyPath: kp] = s // expected-error{{}}
456-
concreteBase[keyPath: wkp] = s // expected-error{{}}
455+
concreteBase[keyPath: kp] = s // expected-error {{cannot assign through subscript: 'kp' is a read-only key path}}
456+
concreteBase[keyPath: wkp] = s // expected-error {{key path with root type 'P' cannot be applied to a base of type 'B'}}
457457
concreteBase[keyPath: rkp] = s
458-
concreteBase[keyPath: pkp] = s // expected-error{{}}
458+
// TODO(diagnostics): Improve this diagnostic message because concreteBase is mutable, the problem is related to assign
459+
// through PartialKeyPath.
460+
concreteBase[keyPath: pkp] = s // expected-error {{cannot assign through subscript: 'concreteBase' is immutable}}
459461

460462
_ = existentialBase[keyPath: kp]
461463
_ = existentialBase[keyPath: wkp]
462464
_ = existentialBase[keyPath: rkp]
463465
_ = existentialBase[keyPath: pkp]
464466

465-
existentialBase[keyPath: kp] = s // expected-error{{}}
467+
existentialBase[keyPath: kp] = s // expected-error {{cannot assign through subscript: 'kp' is a read-only key path}}
466468
existentialBase[keyPath: wkp] = s
467469
existentialBase[keyPath: rkp] = s
468-
existentialBase[keyPath: pkp] = s // expected-error{{}}
470+
// TODO(diagnostics): Improve this diagnostic message because existentialBase is mutable, the problem is related to assign
471+
// through PartialKeyPath.
472+
existentialBase[keyPath: pkp] = s // expected-error {{cannot assign through subscript: 'existentialBase' is immutable}}
469473
}
470474

471475
struct AA {

0 commit comments

Comments
 (0)