From f373ad937c20ba2df6cd3a141bfe885def0b0c61 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Thu, 1 Jun 2023 15:03:27 -0700 Subject: [PATCH 1/5] [Observation] Transition to peer macros instead of arbitrary members --- .../Sources/ObservationMacros/ObservableMacro.swift | 10 ++++------ .../Observation/Sources/Observation/Observable.swift | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift index be1bf85a327e0..aa1ae0280f27e 100644 --- a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift +++ b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift @@ -202,16 +202,13 @@ extension ObservableMacro: MemberMacro { declaration.addIfNeeded(ObservableMacro.registrarVariable(observableType), to: &declarations) declaration.addIfNeeded(ObservableMacro.accessFunction(observableType), to: &declarations) declaration.addIfNeeded(ObservableMacro.withMutationFunction(observableType), to: &declarations) - + let storedInstanceVariables = declaration.definedVariables.filter { $0.isValidForObservation } for property in storedInstanceVariables { if property.hasMacroApplication(ObservableMacro.ignoredMacroName) { continue } if property.initializer == nil { context.addDiagnostics(from: DiagnosticsError(syntax: property, message: "@Observable requires property '\(property.identifier?.text ?? "")' to have an initial value", id: .missingInitializer), node: property) } - let storage = DeclSyntax(property.privatePrefixed("_", addingAttribute: ObservableMacro.ignoredAttribute)) - declaration.addIfNeeded(storage, to: &declarations) - } return declarations @@ -327,8 +324,9 @@ extension ObservationTrackedMacro: PeerMacro { property.isValidForObservation else { return [] } - - if property.hasMacroApplication(ObservableMacro.ignoredMacroName) { + + if property.hasMacroApplication(ObservableMacro.ignoredMacroName) || + property.hasMacroApplication(ObservableMacro.trackedMacroName) { return [] } diff --git a/stdlib/public/Observation/Sources/Observation/Observable.swift b/stdlib/public/Observation/Sources/Observation/Observable.swift index 19a4012f93a1d..bfc4918a4d08d 100644 --- a/stdlib/public/Observation/Sources/Observation/Observable.swift +++ b/stdlib/public/Observation/Sources/Observation/Observable.swift @@ -16,7 +16,7 @@ #if $Macros && hasAttribute(attached) @available(SwiftStdlib 5.9, *) -@attached(member, names: named(_$observationRegistrar), named(access), named(withMutation), arbitrary) +@attached(member, names: named(_$observationRegistrar), named(access), named(withMutation)) @attached(memberAttribute) @attached(conformance) public macro Observable() = @@ -24,7 +24,7 @@ public macro Observable() = @available(SwiftStdlib 5.9, *) @attached(accessor) -// @attached(peer, names: prefixed(_)) +@attached(peer, names: prefixed(_)) public macro ObservationTracked() = #externalMacro(module: "ObservationMacros", type: "ObservationTrackedMacro") From a90e5d1417b63662d71d575271a0319c8a73515d Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Wed, 7 Jun 2023 15:12:31 -0700 Subject: [PATCH 2/5] [Observation] Lift the initializer requirement by utilizing init accessors for fully formed definite initialization --- .../ObservationMacros/ObservableMacro.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift index aa1ae0280f27e..33820450a8a2d 100644 --- a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift +++ b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift @@ -203,14 +203,6 @@ extension ObservableMacro: MemberMacro { declaration.addIfNeeded(ObservableMacro.accessFunction(observableType), to: &declarations) declaration.addIfNeeded(ObservableMacro.withMutationFunction(observableType), to: &declarations) - let storedInstanceVariables = declaration.definedVariables.filter { $0.isValidForObservation } - for property in storedInstanceVariables { - if property.hasMacroApplication(ObservableMacro.ignoredMacroName) { continue } - if property.initializer == nil { - context.addDiagnostics(from: DiagnosticsError(syntax: property, message: "@Observable requires property '\(property.identifier?.text ?? "")' to have an initial value", id: .missingInitializer), node: property) - } - } - return declarations } } @@ -290,6 +282,13 @@ public struct ObservationTrackedMacro: AccessorMacro { return [] } + let initAccessor: AccessorDeclSyntax = + """ + init(initialValue) initializes(_\(identifier)) { + _\(identifier) = initialValue + } + """ + let getAccessor: AccessorDeclSyntax = """ get { @@ -307,7 +306,7 @@ public struct ObservationTrackedMacro: AccessorMacro { } """ - return [getAccessor, setAccessor] + return [initAccessor, getAccessor, setAccessor] } } From 2302e4a4f5343e4d5d8d74fe8e227c46d7d77c59 Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Thu, 8 Jun 2023 10:54:14 -0700 Subject: [PATCH 3/5] [Observation] Gate enabling of peer macros by flag --- .../Sources/ObservationMacros/ObservableMacro.swift | 9 +++++++++ .../Observation/Sources/Observation/Observable.swift | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift index 33820450a8a2d..60708957d43f7 100644 --- a/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift +++ b/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift @@ -203,6 +203,15 @@ extension ObservableMacro: MemberMacro { declaration.addIfNeeded(ObservableMacro.accessFunction(observableType), to: &declarations) declaration.addIfNeeded(ObservableMacro.withMutationFunction(observableType), to: &declarations) +#if !OBSERVATION_SUPPORTS_PEER_MACROS + let storedInstanceVariables = declaration.definedVariables.filter { $0.isValidForObservation } + for property in storedInstanceVariables { + if property.hasMacroApplication(ObservableMacro.ignoredMacroName) { continue } + let storage = DeclSyntax(property.privatePrefixed("_", addingAttribute: ObservableMacro.ignoredAttribute)) + declaration.addIfNeeded(storage, to: &declarations) + } +#endif + return declarations } } diff --git a/stdlib/public/Observation/Sources/Observation/Observable.swift b/stdlib/public/Observation/Sources/Observation/Observable.swift index bfc4918a4d08d..a2f837af85db3 100644 --- a/stdlib/public/Observation/Sources/Observation/Observable.swift +++ b/stdlib/public/Observation/Sources/Observation/Observable.swift @@ -16,7 +16,11 @@ #if $Macros && hasAttribute(attached) @available(SwiftStdlib 5.9, *) +#if OBSERVATION_SUPPORTS_PEER_MACROS @attached(member, names: named(_$observationRegistrar), named(access), named(withMutation)) +#else +@attached(member, names: named(_$observationRegistrar), named(access), named(withMutation), arbitrary) +#endif @attached(memberAttribute) @attached(conformance) public macro Observable() = @@ -24,7 +28,9 @@ public macro Observable() = @available(SwiftStdlib 5.9, *) @attached(accessor) +#if OBSERVATION_SUPPORTS_PEER_MACROS @attached(peer, names: prefixed(_)) +#endif public macro ObservationTracked() = #externalMacro(module: "ObservationMacros", type: "ObservationTrackedMacro") From ca90d9f3069a21c78460d424a789e2db0324dedb Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Thu, 8 Jun 2023 12:07:03 -0700 Subject: [PATCH 4/5] [Observation] Enable feature for InitAccessors in the observation tests --- test/stdlib/Observation/Observable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/stdlib/Observation/Observable.swift b/test/stdlib/Observation/Observable.swift index 886bbf15a19de..cdc403c7bca26 100644 --- a/test/stdlib/Observation/Observable.swift +++ b/test/stdlib/Observation/Observable.swift @@ -1,6 +1,6 @@ // REQUIRES: swift_swift_parser, executable_test -// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins) +// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -parse-as-library -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins) // REQUIRES: observation // REQUIRES: concurrency From 613ce291bfbe5d13bf18479f6e9131948a7fcc7a Mon Sep 17 00:00:00 2001 From: Philippe Hausler Date: Fri, 9 Jun 2023 08:58:13 -0700 Subject: [PATCH 5/5] [Observation] Add tests to validate memberwise and definite initialization --- test/stdlib/Observation/Observable.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/stdlib/Observation/Observable.swift b/test/stdlib/Observation/Observable.swift index cdc403c7bca26..1fef61087f849 100644 --- a/test/stdlib/Observation/Observable.swift +++ b/test/stdlib/Observation/Observable.swift @@ -23,6 +23,24 @@ struct Structure { var field: Int = 0 } +@Observable +struct MemberwiseInitializers { + var field: Int +} + +func validateMemberwiseInitializers() { + _ = MemberwiseInitializers(field: 3) +} + +@Observable +struct DefiniteInitialization { + var field: Int + + init(field: Int) { + self.field = field + } +} + @Observable class ContainsWeak { weak var obj: AnyObject? = nil