Skip to content

Commit 36e5420

Browse files
authored
Support package access modifier for Observation architecture (#2741)
1 parent 63e29b3 commit 36e5420

File tree

5 files changed

+224
-8
lines changed

5 files changed

+224
-8
lines changed

Sources/ComposableArchitectureMacros/ObservableStateMacro.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ extension DeclModifierListSyntax {
134134
switch $0.name.tokenKind {
135135
case .keyword(let keyword):
136136
switch keyword {
137-
case .fileprivate, .private, .internal, .public:
137+
case .fileprivate, .private, .internal, .public, .package:
138138
return false
139139
default:
140140
return true
@@ -248,7 +248,7 @@ extension ObservableStateMacro: MemberMacro {
248248

249249
var declarations = [DeclSyntax]()
250250

251-
let access = declaration.modifiers.first { $0.name.tokenKind == .keyword(.public) }
251+
let access = declaration.modifiers.first { $0.name.tokenKind == .keyword(.public) || $0.name.tokenKind == .keyword(.package) }
252252
declaration.addIfNeeded(
253253
ObservableStateMacro.registrarVariable(observableType), to: &declarations)
254254
declaration.addIfNeeded(ObservableStateMacro.idVariable(access), to: &declarations)
@@ -267,7 +267,7 @@ extension ObservableStateMacro {
267267
providingMembersOf declaration: Declaration,
268268
in context: Context
269269
) throws -> [DeclSyntax] {
270-
let access = declaration.modifiers.first { $0.name.tokenKind == .keyword(.public) }
270+
let access = declaration.modifiers.first { $0.name.tokenKind == .keyword(.public) || $0.name.tokenKind == .keyword(.package) }
271271

272272
let enumCaseDecls = declaration.memberBlock.members
273273
.flatMap { $0.decl.as(EnumCaseDeclSyntax.self)?.elements ?? [] }

Sources/ComposableArchitectureMacros/ViewActionMacro.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,7 @@ public struct ViewActionMacro: ExtensionMacro {
2626
leadingTrivia: declarationWithStoreVariable.memberBlock.members.first?.leadingTrivia
2727
?? "\n ",
2828
decl: VariableDeclSyntax(
29-
bindingSpecifier: declaration.modifiers
30-
.contains(where: { $0.name.tokenKind == .keyword(.public) })
31-
? "public let"
32-
: "let",
29+
bindingSpecifier: declaration.modifiers.bindingSpecifier(),
3330
bindings: [
3431
PatternBindingSyntax(
3532
pattern: " store" as PatternSyntax,
@@ -160,6 +157,15 @@ extension DeclGroupSyntax {
160157
}
161158
}
162159

160+
extension DeclModifierListSyntax {
161+
fileprivate func bindingSpecifier() -> TokenSyntax {
162+
guard
163+
let modifier = first(where: { $0.name.tokenKind == .keyword(.public) || $0.name.tokenKind == .keyword(.package) })
164+
else { return "let" }
165+
return "\(raw: modifier.name.text) let"
166+
}
167+
}
168+
163169
extension FunctionCallExprSyntax {
164170
fileprivate var sendExpression: ExprSyntax? {
165171
guard

Tests/ComposableArchitectureMacrosTests/ObservableStateMacroTests.swift

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,95 @@
106106
}
107107
}
108108

109+
func testObservableState_AccessControl() throws {
110+
assertMacro {
111+
#"""
112+
@ObservableState
113+
public struct State {
114+
var count = 0
115+
}
116+
"""#
117+
} expansion: {
118+
#"""
119+
public struct State {
120+
var count = 0 {
121+
@storageRestrictions(initializes: _count)
122+
init(initialValue) {
123+
_count = initialValue
124+
}
125+
get {
126+
_$observationRegistrar.access(self, keyPath: \.count)
127+
return _count
128+
}
129+
set {
130+
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
131+
}
132+
_modify {
133+
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
134+
defer {
135+
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
136+
}
137+
yield &_count
138+
}
139+
}
140+
141+
var _$observationRegistrar = ComposableArchitecture.ObservationStateRegistrar()
142+
143+
public var _$id: ComposableArchitecture.ObservableStateID {
144+
_$observationRegistrar.id
145+
}
146+
147+
public mutating func _$willModify() {
148+
_$observationRegistrar._$willModify()
149+
}
150+
}
151+
"""#
152+
}
153+
assertMacro {
154+
#"""
155+
@ObservableState
156+
package struct State {
157+
var count = 0
158+
}
159+
"""#
160+
} expansion: {
161+
#"""
162+
package struct State {
163+
var count = 0 {
164+
@storageRestrictions(initializes: _count)
165+
init(initialValue) {
166+
_count = initialValue
167+
}
168+
get {
169+
_$observationRegistrar.access(self, keyPath: \.count)
170+
return _count
171+
}
172+
set {
173+
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
174+
}
175+
_modify {
176+
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
177+
defer {
178+
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
179+
}
180+
yield &_count
181+
}
182+
}
183+
184+
var _$observationRegistrar = ComposableArchitecture.ObservationStateRegistrar()
185+
186+
package var _$id: ComposableArchitecture.ObservableStateID {
187+
_$observationRegistrar.id
188+
}
189+
190+
package mutating func _$willModify() {
191+
_$observationRegistrar._$willModify()
192+
}
193+
}
194+
"""#
195+
}
196+
}
197+
109198
func testObservableStateIgnored() throws {
110199
assertMacro {
111200
#"""
@@ -242,6 +331,42 @@
242331
}
243332
"""
244333
}
334+
assertMacro {
335+
"""
336+
@ObservableState
337+
package enum Path {
338+
case feature1(Feature1.State)
339+
case feature2(Feature2.State)
340+
}
341+
"""
342+
} expansion: {
343+
"""
344+
package enum Path {
345+
case feature1(Feature1.State)
346+
case feature2(Feature2.State)
347+
348+
package var _$id: ComposableArchitecture.ObservableStateID {
349+
switch self {
350+
case let .feature1(state):
351+
return ._$id(for: state)._$tag(0)
352+
case let .feature2(state):
353+
return ._$id(for: state)._$tag(1)
354+
}
355+
}
356+
357+
package mutating func _$willModify() {
358+
switch self {
359+
case var .feature1(state):
360+
ComposableArchitecture._$willModify(&state)
361+
self = .feature1(state)
362+
case var .feature2(state):
363+
ComposableArchitecture._$willModify(&state)
364+
self = .feature2(state)
365+
}
366+
}
367+
}
368+
"""
369+
}
245370
}
246371

247372
func testObservableState_Enum_NonObservableCase() {

Tests/ComposableArchitectureMacrosTests/PresentsMacroTests.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
}
5656
}
5757

58-
func testPublicAccess() {
58+
func testAccessControl() {
5959
assertMacro {
6060
"""
6161
public struct State {
@@ -93,6 +93,43 @@
9393
}
9494
"""#
9595
}
96+
assertMacro {
97+
"""
98+
package struct State {
99+
@Presents package var destination: Destination.State?
100+
}
101+
"""
102+
} expansion: {
103+
#"""
104+
package struct State {
105+
package var destination: Destination.State? {
106+
@storageRestrictions(initializes: _destination)
107+
init(initialValue) {
108+
_destination = PresentationState(wrappedValue: initialValue)
109+
}
110+
get {
111+
_$observationRegistrar.access(self, keyPath: \.destination)
112+
return _destination.wrappedValue
113+
}
114+
set {
115+
_$observationRegistrar.mutate(self, keyPath: \.destination, &_destination.wrappedValue, newValue, _$isIdentityEqual)
116+
}
117+
}
118+
119+
package var $destination: ComposableArchitecture.PresentationState<Destination.State> {
120+
get {
121+
_$observationRegistrar.access(self, keyPath: \.destination)
122+
return _destination.projectedValue
123+
}
124+
set {
125+
_$observationRegistrar.mutate(self, keyPath: \.destination, &_destination.projectedValue, newValue, _$isIdentityEqual)
126+
}
127+
}
128+
129+
@ObservationStateIgnored private var _destination = ComposableArchitecture.PresentationState<Destination.State>(wrappedValue: nil)
130+
}
131+
"""#
132+
}
96133
}
97134

98135
func testObservableStateDiagnostic() {

Tests/ComposableArchitectureMacrosTests/ViewActionMacroTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,54 @@
217217
}
218218
}
219219

220+
func testNoStore_Package() {
221+
assertMacro {
222+
"""
223+
@ViewAction(for: Feature.self)
224+
package struct FeatureView: View {
225+
package var body: some View {
226+
EmptyView()
227+
}
228+
}
229+
"""
230+
} diagnostics: {
231+
"""
232+
@ViewAction(for: Feature.self)
233+
╰─ 🛑 '@ViewAction' requires 'FeatureView' to have a 'store' property of type 'Store'.
234+
✏️ Add 'store'
235+
package struct FeatureView: View {
236+
package var body: some View {
237+
EmptyView()
238+
}
239+
}
240+
"""
241+
} fixes: {
242+
"""
243+
@ViewAction(for: Feature.self)
244+
package struct FeatureView: View {
245+
package let store: StoreOf<Feature>
246+
247+
package var body: some View {
248+
EmptyView()
249+
}
250+
}
251+
"""
252+
} expansion: {
253+
"""
254+
package struct FeatureView: View {
255+
package let store: StoreOf<Feature>
256+
257+
package var body: some View {
258+
EmptyView()
259+
}
260+
}
261+
262+
extension FeatureView: ComposableArchitecture.ViewActionSending {
263+
}
264+
"""
265+
}
266+
}
267+
220268
func testWarning_StoreSend() {
221269
assertMacro {
222270
"""

0 commit comments

Comments
 (0)