Skip to content

Commit dd810a7

Browse files
committed
[Diagnostics] Improve mutability diagnostics related to dynamic member lookup
Since there is a way to mutate through use of writable keypath diagnostics have to be adjusted to point to the members found via keypath member lookup instead to using catch-all "immutable base" diagnostic.
1 parent d602cdf commit dd810a7

File tree

4 files changed

+63
-15
lines changed

4 files changed

+63
-15
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ bool MissingOptionalUnwrapFailure::diagnoseAsError() {
776776
bool RValueTreatedAsLValueFailure::diagnoseAsError() {
777777
Diag<StringRef> subElementDiagID;
778778
Diag<Type> rvalueDiagID = diag::assignment_lhs_not_lvalue;
779-
Expr *diagExpr = getLocator()->getAnchor();
779+
Expr *diagExpr = getRawAnchor();
780780
SourceLoc loc = diagExpr->getLoc();
781781

782782
if (auto assignExpr = dyn_cast<AssignExpr>(diagExpr)) {
@@ -854,12 +854,18 @@ bool RValueTreatedAsLValueFailure::diagnoseAsError() {
854854
}
855855
}
856856

857-
if (auto resolvedOverload = getResolvedOverload(getLocator()))
857+
if (auto resolvedOverload = getResolvedOverload(getLocator())) {
858858
if (resolvedOverload->Choice.getKind() ==
859-
OverloadChoiceKind::DynamicMemberLookup ||
860-
resolvedOverload->Choice.getKind() ==
861-
OverloadChoiceKind::KeyPathDynamicMemberLookup)
859+
OverloadChoiceKind::DynamicMemberLookup)
862860
subElementDiagID = diag::assignment_dynamic_property_has_immutable_base;
861+
862+
if (resolvedOverload->Choice.getKind() ==
863+
OverloadChoiceKind::KeyPathDynamicMemberLookup) {
864+
if (!getType(member->getBase())->hasLValueType())
865+
subElementDiagID =
866+
diag::assignment_dynamic_property_has_immutable_base;
867+
}
868+
}
863869
} else if (auto sub = dyn_cast<SubscriptExpr>(diagExpr)) {
864870
subElementDiagID = diag::assignment_subscript_has_immutable_base;
865871
} else {
@@ -1204,7 +1210,7 @@ AssignmentFailure::resolveImmutableBase(Expr *expr) const {
12041210
if (!member) {
12051211
auto loc =
12061212
cs.getConstraintLocator(SE, ConstraintLocator::SubscriptMember);
1207-
member = dyn_cast_or_null<SubscriptDecl>(cs.findResolvedMemberRef(loc));
1213+
member = dyn_cast_or_null<SubscriptDecl>(getMemberRef(loc));
12081214
}
12091215

12101216
// If it isn't settable, return it.
@@ -1233,9 +1239,10 @@ AssignmentFailure::resolveImmutableBase(Expr *expr) const {
12331239
// If we found a decl for the UDE, check it.
12341240
auto loc = cs.getConstraintLocator(UDE, ConstraintLocator::Member);
12351241

1242+
auto *member = getMemberRef(loc);
12361243
// If we can resolve a member, we can determine whether it is settable in
12371244
// this context.
1238-
if (auto *member = cs.findResolvedMemberRef(loc)) {
1245+
if (member) {
12391246
auto *memberVD = dyn_cast<VarDecl>(member);
12401247

12411248
// If the member isn't a vardecl (e.g. its a funcdecl), or it isn't
@@ -1283,6 +1290,43 @@ AssignmentFailure::resolveImmutableBase(Expr *expr) const {
12831290
return {expr, nullptr};
12841291
}
12851292

1293+
ValueDecl *AssignmentFailure::getMemberRef(ConstraintLocator *locator) const {
1294+
auto member = getOverloadChoiceIfAvailable(locator);
1295+
if (!member || !member->choice.isDecl())
1296+
return nullptr;
1297+
1298+
auto *DC = getDC();
1299+
auto &TC = getTypeChecker();
1300+
1301+
auto *decl = member->choice.getDecl();
1302+
if (isa<SubscriptDecl>(decl) &&
1303+
isValidDynamicMemberLookupSubscript(cast<SubscriptDecl>(decl), DC, TC)) {
1304+
auto *subscript = cast<SubscriptDecl>(decl);
1305+
// If this is a keypath dynamic member lookup, we have to
1306+
// adjust the locator to find member referred by it.
1307+
if (isValidKeyPathDynamicMemberLookup(subscript, TC)) {
1308+
auto &cs = getConstraintSystem();
1309+
// Type has a following format:
1310+
// `(Self) -> (dynamicMember: {Writable}KeyPath<T, U>) -> U`
1311+
auto *fullType = member->openedFullType->castTo<FunctionType>();
1312+
auto *fnType = fullType->getResult()->castTo<FunctionType>();
1313+
1314+
auto paramTy = fnType->getParams()[0].getPlainType();
1315+
auto keyPath = paramTy->getAnyNominal();
1316+
auto memberLoc = cs.getConstraintLocator(
1317+
locator, LocatorPathElt::getKeyPathDynamicMember(keyPath));
1318+
1319+
auto memberRef = getOverloadChoiceIfAvailable(memberLoc);
1320+
return memberRef ? memberRef->choice.getDecl() : nullptr;
1321+
}
1322+
1323+
// If this is a string based dynamic lookup, there is no member declaration.
1324+
return nullptr;
1325+
}
1326+
1327+
return decl;
1328+
}
1329+
12861330
Diag<StringRef> AssignmentFailure::findDeclDiagonstic(ASTContext &ctx,
12871331
Expr *destExpr) {
12881332
if (isa<ApplyExpr>(destExpr) || isa<SelfApplyExpr>(destExpr))

lib/Sema/CSDiagnostics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,10 @@ class AssignmentFailure final : public FailureDiagnostic {
647647
isLoadedLValue(ifExpr->getElseExpr());
648648
return false;
649649
}
650+
651+
/// Retrive an member reference associated with given member
652+
/// looking through dynamic member lookup on the way.
653+
ValueDecl *getMemberRef(ConstraintLocator *locator) const;
650654
};
651655

652656
/// Intended to diagnose any possible contextual failure

test/attr/attr_dynamic_member_lookup.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ _ = \KP.testLookup
402402

403403
struct Point {
404404
var x: Int
405-
let y: Int
405+
let y: Int // expected-note 2 {{change 'let' to 'var' to make it mutable}}
406406

407407
private let z: Int = 0 // expected-note 7 {{declared here}}
408408
}
@@ -447,7 +447,7 @@ _ = lens.bottomRight.z // expected-error {{'z' is inaccessible due to 'private'
447447

448448
lens.topLeft = Lens(Point(x: 1, y: 2)) // Ok
449449
lens.bottomRight.x = Lens(11) // Ok
450-
lens.bottomRight.y = Lens(12) // expected-error {{cannot assign through dynamic lookup property: 'lens' is immutable}}
450+
lens.bottomRight.y = Lens(12) // expected-error {{cannot assign to property: 'y' is a 'let' constant}}
451451
lens.bottomRight.z = Lens(13) // expected-error {{'z' is inaccessible due to 'private' protection level}}
452452

453453
func acceptKeyPathDynamicLookup(_: Lens<Int>) {}
@@ -576,7 +576,7 @@ func keypath_with_subscripts(_ arr: SubscriptLens<[Int]>,
576576
_ = arr["hello"] // Ok
577577
_ = dict["hello"] // Ok
578578

579-
_ = arr["hello"] = 42 // expected-error {{cannot assign through subscript: subscript is get-only}}
579+
_ = arr["hello"] = 42 // expected-error {{cannot assign through subscript: 'arr' is a 'let' constant}}
580580
_ = dict["hello"] = 0 // Ok
581581

582582
_ = arr[0] = 42 // expected-error {{cannot assign through subscript: 'arr' is a 'let' constant}}
@@ -620,6 +620,6 @@ func keypath_to_subscript_to_property(_ lens: inout Lens<Array<Rectangle>>) {
620620
_ = lens[0].topLeft.x
621621
_ = lens[0].topLeft.y
622622
_ = lens[0].topLeft.x = Lens(0)
623-
_ = lens[0].topLeft.y = Lens(1) // FIXME(diagnostics): Diagnostic should point to 'y' instead of 'lens'
624-
// expected-error@-1 {{cannot assign through dynamic lookup property: 'lens' is immutable}}
623+
_ = lens[0].topLeft.y = Lens(1)
624+
// expected-error@-1 {{cannot assign to property: 'y' is a 'let' constant}}
625625
}

test/expr/unary/keypath/keypath.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,9 @@ func testKeyPathSubscript(readonly: ZwithSubscript, writable: inout ZwithSubscri
350350
sink = writable[keyPath: wkp]
351351
sink = readonly[keyPath: rkp]
352352
sink = writable[keyPath: rkp]
353-
readonly[keyPath: kp] = sink // expected-error{{cannot assign through subscript: subscript is get-only}}
354-
writable[keyPath: kp] = sink // expected-error{{cannot assign through subscript: subscript is get-only}}
355-
readonly[keyPath: wkp] = sink // expected-error{{cannot assign through subscript: subscript is get-only}}
353+
readonly[keyPath: kp] = sink // expected-error {{cannot assign through subscript: 'kp' is a read-only key path}}
354+
writable[keyPath: kp] = sink // expected-error {{cannot assign through subscript: 'kp' is a read-only key path}}
355+
readonly[keyPath: wkp] = sink // expected-error {{cannot assign through subscript: 'readonly' is a 'let' constant}}
356356
// FIXME: silently falls back to keypath application, which seems inconsistent
357357
writable[keyPath: wkp] = sink
358358
// FIXME: silently falls back to keypath application, which seems inconsistent

0 commit comments

Comments
 (0)