Sema: Infer key path immutability during expression availability checking.

Accessor availability diagnostics for key path expressions were first
introduced by https://github.com/swiftlang/swift/pull/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
https://github.com/swiftlang/swift/pull/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.
This commit is contained in:
Allan Shortlidge
2025-09-23 18:41:37 -07:00
parent 2467b931a7
commit ee988c0da8
2 changed files with 82 additions and 29 deletions

View File

@@ -56,7 +56,7 @@ struct BaseStruct<T> {
var unavailableGetter: T {
@available(*, unavailable)
get { fatalError() } // expected-note 69 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
get { fatalError() } // expected-note 73 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
set { }
}
@@ -68,7 +68,7 @@ struct BaseStruct<T> {
var unavailableGetterAndSetter: T {
@available(*, unavailable)
get { fatalError() } // expected-note 68 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
get { fatalError() } // expected-note 71 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
@available(*, unavailable)
set { fatalError() } // expected-note 33 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
}
@@ -116,11 +116,6 @@ struct SubscriptHelper {
return t
}
func takesKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U>) -> () { }
func takesWritableKeyPath<T, U>(_ t: inout T, _ keyPath: WritableKeyPath<T, U>) -> () {
// expected-note@-1 2 {{in call to function 'takesWritableKeyPath'}}
}
func testDiscardedRValueLoads_Struct() {
var x = BaseStruct<StructValue>() // expected-warning {{variable 'x' was never mutated; consider changing to 'let' constant}}
@@ -548,6 +543,8 @@ func testDiscardedApplyOfFuncWithInOutParam_Class() {
func testKeyPathArguments_Struct() {
var x = BaseStruct<StructValue>()
func takesKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U>) -> () { }
takesKeyPath(x, \.available)
takesKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
takesKeyPath(x, \.unavailableSetter)
@@ -555,6 +552,35 @@ func testKeyPathArguments_Struct() {
takesKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
takesKeyPath(x, \.deprecatedSetter)
func takesAnyKeyPath(_ keyPath: AnyKeyPath) -> () { }
takesAnyKeyPath(\BaseStruct<StructValue>.available)
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableSetter)
takesAnyKeyPath(\BaseStruct<StructValue>.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
takesAnyKeyPath(\BaseStruct<StructValue>.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
takesAnyKeyPath(\BaseStruct<StructValue>.deprecatedSetter)
func takesPartialKeyPath<T>(_ t: T, _ keyPath: PartialKeyPath<T>) -> () { }
takesPartialKeyPath(x, \.available)
takesPartialKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
takesPartialKeyPath(x, \.unavailableSetter)
takesPartialKeyPath(x, \.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
takesPartialKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
takesPartialKeyPath(x, \.deprecatedSetter)
func takesSendableKeyPath<T, U>(_ t: T, _ keyPath: KeyPath<T, U> & Sendable) -> () { }
takesSendableKeyPath(x, \.available)
takesSendableKeyPath(x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
takesSendableKeyPath(x, \.unavailableSetter)
takesSendableKeyPath(x, \.unavailableGetterAndSetter) // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
takesSendableKeyPath(x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
takesSendableKeyPath(x, \.deprecatedSetter)
func takesWritableKeyPath<T, U>(_ t: inout T, _ keyPath: WritableKeyPath<T, U>) -> () { }
// expected-note@-1 2 {{in call to function 'takesWritableKeyPath'}}
takesWritableKeyPath(&x, \.available)
takesWritableKeyPath(&x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
// FIXME: Ideally we would diagnose the unavailability of the setter instead
@@ -566,6 +592,14 @@ func testKeyPathArguments_Struct() {
// expected-error@-1 {{generic parameter 'U' could not be inferred}}
takesWritableKeyPath(&x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
takesWritableKeyPath(&x, \.deprecatedSetter) // expected-warning {{setter for 'deprecatedSetter' is deprecated: writing not recommended}}
func takesUnifiedTypes<T>(_: T, _: T) { }
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.available)
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.unavailableSetter)
takesUnifiedTypes(\BaseStruct<StructValue>.available, \BaseStruct<StructValue>.deprecatedSetter) // expected-warning {{setter for 'deprecatedSetter' is deprecated: writing not recommended}}
takesUnifiedTypes(\BaseStruct<StructValue>.unavailableSetter, \BaseStruct<StructValue>.deprecatedSetter)
}
var global = BaseStruct<StructValue>()