Skip to content
Merged
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
119 changes: 50 additions & 69 deletions Sources/Atoms/Effect/AtomEffectBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@
@MainActor
@resultBuilder
public enum AtomEffectBuilder {
public static func buildBlock() -> some AtomEffect {
EmptyEffect()
}

public static func buildBlock<Effect: AtomEffect>(_ effect: Effect) -> Effect {
effect
}

public static func buildBlock<each Effect: AtomEffect>(_ effect: repeat each Effect) -> some AtomEffect {
if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) {
return BlockEffect(repeat each effect)
}
else {
return BlockBCEffect(repeat each effect)
}
// NB: The combination of variadic type and opaque return type in iOS 16 fails to demangle
// the witness for the return type, which causes a runtime crash.
// Once iOS 16 support is dropped, this function will be changed to return an opaque type.
public static func buildBlock<each Effect: AtomEffect>(_ effect: repeat each Effect) -> BlockEffect {
BlockEffect(repeat each effect)
}

public static func buildIf<Effect: AtomEffect>(_ effect: Effect?) -> some AtomEffect {
Expand All @@ -54,6 +56,46 @@ public enum AtomEffectBuilder {
}

public extension AtomEffectBuilder {
// Use type pack once it is available in iOS 17 or newer.
// struct BlockEffect<each Effect: AtomEffect>: AtomEffect
struct BlockEffect: AtomEffect {
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

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

Consider annotating BlockEffect (and its initializer and methods) with @MainActor to enforce actor isolation and prevent potential threading issues, aligning with the @MainActor context of AtomEffectBuilder.

Copilot uses AI. Check for mistakes.
private let _initializing: @MainActor (Context) -> Void
private let _initialized: @MainActor (Context) -> Void
private let _updated: @MainActor (Context) -> Void
private let _released: @MainActor (Context) -> Void

internal init<each Effect: AtomEffect>(_ effect: repeat each Effect) {
_initializing = { context in
repeat (each effect).initializing(context: context)
}
_initialized = { context in
repeat (each effect).initialized(context: context)
}
_updated = { context in
repeat (each effect).updated(context: context)
}
_released = { context in
repeat (each effect).released(context: context)
}
Comment on lines +68 to +79
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

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

[nitpick] The initializer and the related closure properties (_initializing, _initialized, etc.) are repetitive. Consider abstracting the repeated logic—e.g., iterating over a collection of effects in a single closure—to reduce boilerplate.

Suggested change
_initializing = { context in
repeat (each effect).initializing(context: context)
}
_initialized = { context in
repeat (each effect).initialized(context: context)
}
_updated = { context in
repeat (each effect).updated(context: context)
}
_released = { context in
repeat (each effect).released(context: context)
}
_initializing = createEffectHandler(effect: repeat each effect, method: { $0.initializing(context:) })
_initialized = createEffectHandler(effect: repeat each effect, method: { $0.initialized(context:) })
_updated = createEffectHandler(effect: repeat each effect, method: { $0.updated(context:) })
_released = createEffectHandler(effect: repeat each effect, method: { $0.released(context:) })
}
private func createEffectHandler<each Effect: AtomEffect>(
effect: repeat each Effect,
method: @escaping (Effect) -> (Context) -> Void
) -> @MainActor (Context) -> Void {
return { context in
repeat method(each effect)(context)
}

Copilot uses AI. Check for mistakes.
}

public func initializing(context: Context) {
_initializing(context)
}

public func initialized(context: Context) {
_initialized(context)
}

public func updated(context: Context) {
_updated(context)
}

public func released(context: Context) {
_released(context)
}
}

struct ConditionalEffect<TrueEffect: AtomEffect, FalseEffect: AtomEffect>: AtomEffect {
internal enum Storage {
case trueEffect(TrueEffect)
Expand Down Expand Up @@ -108,68 +150,7 @@ public extension AtomEffectBuilder {
}

private extension AtomEffectBuilder {
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
struct BlockEffect<each Effect: AtomEffect>: AtomEffect {
private let effect: (repeat each Effect)

init(_ effect: repeat each Effect) {
self.effect = (repeat each effect)
}

func initializing(context: Context) {
repeat (each effect).initializing(context: context)
}

func initialized(context: Context) {
repeat (each effect).initialized(context: context)
}

func updated(context: Context) {
repeat (each effect).updated(context: context)
}

func released(context: Context) {
repeat (each effect).released(context: context)
}
}

struct BlockBCEffect: AtomEffect {
private let _initializing: @MainActor (Context) -> Void
private let _initialized: @MainActor (Context) -> Void
private let _updated: @MainActor (Context) -> Void
private let _released: @MainActor (Context) -> Void

init<each Effect: AtomEffect>(_ effect: repeat each Effect) {
_initializing = { context in
repeat (each effect).initializing(context: context)
}
_initialized = { context in
repeat (each effect).initialized(context: context)
}
_updated = { context in
repeat (each effect).updated(context: context)
}
_released = { context in
repeat (each effect).released(context: context)
}
}

func initializing(context: Context) {
_initializing(context)
}

func initialized(context: Context) {
_initialized(context)
}

func updated(context: Context) {
_updated(context)
}

func released(context: Context) {
_released(context)
}
}
struct EmptyEffect: AtomEffect {}

struct IfEffect<Effect: AtomEffect>: AtomEffect {
private let effect: Effect?
Expand Down