Skip to content

feat: add CombineAsync - #37

Merged
Frognar merged 1 commit into
mainfrom
feat/tp-11-combine-async
Nov 14, 2026
Merged

Frognar merged 1 commit into
mainfrom
feat/tp-11-combine-async

Conversation

@Frognar

@Frognar Frognar commented Nov 14, 2026

Copy link
Copy Markdown
Owner

What

TP-11, card 6.2 of milestone M6 (1.0.0-beta.1), in the shape decided in #16.

  • Validated.CombineAsync(t1, …, tN, combine) for N = 2…8 Task<Validated<…>> inputs: the asynchronous Combine. The tasks run concurrently, and every error of every input comes back in argument order (V7). It is generated from one template, like Combine.
  • Every input is a task. Two spikes showed that a mix of bare validations and tasks cannot infer types (C# does not infer through conversions); the builder and ZipAsync alternatives were dropped (see the comment in feat: add CombineAsync (TP-11) #16). A synchronous check goes in as Task.FromResult(...), or the synchronous checks are combined on their own and joined with Zip.
  • R8 semantics: missing tasks or a missing combiner throw at once, by parameter name. When every task has already succeeded, the result is complete at once and nothing is allocated. Otherwise Task.WhenAll observes every task: a failed task comes out as itself, a canceled one cancels the result, an uninitialized result gives ArgumentException, and a null from the combiner gives InvalidOperationException. The library never resumes on the caller's synchronization context.

Tests: CombineAsyncProperties (every arity ≡ awaiting each input + Combine, with completed and yielding tasks; a completed result for completed tasks; a failure at any position of every arity comes out as itself; cancellation; null and uninitialized results; no posts to a counting SynchronizationContext for any arity; missing tasks by name for every position). The first Stryker run left 83 survivors. 34 were ConfigureAwait on already completed tasks, gone now that the results are read in a non-async helper. The rest showed missing per-arity tests, which are now in place. Allocation lines: CombineAsync(2) and (8) with completed tasks, 0 B. CombineAsyncContracts: tasks infer; a bare validation hits CS0411 and Task.FromResult fixes it; the split + Zip pattern compiles. Stryker locally: 100 %. README "Async" with the split pattern; AOT smoke extended (a yielding task, accumulation, joining with Zip).

Closes #16

Checklist

  • Tests cover the change (properties first, examples only where a property does not fit)
  • dotnet test --solution dotValid.slnx is green locally
  • Public API changes have XML docs

🤖 Generated with Claude Code

https://claude.ai/code/session_012weRUz3QcTAaQw32ZmQkoK

Validated.CombineAsync waits for two to eight validations that run as
tasks and combines them like Combine, every error in argument order (V7,
V10). Every input is a task (C# cannot infer types through conversions,
see #16); completed tasks give a completed result without allocating, and
every task is observed before combining.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012weRUz3QcTAaQw32ZmQkoK
@Frognar
Frognar merged commit e4e92e2 into main Nov 14, 2026
5 checks passed
@Frognar
Frognar deleted the feat/tp-11-combine-async branch November 14, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add CombineAsync (TP-11)

1 participant