Skip to content

Commit fea720f

Browse files
committed
[MiscDiagnostics] Emit a deprecation warning for some writes through literal keypaths.
We incorrectly allowed some keypaths to be inferred as writable keypaths in Swift 3/4 modes. This no longer happens when -swift-version 5 is specified. This warning is a limited attempt at providing some advanced notice of code that will break, only in the cases where the keypath is a direct argument to a keypath subscript write. Fixes: rdar://problem/40068274
1 parent 7b0b15e commit fea720f

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -495,6 +495,9 @@ ERROR(expr_keypath_subscript_index_not_hashable, none,
495495
ERROR(expr_smart_keypath_application_type_mismatch,none,
496496
"key path of type %0 cannot be applied to a base of type %1",
497497
(Type, Type))
498+
WARNING(expr_deprecated_writable_keypath,none,
499+
"forming a writable keypath to property %0 that is read-only in this context "
500+
"is deprecated and will be removed in a future release",(DeclName))
498501

499502
// Selector expressions.
500503
ERROR(expr_selector_no_objc_runtime,none,

lib/Sema/MiscDiagnostics.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3927,6 +3927,62 @@ static void diagnoseUnintendedOptionalBehavior(TypeChecker &TC, const Expr *E,
39273927
const_cast<Expr *>(E)->walk(Walker);
39283928
}
39293929

3930+
static void diagnoseDeprecatedWritableKeyPath(TypeChecker &TC, const Expr *E,
3931+
const DeclContext *DC) {
3932+
if (!E || isa<ErrorExpr>(E) || !E->getType())
3933+
return;
3934+
3935+
class DeprecatedWritableKeyPathWalker : public ASTWalker {
3936+
TypeChecker &TC;
3937+
const DeclContext *DC;
3938+
3939+
void visitKeyPathApplicationExpr(KeyPathApplicationExpr *E) {
3940+
if (E->hasLValueAccessKind() &&
3941+
E->getLValueAccessKind() == AccessKind::Read)
3942+
return;
3943+
3944+
if (auto *keyPathExpr = dyn_cast<KeyPathExpr>(E->getKeyPath())) {
3945+
auto *decl = keyPathExpr->getType()->getNominalOrBoundGenericNominal();
3946+
if (decl != TC.Context.getWritableKeyPathDecl() &&
3947+
decl != TC.Context.getReferenceWritableKeyPathDecl())
3948+
return;
3949+
3950+
assert(keyPathExpr->getComponents().size() > 0);
3951+
auto &component = keyPathExpr->getComponents().back();
3952+
if (component.getKind() == KeyPathExpr::Component::Kind::Property) {
3953+
auto *storage =
3954+
cast<AbstractStorageDecl>(component.getDeclRef().getDecl());
3955+
if (!storage->isSettable(nullptr) ||
3956+
!storage->isSetterAccessibleFrom(DC)) {
3957+
TC.diagnose(keyPathExpr->getLoc(),
3958+
swift::diag::expr_deprecated_writable_keypath,
3959+
storage->getFullName());
3960+
}
3961+
}
3962+
}
3963+
}
3964+
3965+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
3966+
if (!E || isa<ErrorExpr>(E) || !E->getType())
3967+
return {false, E};
3968+
3969+
if (auto *KPAE = dyn_cast<KeyPathApplicationExpr>(E)) {
3970+
visitKeyPathApplicationExpr(KPAE);
3971+
return {true, E};
3972+
}
3973+
3974+
return {true, E};
3975+
}
3976+
3977+
public:
3978+
DeprecatedWritableKeyPathWalker(TypeChecker &TC, const DeclContext *DC)
3979+
: TC(TC), DC(DC) {}
3980+
};
3981+
3982+
DeprecatedWritableKeyPathWalker Walker(TC, DC);
3983+
const_cast<Expr *>(E)->walk(Walker);
3984+
}
3985+
39303986
//===----------------------------------------------------------------------===//
39313987
// High-level entry points.
39323988
//===----------------------------------------------------------------------===//
@@ -3940,6 +3996,8 @@ void swift::performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
39403996
diagRecursivePropertyAccess(TC, E, DC);
39413997
diagnoseImplicitSelfUseInClosure(TC, E, DC);
39423998
diagnoseUnintendedOptionalBehavior(TC, E, DC);
3999+
if (!TC.Context.isSwiftVersionAtLeast(5))
4000+
diagnoseDeprecatedWritableKeyPath(TC, E, DC);
39434001
if (!TC.getLangOpts().DisableAvailabilityChecking)
39444002
diagAvailability(TC, E, const_cast<DeclContext*>(DC));
39454003
if (TC.Context.LangOpts.EnableObjCInterop)

test/Constraints/keypath.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ struct S {
55

66
init() {
77
let _: WritableKeyPath<S, Int> = \.i // no error for Swift 3/4
8+
9+
S()[keyPath: \.i] = 1
10+
// expected-error@-1 {{cannot assign to immutable expression}}
811
}
912
}
1013

1114
func test() {
1215
let _: WritableKeyPath<C, Int> = \.i // no error for Swift 3/4
16+
17+
C()[keyPath: \.i] = 1 // warning on write with literal keypath
18+
// expected-warning@-1 {{forming a writable keypath to property}}
19+
20+
let _ = C()[keyPath: \.i] // no warning for a read
1321
}

0 commit comments

Comments
 (0)