Skip to content

[NFC]: reformatting and some generic syntax sugar #332

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

Merged
merged 1 commit into from
Apr 4, 2025
Merged
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
26 changes: 21 additions & 5 deletions Workflow/Sources/AnyWorkflow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ extension AnyWorkflow {
/// - Parameter transform: An escaping closure that maps the original output type into the new output type.
///
/// - Returns: A type erased workflow with the new output type (the rendering type remains unchanged).
public func mapOutput<NewOutput>(_ transform: @escaping (Output) -> NewOutput) -> AnyWorkflow<Rendering, NewOutput> {
public func mapOutput<NewOutput>(
_ transform: @escaping (Output) -> NewOutput
) -> AnyWorkflow<Rendering, NewOutput> {
let storage = storage.mapOutput(transform: transform)
return AnyWorkflow<Rendering, NewOutput>(storage: storage)
}
Expand All @@ -76,7 +78,9 @@ extension AnyWorkflow {
/// - Parameter transform: An escaping closure that maps the original rendering type into the new rendering type.
///
/// - Returns: A type erased workflow with the new rendering type (the output type remains unchanged).
public func mapRendering<NewRendering>(_ transform: @escaping (Rendering) -> NewRendering) -> AnyWorkflow<NewRendering, Output> {
public func mapRendering<NewRendering>(
_ transform: @escaping (Rendering) -> NewRendering
) -> AnyWorkflow<NewRendering, Output> {
let storage = storage.mapRendering(transform: transform)
return AnyWorkflow<NewRendering, Output>(storage: storage)
}
Expand All @@ -91,7 +95,11 @@ extension AnyWorkflow {
/// That type information *is* present in our storage object, however, so we
/// pass the context down to that storage object which will ultimately call
/// through to `context.render(workflow:key:reducer:)`.
func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
func render<Parent, Action: WorkflowAction>(
context: RenderContext<Parent>,
key: String,
outputMap: @escaping (Output) -> Action
) -> Rendering where Action.WorkflowType == Parent {
storage.render(context: context, key: key, outputMap: outputMap)
}
}
Expand All @@ -103,7 +111,11 @@ extension AnyWorkflow {
fileprivate class AnyStorage {
var base: Any { fatalError() }

func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
func render<Parent, Action: WorkflowAction>(
context: RenderContext<Parent>,
key: String,
outputMap: @escaping (Output) -> Action
) -> Rendering where Action.WorkflowType == Parent {
fatalError()
}

Expand Down Expand Up @@ -140,7 +152,11 @@ extension AnyWorkflow {
T.self
}

override func render<Parent, Action>(context: RenderContext<Parent>, key: String, outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
override func render<Parent, Action: WorkflowAction>(
context: RenderContext<Parent>,
key: String,
outputMap: @escaping (Output) -> Action
) -> Rendering where Action.WorkflowType == Parent {
let outputMap: (T.Output) -> Action = { [outputTransform] output in
outputMap(outputTransform(output))
}
Expand Down
35 changes: 30 additions & 5 deletions Workflow/Sources/AnyWorkflowConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,31 @@ extension AnyWorkflowConvertible {
/// - Parameter key: A string that uniquely identifies this workflow.
///
/// - Returns: The `Rendering` generated by the workflow.
public func rendered<Parent>(in context: RenderContext<Parent>, key: String = "") -> Rendering where Output: WorkflowAction, Output.WorkflowType == Parent {
public func rendered<Parent>(
in context: RenderContext<Parent>,
key: String = ""
) -> Rendering
where Output: WorkflowAction, Output.WorkflowType == Parent
{
asAnyWorkflow().render(context: context, key: key, outputMap: { $0 })
}

public func rendered<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) -> Rendering where Action: WorkflowAction, Action.WorkflowType == Parent {
public func rendered<Parent, Action: WorkflowAction>(
in context: RenderContext<Parent>,
key: String = "",
outputMap: @escaping (Output) -> Action
) -> Rendering
where Action.WorkflowType == Parent
{
asAnyWorkflow().render(context: context, key: key, outputMap: { outputMap($0) })
}

public func rendered<Parent>(in context: RenderContext<Parent>, key: String = "") -> Rendering where Output == AnyWorkflowAction<Parent> {
public func rendered<Parent>(
in context: RenderContext<Parent>,
key: String = ""
) -> Rendering
where Output == AnyWorkflowAction<Parent>
{
asAnyWorkflow().render(context: context, key: key, outputMap: { $0 })
}
}
Expand Down Expand Up @@ -73,13 +89,22 @@ extension AnyWorkflowConvertible where Output == Never {
}

extension AnyWorkflowConvertible where Rendering == Void {
public func running<Parent, Action>(in context: RenderContext<Parent>, key: String = "", outputMap: @escaping (Output) -> Action) where Action: WorkflowAction, Action.WorkflowType == Parent {
public func running<Parent, Action: WorkflowAction>(
in context: RenderContext<Parent>,
key: String = "",
outputMap: @escaping (Output) -> Action
)
where Action.WorkflowType == Parent
{
rendered(in: context, key: key, outputMap: outputMap)
}
}

extension AnyWorkflowConvertible where Rendering == Void, Output: WorkflowAction {
public func running<Parent>(in context: RenderContext<Parent>, key: String = "") where Output.WorkflowType == Parent {
public func running<Parent>(
in context: RenderContext<Parent>,
key: String = ""
) where Output.WorkflowType == Parent {
rendered(in: context, key: key)
}
}
Expand Down
49 changes: 40 additions & 9 deletions Workflow/Sources/RenderContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
/// - Parameter key: A string that uniquely identifies this child.
///
/// - Returns: The `Rendering` result of the child's `render` method.
func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowAction, WorkflowType == Action.WorkflowType {
func render<Child: Workflow, Action: WorkflowAction>(
workflow: Child,
key: String,
outputMap: @escaping (Child.Output) -> Action
) -> Child.Rendering where WorkflowType == Action.WorkflowType {
fatalError()
}

Expand All @@ -77,7 +81,9 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
///
/// - Parameter actionType: The type of Action this Sink may process
/// - Returns: A Sink capable of relaying `Action` instances to the Workflow runtime
public func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, Action.WorkflowType == WorkflowType {
public func makeSink<Action: WorkflowAction>(
of actionType: Action.Type
) -> Sink<Action> where Action.WorkflowType == WorkflowType {
fatalError()
}

Expand Down Expand Up @@ -118,17 +124,30 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
super.init()
}

override func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where WorkflowType == Action.WorkflowType, Child: Workflow, Action: WorkflowAction {
override func render<Child: Workflow, Action: WorkflowAction>(
workflow: Child,
key: String,
outputMap: @escaping (Child.Output) -> Action
) -> Child.Rendering
where WorkflowType == Action.WorkflowType
{
assertStillValid()
return implementation.render(workflow: workflow, key: key, outputMap: outputMap)
}

override func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where WorkflowType == Action.WorkflowType, Action: WorkflowAction {
override func makeSink<Action: WorkflowAction>(
of actionType: Action.Type
) -> Sink<Action>
where WorkflowType == Action.WorkflowType
{
assertStillValid()
return implementation.makeSink(of: actionType)
}

override func runSideEffect(key: AnyHashable, action: (_ lifetime: Lifetime) -> Void) {
override func runSideEffect(
key: AnyHashable,
action: (_ lifetime: Lifetime) -> Void
) {
assertStillValid()
implementation.runSideEffect(key: key, action: action)
}
Expand All @@ -142,15 +161,27 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
protocol RenderContextType: AnyObject {
associatedtype WorkflowType: Workflow

func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowAction, Action.WorkflowType == WorkflowType
func render<Child: Workflow, Action: WorkflowAction>(
workflow: Child,
key: String,
outputMap: @escaping (Child.Output) -> Action
) -> Child.Rendering where Action.WorkflowType == WorkflowType

func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, Action.WorkflowType == WorkflowType
func makeSink<Action: WorkflowAction>(
of actionType: Action.Type
) -> Sink<Action> where Action.WorkflowType == WorkflowType

func runSideEffect(key: AnyHashable, action: (_ lifetime: Lifetime) -> Void)
func runSideEffect(
key: AnyHashable,
action: (_ lifetime: Lifetime) -> Void
)
}

extension RenderContext {
public func makeSink<Event>(of eventType: Event.Type, onEvent: @escaping (Event, inout WorkflowType.State) -> WorkflowType.Output?) -> Sink<Event> {
public func makeSink<Event>(
of eventType: Event.Type,
onEvent: @escaping (Event, inout WorkflowType.State) -> WorkflowType.Output?
) -> Sink<Event> {
makeSink(of: AnyWorkflowAction.self)
.contraMap { event in
AnyWorkflowAction<WorkflowType> { state in
Expand Down
15 changes: 9 additions & 6 deletions Workflow/Sources/SubtreeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,12 @@ extension WorkflowNode.SubtreeManager {
self.session = session
}

func render<Child, Action>(
func render<Child: Workflow, Action: WorkflowAction>(
workflow: Child,
key: String,
outputMap: @escaping (Child.Output) -> Action
) -> Child.Rendering
where Child: Workflow,
Action: WorkflowAction,
WorkflowType == Action.WorkflowType
where WorkflowType == Action.WorkflowType
{
/// A unique key used to identify this child workflow
let childKey = ChildKey(childType: Child.self, key: key)
Expand Down Expand Up @@ -256,7 +254,9 @@ extension WorkflowNode.SubtreeManager {
return child.render()
}

func makeSink<Action>(of actionType: Action.Type) -> Sink<Action> where Action: WorkflowAction, WorkflowType == Action.WorkflowType {
func makeSink<Action: WorkflowAction>(
of actionType: Action.Type
) -> Sink<Action> where WorkflowType == Action.WorkflowType {
let reusableSink = sinkStore.findOrCreate(actionType: Action.self)

let sink = Sink<Action> { [weak reusableSink] action in
Expand All @@ -269,7 +269,10 @@ extension WorkflowNode.SubtreeManager {
return sink
}

func runSideEffect(key: AnyHashable, action: (Lifetime) -> Void) {
func runSideEffect(
key: AnyHashable,
action: (Lifetime) -> Void
) {
if let existingSideEffect = originalSideEffectLifetimes[key] {
usedSideEffectLifetimes[key] = existingSideEffect
} else {
Expand Down
2 changes: 1 addition & 1 deletion Workflow/Sources/WorkflowAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct AnyWorkflowAction<WorkflowType: Workflow>: WorkflowAction {
/// Creates a type-erased workflow action that wraps the given instance.
///
/// - Parameter base: A workflow action to wrap.
public init<E>(_ base: E) where E: WorkflowAction, E.WorkflowType == WorkflowType {
public init<E: WorkflowAction>(_ base: E) where E.WorkflowType == WorkflowType {
if let anyEvent = base as? AnyWorkflowAction<WorkflowType> {
self = anyEvent
return
Expand Down
8 changes: 4 additions & 4 deletions Workflow/Sources/WorkflowObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,11 @@ final class ChainedWorkflowObserver: WorkflowObserver {
}
}

func workflowDidReceiveAction<Action>(
func workflowDidReceiveAction<Action: WorkflowAction>(
_ action: Action,
workflow: Action.WorkflowType,
session: WorkflowSession
) where Action: WorkflowAction {
) {
for observer in observers {
observer.workflowDidReceiveAction(
action,
Expand All @@ -290,12 +290,12 @@ final class ChainedWorkflowObserver: WorkflowObserver {
}
}

func workflowWillApplyAction<Action>(
func workflowWillApplyAction<Action: WorkflowAction>(
_ action: Action,
workflow: Action.WorkflowType,
state: Action.WorkflowType.State,
session: WorkflowSession
) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? where Action: WorkflowAction {
) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? {
let callbacks = observers.compactMap {
$0.workflowWillApplyAction(
action,
Expand Down
4 changes: 2 additions & 2 deletions Workflow/Tests/WorkflowObserverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -647,11 +647,11 @@ private final class TestObserver: WorkflowObserver {
onDidChange?(oldWorkflow, newWorkflow, state, session)
}

func workflowDidReceiveAction<Action>(_ action: Action, workflow: Action.WorkflowType, session: WorkflowSession) where Action: WorkflowAction {
func workflowDidReceiveAction<Action: WorkflowAction>(_ action: Action, workflow: Action.WorkflowType, session: WorkflowSession) {
onDidReceiveAction?(action, workflow, session)
}

func workflowWillApplyAction<Action>(_ action: Action, workflow: Action.WorkflowType, state: Action.WorkflowType.State, session: WorkflowSession) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? where Action: WorkflowAction {
func workflowWillApplyAction<Action: WorkflowAction>(_ action: Action, workflow: Action.WorkflowType, state: Action.WorkflowType.State, session: WorkflowSession) -> ((Action.WorkflowType.State, Action.WorkflowType.Output?) -> Void)? {
onApplyAction?(action, workflow, state, session)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension RenderTester {
self.line = line
}

func render<Child, Action>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Child: Workflow, Action: WorkflowAction, Action.WorkflowType == WorkflowType {
func render<Child: Workflow, Action: WorkflowAction>(workflow: Child, key: String, outputMap: @escaping (Child.Output) -> Action) -> Child.Rendering where Action.WorkflowType == WorkflowType {
let matchingTypes = expectedWorkflows.compactMap { $0 as? ExpectedWorkflow<Child> }
guard let expectedWorkflow = matchingTypes.first(where: { $0.key == key }) else {
let sameTypeDifferentKeys = matchingTypes.map(\.key)
Expand Down Expand Up @@ -88,7 +88,7 @@ extension RenderTester {
return expectedWorkflow.rendering
}

func makeSink<ActionType>(of actionType: ActionType.Type) -> Sink<ActionType> where ActionType: WorkflowAction, ActionType.WorkflowType == WorkflowType {
func makeSink<ActionType: WorkflowAction>(of actionType: ActionType.Type) -> Sink<ActionType> where ActionType.WorkflowType == WorkflowType {
Sink<ActionType> { action in
self.apply(action: action)
}
Expand All @@ -114,7 +114,7 @@ extension RenderTester {
}
}

private func apply<ActionType>(action: ActionType) where ActionType: WorkflowAction, ActionType.WorkflowType == WorkflowType {
private func apply<ActionType: WorkflowAction>(action: ActionType) where ActionType.WorkflowType == WorkflowType {
XCTAssertNil(appliedAction, "Received multiple actions in a single render test", file: file, line: line)
appliedAction = AppliedAction(action)
let output = action.apply(toState: &state)
Expand Down
2 changes: 1 addition & 1 deletion WorkflowTesting/Sources/WorkflowActionTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extension WorkflowAction {
/// .assert(output: .finished)
/// .assert(state: .differentState)
/// ```
public struct WorkflowActionTester<WorkflowType, Action> where Action: WorkflowAction, Action.WorkflowType == WorkflowType {
public struct WorkflowActionTester<WorkflowType, Action: WorkflowAction> where Action.WorkflowType == WorkflowType {
/// The current state
let state: WorkflowType.State
let output: WorkflowType.Output?
Expand Down
4 changes: 2 additions & 2 deletions WorkflowTesting/Sources/WorkflowRenderTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,11 @@ public struct RenderTester<WorkflowType: Workflow> {
/// - Parameters:
/// - key: The key to expect.
/// - action: The action to produce when this side-effect is requested.
public func expectSideEffect<ActionType>(
public func expectSideEffect<ActionType: WorkflowAction>(
key: AnyHashable,
producingAction action: ActionType,
file: StaticString = #file, line: UInt = #line
) -> RenderTester<WorkflowType> where ActionType: WorkflowAction, ActionType.WorkflowType == WorkflowType {
) -> RenderTester<WorkflowType> where ActionType.WorkflowType == WorkflowType {
RenderTester(
workflow: workflow,
state: state,
Expand Down
Loading