breaking: Throw error on empty concatenate, remove force cast#12
Merged
takeshishimada merged 2 commits intomainfrom Oct 30, 2025
Merged
breaking: Throw error on empty concatenate, remove force cast#12takeshishimada merged 2 commits intomainfrom
takeshishimada merged 2 commits intomainfrom
Conversation
Make ActionTask.concatenate() throw FlowError.noTasksToExecute when
given an empty array, implementing fail-fast principle for better
error detection and debugging.
Changes:
- Add FlowError.noTasksToExecute case with comprehensive documentation
- Make ActionTask.concatenate(_:) throwing for array version
- Remove force cast from Store.swift executeTask() method
- Add precondition with clear invariant documentation
- Update all LocalizedError implementations
Benefits:
- Fail fast: Empty arrays detected immediately
- Better debugging: Clear error messages with recovery suggestions
- Type safety: Works for all ActionResult types (not just Void)
- No force cast: Eliminates runtime type checking
- Explicit intent: Developers must handle empty case explicitly
Breaking Changes:
- ActionTask.concatenate([ActionTask]) now throws
- Callers must either:
1. Use try and handle the error
2. Guard against empty arrays before calling
3. Variadic version remains non-throwing (compile-time safe)
Migration:
```swift
// Before
return .concatenate(tasks)
// After Option 1: Explicit guard
guard !tasks.isEmpty else {
return .none // Empty is valid
}
return try .concatenate(tasks)
// After Option 2: Propagate error
return try .concatenate(tasks)
```
All tests pass (299 tests). No existing code used empty concatenate.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix SwiftLint errors and add documentation for dynamic array usage. Changes: - Add swiftlint:disable for justified force_try in variadic concatenate - Fix line length violations in Store.swift and FlowError.swift - Add DocC example for dynamic task concatenation with empty handling SwiftLint fixes: - ActionTask.swift:479 - Disable force_try (justified: variadic guarantees non-empty) - Store.swift:343 - Split long precondition message across multiple lines - FlowError.swift:193,195 - Use multiline strings for long error messages DocC updates: - Add migration guide for dynamic concatenate in CoreElements.md - Show both static (variadic) and dynamic (array) usage patterns - Demonstrate explicit empty array handling with guard All tests pass (299 tests). SwiftLint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make
ActionTask.concatenate()throwFlowError.noTasksToExecutewhen given an empty array, implementing the fail-fast principle for better error detection. This change completely removes the force cast from Store.swift.Problem
The current implementation has a critical flaw:
This code is actually unreachable because:
concatenate([])callsfatalError(crashes app)The force cast exists to handle a case that cannot occur.
Root Cause Analysis
Empty task arrays indicate one of two scenarios:
Current
fatalErrorapproach:Solution: Throw Typed Error
Changes
1. New Error Case
With comprehensive documentation:
2. Make concatenate() Throwing
3. Remove Force Cast
Benefits
1. Fail Fast
2. Explicit Intent
3. Recoverable Errors
4. Type Safety
ActionResulttypes (not justVoid)5. Better Debugging
Migration Guide
Before
After - Option 1: Explicit Guard
After - Option 2: Propagate Error
Variadic Version (Non-Breaking)
Breaking Changes
ActionTask.concatenate([ActionTask])now throwstryorguardTesting
Technical Details
Why Throw Instead of Returning .none?
tryto mark dangerous operationsWhy This is Safe:
The invariant is enforced at construction time:
concatenate([])throws before creating.concatenated.concatenatedalways contains ≥1 taskflattenConcatenated()preserves this propertypreconditiondocuments and verifies this invariant🤖 Generated with Claude Code