Skip to content

[Observation] Add type annotation on cached keypaths #78842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions lib/Macros/Sources/ObservationMacros/ObservableMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,18 @@ extension ObservationTrackedMacro: PeerMacro {

let storage = DeclSyntax(property.privatePrefixed("_", addingAttribute: ObservableMacro.ignoredAttribute))
if ObservableMacro.canCacheKeyPaths(context.lexicalContext) {
let cachedKeypath: DeclSyntax =
"""
private static let _cachedKeypath_\(identifier) = \\\(container.name).\(identifier)
"""
let cachedKeypath: DeclSyntax
if let type = property.type {
cachedKeypath =
"""
private static let _cachedKeypath_\(identifier): ReferenceWritableKeyPath<\(container.name), \(type)> = \\\(container.name).\(identifier)
"""
} else {
cachedKeypath =
"""
private static let _cachedKeypath_\(identifier) = \\\(container.name).\(identifier)
"""
}
return [storage, cachedKeypath]
} else {
return [storage]
Expand Down
34 changes: 34 additions & 0 deletions test/stdlib/Observation/Observable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,40 @@ class GenericClassParent<T> {
}
}

protocol MemberProtocol {}
struct MemberType: MemberProtocol {}

@Observable
class HasUniversalMember {
var member: any MemberProtocol = MemberType()
}

@Observable
class HasExistentialMember {
var member: some MemberProtocol = MemberType()
}

protocol HasAssociatedType {
associatedtype Associated
static var associatedMember: Associated { get }
}

@Observable
class HasHiddenExistential: HasAssociatedType {
var member: Associated = HasHiddenExistential.associatedMember
static var associatedMember: some MemberProtocol { MemberType() }
}

/*
// FIXME: This case is not handled yet. Properties that have no type annotation
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for reviewers: I don't know how best to handle this case.

The simplest thing to do is just disable the optimization when there's no explicit type, but that's an annoying performance hazard when it's idiomatic to use an inferred type for most default-initialized properties.

It's very simple to fix the rare case where this fails (just add a type annotation) but that's still leaving in a source break.

// that are inferred as containing a `some` type fail to compile.
@Observable
class HasMoreHiddenExistential: HasAssociatedType {
var member = HasMoreHiddenExistential.associatedMember
static var associatedMember: some MemberProtocol { MemberType() }
}
*/

@Observable
class RecursiveOuter {
var inner = RecursiveInner()
Expand Down