Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Sources/ComposableArchitecture/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public macro ReducerCaseIgnored() =

/// Defines and implements conformance of the Observable protocol.
@attached(extension, conformances: Observable, ObservableState)
@attached(member, names: named(_$id), named(_$observationRegistrar), named(_$willModify))
@attached(member, names: named(_$id), named(_$observationRegistrar), named(_$willModify), named(shouldNotifyObservers))
@attached(memberAttribute)
public macro ObservableState() =
#externalMacro(module: "ComposableArchitectureMacros", type: "ObservableStateMacro")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@ extension ObservationStateRegistrar: Equatable, Hashable, Codable {
keyPath: KeyPath<Subject, Member>,
_ value: inout Value,
_ newValue: Value,
_ isIdentityEqual: (Value, Value) -> Bool
_ isIdentityEqual: (Value, Value) -> Bool,
_ shouldNotifyObservers: (Value, Value) -> Bool
) {
if isIdentityEqual(value, newValue) {
value = newValue
} else {
self.registrar.withMutation(of: subject, keyPath: keyPath) {
if shouldNotifyObservers(value, newValue) {
self.registrar.withMutation(of: subject, keyPath: keyPath) {
value = newValue
}
} else {
value = newValue
}
}
Expand Down Expand Up @@ -101,12 +106,13 @@ extension ObservationStateRegistrar: Equatable, Hashable, Codable {
keyPath: KeyPath<Subject, Member>,
_ member: inout Member,
_ oldValue: Member,
_ isIdentityEqual: (Member, Member) -> Bool
_ isIdentityEqual: (Member, Member) -> Bool,
_ shouldNotifyObservers: (Member, Member) -> Bool
) {
if !isIdentityEqual(oldValue, member) {
let newValue = member
member = oldValue
self.mutate(subject, keyPath: keyPath, &member, newValue, isIdentityEqual)
self.mutate(subject, keyPath: keyPath, &member, newValue, isIdentityEqual, shouldNotifyObservers)
}
}
}
Expand Down
40 changes: 38 additions & 2 deletions Sources/ComposableArchitectureMacros/ObservableStateMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ public struct ObservableStateMacro {
"""
}

static func shouldNotifyObserversNonEquatableFunction(_ perceptibleType: TokenSyntax, context: some MacroExpansionContext) -> DeclSyntax {
let memberGeneric = context.makeUniqueName("Member")
return
"""
private nonisolated func shouldNotifyObservers<\(memberGeneric)>(_ lhs: \(memberGeneric), _ rhs: \(memberGeneric)) -> Bool { true }
"""
}

static func shouldNotifyObserversEquatableFunction(_ perceptibleType: TokenSyntax, context: some MacroExpansionContext) -> DeclSyntax {
let memberGeneric = context.makeUniqueName("Member")
return
"""
private nonisolated func shouldNotifyObservers<\(memberGeneric): Equatable>(_ lhs: \(memberGeneric), _ rhs: \(memberGeneric)) -> Bool { lhs != rhs }
"""
}

static func shouldNotifyObserversNonEquatableObjectFunction(_ perceptibleType: TokenSyntax, context: some MacroExpansionContext) -> DeclSyntax {
let memberGeneric = context.makeUniqueName("Member")
return
"""
private nonisolated func shouldNotifyObservers<\(memberGeneric): AnyObject>(_ lhs: \(memberGeneric), _ rhs: \(memberGeneric)) -> Bool { lhs !== rhs }
"""
}

static func shouldNotifyObserversEquatableObjectFunction(_ perceptibleType: TokenSyntax, context: some MacroExpansionContext) -> DeclSyntax {
let memberGeneric = context.makeUniqueName("Member")
return
"""
private nonisolated func shouldNotifyObservers<\(memberGeneric): Equatable & AnyObject>(_ lhs: \(memberGeneric), _ rhs: \(memberGeneric)) -> Bool { lhs != rhs }
"""
}

static var ignoredAttribute: AttributeSyntax {
AttributeSyntax(
leadingTrivia: .space,
Expand Down Expand Up @@ -298,6 +330,10 @@ extension ObservableStateMacro: MemberMacro {
)
declaration.addIfNeeded(ObservableStateMacro.idVariable(), to: &declarations)
declaration.addIfNeeded(ObservableStateMacro.willModifyFunction(), to: &declarations)
declaration.addIfNeeded(ObservableStateMacro.shouldNotifyObserversNonEquatableFunction(observableType, context: context), to: &declarations)
declaration.addIfNeeded(ObservableStateMacro.shouldNotifyObserversEquatableFunction(observableType, context: context), to: &declarations)
declaration.addIfNeeded(ObservableStateMacro.shouldNotifyObserversNonEquatableObjectFunction(observableType, context: context), to: &declarations)
declaration.addIfNeeded(ObservableStateMacro.shouldNotifyObserversEquatableObjectFunction(observableType, context: context), to: &declarations)

return declarations
}
Expand Down Expand Up @@ -611,14 +647,14 @@ public struct ObservationStateTrackedMacro: AccessorMacro {
let setAccessor: AccessorDeclSyntax =
"""
set {
\(raw: ObservableStateMacro.registrarVariableName).mutate(self, keyPath: \\.\(identifier), &_\(identifier), newValue, _$isIdentityEqual)
\(raw: ObservableStateMacro.registrarVariableName).mutate(self, keyPath: \\.\(identifier), &_\(identifier), newValue, _$isIdentityEqual, shouldNotifyObservers)
}
"""
let modifyAccessor: AccessorDeclSyntax = """
_modify {
let oldValue = _$observationRegistrar.willModify(self, keyPath: \\.\(identifier), &_\(identifier))
defer {
_$observationRegistrar.didModify(self, keyPath: \\.\(identifier), &_\(identifier), oldValue, _$isIdentityEqual)
_$observationRegistrar.didModify(self, keyPath: \\.\(identifier), &_\(identifier), oldValue, _$isIdentityEqual, shouldNotifyObservers)
}
yield &_\(identifier)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

final class ObservableStateMacroTests: MacroBaseTestCase {
override func invokeTest() {
withMacroTesting {
withMacroTesting(
// record: .failed,
) {
super.invokeTest()
}
}
Expand Down Expand Up @@ -35,12 +37,12 @@
return _count
}
set {
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual, shouldNotifyObservers)
}
_modify {
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
defer {
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual, shouldNotifyObservers)
}
yield &_count
}
Expand All @@ -55,6 +57,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""#
}
Expand All @@ -81,12 +99,12 @@
return _count
}
set {
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual, shouldNotifyObservers)
}
_modify {
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
defer {
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual, shouldNotifyObservers)
}
yield &_count
}
Expand All @@ -101,6 +119,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""#
}
Expand All @@ -127,12 +161,12 @@
return _count
}
set {
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual, shouldNotifyObservers)
}
_modify {
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
defer {
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual, shouldNotifyObservers)
}
yield &_count
}
Expand All @@ -147,6 +181,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""#
}
Expand All @@ -170,12 +220,12 @@
return _count
}
set {
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual)
_$observationRegistrar.mutate(self, keyPath: \.count, &_count, newValue, _$isIdentityEqual, shouldNotifyObservers)
}
_modify {
let oldValue = _$observationRegistrar.willModify(self, keyPath: \.count, &_count)
defer {
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual)
_$observationRegistrar.didModify(self, keyPath: \.count, &_count, oldValue, _$isIdentityEqual, shouldNotifyObservers)
}
yield &_count
}
Expand All @@ -190,6 +240,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""#
}
Expand Down Expand Up @@ -218,6 +284,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""
}
Expand Down Expand Up @@ -673,6 +755,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""
}
Expand Down
18 changes: 17 additions & 1 deletion Tests/ComposableArchitectureMacrosTests/PresentsMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
final class PresentsMacroTests: XCTestCase {
override func invokeTest() {
withMacroTesting(
// isRecording: true,
// record: .failed,
macros: [PresentsMacro.self]
) {
super.invokeTest()
Expand Down Expand Up @@ -200,6 +200,22 @@
public mutating func _$willModify() {
_$observationRegistrar._$willModify()
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu_>(_ lhs: __macro_local_6MemberfMu_, _ rhs: __macro_local_6MemberfMu_) -> Bool {
true
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu0_: Equatable>(_ lhs: __macro_local_6MemberfMu0_, _ rhs: __macro_local_6MemberfMu0_) -> Bool {
lhs != rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu1_: AnyObject>(_ lhs: __macro_local_6MemberfMu1_, _ rhs: __macro_local_6MemberfMu1_) -> Bool {
lhs !== rhs
}

private nonisolated func shouldNotifyObservers<__macro_local_6MemberfMu2_: Equatable & AnyObject>(_ lhs: __macro_local_6MemberfMu2_, _ rhs: __macro_local_6MemberfMu2_) -> Bool {
lhs != rhs
}
}
"""#
}
Expand Down
13 changes: 13 additions & 0 deletions Tests/ComposableArchitectureTests/ObservableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ final class ObservableTests: BaseTCATestCase {
XCTAssertEqual(state.count, 1)
}

func testAssignEqualValue() async {
var state = ChildState()

withPerceptionTracking {
_ = state.count
} onChange: {
XCTFail("state.count should not change.")
}

state.count = state.count
XCTAssertEqual(state.count, 0)
}

func testCopyMutation() async {
XCTTODO(
"""
Expand Down
Loading