Skip to content

Commit ee988c0

Browse files
committed
Sema: Infer key path immutability during expression availability checking.
Accessor availability diagnostics for key path expressions were first introduced by #83931. Those changes were insufficient because sometimes key path expressions are generated in the AST with more mutability than is needed by the context and so setter availability could be diagnosed inappropriately. Key path expression binding was refined in #84491 to make this less likely to occur. However, there are still some circumstances in which a mutable key path is generated in the AST and then immediately coerced into an immutable key path to satisfy the contextual type. This change infers the immutability of these key path expressions by looking through surrounding conversion expressions.
1 parent 2467b93 commit ee988c0

File tree

2 files changed

+82
-29
lines changed

2 files changed

+82
-29
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/AST/ClangModuleLoader.h"
3030
#include "swift/AST/DeclExportabilityVisitor.h"
3131
#include "swift/AST/DiagnosticsParse.h"
32+
#include "swift/AST/ExistentialLayout.h"
3233
#include "swift/AST/GenericEnvironment.h"
3334
#include "swift/AST/Initializer.h"
3435
#include "swift/AST/NameLookup.h"
@@ -2232,9 +2233,6 @@ class ExprAvailabilityWalker : public BaseDiagnosticWalker {
22322233
/// The context does not involve a key path access.
22332234
None,
22342235

2235-
/// The context is an expression that is coerced to a read-only key path.
2236-
ReadOnlyCoercion,
2237-
22382236
/// The context is a key path application (`x[keyPath: \.member]`).
22392237
Application,
22402238
};
@@ -2361,15 +2359,6 @@ class ExprAvailabilityWalker : public BaseDiagnosticWalker {
23612359
FCE->getLoc(),
23622360
Where.getDeclContext());
23632361
}
2364-
if (auto DTBE = dyn_cast<DerivedToBaseExpr>(E)) {
2365-
if (auto ty = DTBE->getType()) {
2366-
if (ty->isKeyPath()) {
2367-
walkInKeyPathAccessContext(DTBE->getSubExpr(),
2368-
KeyPathAccessContext::ReadOnlyCoercion);
2369-
return Action::SkipChildren(E);
2370-
}
2371-
}
2372-
}
23732362
if (auto KP = dyn_cast<KeyPathExpr>(E)) {
23742363
maybeDiagKeyPath(KP);
23752364
}
@@ -2586,19 +2575,49 @@ class ExprAvailabilityWalker : public BaseDiagnosticWalker {
25862575

25872576
StorageAccessKind getStorageAccessKindForKeyPath(KeyPathExpr *KP) const {
25882577
switch (KeyPathAccess) {
2589-
case KeyPathAccessContext::None:
2590-
// Use the key path's type to determine the access kind.
2591-
if (!KP->isObjC())
2592-
if (auto keyPathType = KP->getKeyPathType())
2593-
if (keyPathType->isKeyPath())
2578+
case KeyPathAccessContext::None: {
2579+
// Obj-C key paths are always considered mutable.
2580+
if (KP->isObjC())
2581+
return StorageAccessKind::GetSet;
2582+
2583+
// Check whether key path type indicates immutability, in which case only
2584+
// the getter will be used.
2585+
if (auto keyPathType = KP->getKeyPathType())
2586+
if (keyPathType->isKnownImmutableKeyPathType())
2587+
return StorageAccessKind::Get;
2588+
2589+
// Check if there's a conversion expression containing this one directly
2590+
// above it in the stack. If so, and that conversion is to an immutable
2591+
// key path type, then we should infer that use of the key path only
2592+
// implies use of the getter.
2593+
ArrayRef<const Expr *> exprs = ExprStack;
2594+
// Must be called while visiting the given key path expression.
2595+
ASSERT(exprs.back() == KP);
2596+
2597+
for (auto *expr : llvm::reverse(exprs.drop_back())) {
2598+
// Only traverse through conversions on the stack.
2599+
if (!isa<ImplicitConversionExpr>(expr) &&
2600+
!isa<OpenExistentialExpr>(expr))
2601+
break;
2602+
2603+
if (auto ty = expr->getType()) {
2604+
if (ty->isKnownImmutableKeyPathType())
25942605
return StorageAccessKind::Get;
25952606

2596-
return StorageAccessKind::GetSet;
2607+
if (auto existential = dyn_cast<ExistentialType>(ty)) {
2608+
if (auto superclass =
2609+
existential->getExistentialLayout().getSuperclass()) {
2610+
if (superclass->isKnownImmutableKeyPathType())
2611+
return StorageAccessKind::Get;
2612+
}
2613+
}
2614+
}
2615+
}
25972616

2598-
case KeyPathAccessContext::ReadOnlyCoercion:
2599-
// The type of this key path is being coerced to the type KeyPath<_, _> so
2600-
// ignore the actual key path type.
2601-
return StorageAccessKind::Get;
2617+
// We failed to prove that the key path is only used immutably,
2618+
// so diagnose both get and set access.
2619+
return StorageAccessKind::GetSet;
2620+
}
26022621

26032622
case KeyPathAccessContext::Application:
26042623
// The key path is being applied directly to a base so treat it as if it

test/Availability/availability_accessors.swift

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct BaseStruct<T> {
5656

5757
var unavailableGetter: T {
5858
@available(*, unavailable)
59-
get { fatalError() } // expected-note 69 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
59+
get { fatalError() } // expected-note 73 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
6060
set { }
6161
}
6262

@@ -68,7 +68,7 @@ struct BaseStruct<T> {
6868

6969
var unavailableGetterAndSetter: T {
7070
@available(*, unavailable)
71-
get { fatalError() } // expected-note 68 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
71+
get { fatalError() } // expected-note 71 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7272
@available(*, unavailable)
7373
set { fatalError() } // expected-note 33 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7474
}
@@ -116,11 +116,6 @@ struct SubscriptHelper {
116116
return t
117117
}
118118

119-
func takesKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U>) -> () { }
120-
func takesWritableKeyPath<T, U>(_ t: inout T, _ keyPath: WritableKeyPath<T, U>) -> () {
121-
// expected-note@-1 2 {{in call to function 'takesWritableKeyPath'}}
122-
}
123-
124119
func testDiscardedRValueLoads_Struct() {
125120
var x = BaseStruct<StructValue>() // expected-warning {{variable 'x' was never mutated; consider changing to 'let' constant}}
126121

@@ -548,13 +543,44 @@ func testDiscardedApplyOfFuncWithInOutParam_Class() {
548543
func testKeyPathArguments_Struct() {
549544
var x = BaseStruct<StructValue>()
550545

546+
func takesKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U>) -> () { }
547+
551548
takesKeyPath(x, \.available)
552549
takesKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
553550
takesKeyPath(x, \.unavailableSetter)
554551
takesKeyPath(x, \.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
555552
takesKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
556553
takesKeyPath(x, \.deprecatedSetter)
557554

555+
func takesAnyKeyPath(_ keyPath: AnyKeyPath) -> () { }
556+
557+
takesAnyKeyPath(\BaseStruct<StructValue>.available)
558+
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
559+
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableSetter)
560+
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
561+
takesAnyKeyPath(\BaseStruct<StructValue>.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
562+
takesAnyKeyPath(\BaseStruct<StructValue>.deprecatedSetter)
563+
564+
func takesPartialKeyPath<T>(_ t: T, _ keyPath: PartialKeyPath<T>) -> () { }
565+
takesPartialKeyPath(x, \.available)
566+
takesPartialKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
567+
takesPartialKeyPath(x, \.unavailableSetter)
568+
takesPartialKeyPath(x, \.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
569+
takesPartialKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
570+
takesPartialKeyPath(x, \.deprecatedSetter)
571+
572+
func takesSendableKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U> & Sendable) -> () { }
573+
574+
takesSendableKeyPath(x, \.available)
575+
takesSendableKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
576+
takesSendableKeyPath(x, \.unavailableSetter)
577+
takesSendableKeyPath(x, \.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
578+
takesSendableKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
579+
takesSendableKeyPath(x, \.deprecatedSetter)
580+
581+
func takesWritableKeyPath<T, U>(_ t: inout T, _ keyPath: WritableKeyPath<T, U>) -> () { }
582+
// expected-note@-1 2 {{in call to function 'takesWritableKeyPath'}}
583+
558584
takesWritableKeyPath(&x, \.available)
559585
takesWritableKeyPath(&x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
560586
// FIXME: Ideally we would diagnose the unavailability of the setter instead
@@ -566,6 +592,14 @@ func testKeyPathArguments_Struct() {
566592
// expected-error@-1 {{generic parameter 'U' could not be inferred}}
567593
takesWritableKeyPath(&x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
568594
takesWritableKeyPath(&x, \.deprecatedSetter) // expected-warning {{setter for 'deprecatedSetter' is deprecated: writing not recommended}}
595+
596+
func takesUnifiedTypes<T>(_: T, _: T) { }
597+
598+
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.available)
599+
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
600+
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.unavailableSetter)
601+
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.deprecatedSetter) // expected-warning {{setter for 'deprecatedSetter' is deprecated: writing not recommended}}
602+
takesUnifiedTypes(\BaseStruct<StructValue>.unavailableSetter, \BaseStruct<StructValue>.deprecatedSetter)
569603
}
570604

571605
var global = BaseStruct<StructValue>()

0 commit comments

Comments
 (0)