| name | swift-concurrency-review |
| description | Review Swift 6 strict-concurrency and SwiftUI code for idiom and build-breaking issues - non-Sendable across actor boundaries, @MainActor witness vs nonisolated protocol requirements, Combine/ObservableObject usage, force-unwraps, #Predicate macro limits, the 6.3.x Binding IRGen crash, missing #if os() guards, and unsafe escape hatches. Reports file:line with the fix and the why. |
| argument-hint | [path or files; default = changed files vs git] |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
| disable-model-invocation | true |
Swift 6 + SwiftUI Concurrency Review
You review Swift source for Swift 6 strict-concurrency correctness and SwiftUI idiom on modern iOS / macOS. The focus is the class of issues the compiler will eventually reject (or that crash swift-frontend, or that pass lint but break at runtime), plus the architectural patterns that keep @Observable services and SwiftUI views clean.
Scope discipline: this is not a general bug hunt or a style cleanup. Hand general correctness to /code-review and reuse/simplification to /simplify. Stay on the Swift-6 / SwiftUI concerns below so the review stays sharp and non-duplicative.
What to review
Default to the changed Swift files (git diff --name-only + staged + untracked *.swift). If $ARGUMENTS names a path or files, review those. If a KB exists at docs/kb/conventions/swift-6-patterns.md (or similar), read it first - the project may have its own codified rules that supersede the generic ones here.
Review dimensions
For each, report file:line, the issue, the concrete fix, and a one-line why. Cite the compiler diagnostic text where it helps the user recognize it.
1. Sendability across isolation boundaries
- A type sent across an actor boundary (an endpoint
body, a value captured into a detached Task, anything an actor method returns) must be Sendable. Endpoint bodies that cross the boundary need (any Encodable & Sendable)?, not bare any Encodable.
- Domain models that drop
Sendable (e.g. by adding a non-Sendable class field) break their Codable & Equatable & Sendable contract. Flag a new stored property that is a non-Sendable reference type.
CFString and most CoreFoundation types are not Sendable. A struct holding kSec… constants (a keychain wrapper) cannot conform to Sendable; the right move is to drop the conformance when the only consumer is @MainActor, not @preconcurrency import Security.
2. Actor isolation mismatches
@MainActor witness vs nonisolated protocol requirement. A @MainActor type (including any static func/static let on a SwiftUI.View, which is @MainActor by default) cannot satisfy a non-isolated protocol requirement: "main actor-isolated instance method cannot be used to satisfy nonisolated requirement." Flag in-memory test fakes declared @MainActor that conform to a non-isolated seam - they must be a plain final class.
- Static helper on a
View type read from a nonisolated context (e.g. a plain XCTestCase) warns under Swift 6. If the helper is pure math/string work, mark both the func and the static lets it reads nonisolated.
nonisolated init() for default-param evaluation. A @MainActor class used as another @MainActor initializer's default-param value triggers "Call to main actor-isolated initializer in a synchronous nonisolated context." Fix: mark the producer's init() nonisolated when it only sets nonisolated-safe stored properties.
extension MainActorClass: Identifiable crosses isolation ("conformance crosses into main actor-isolated code and can cause data races"). Use a plain Sendable struct as the sheet(item:) payload instead.
3. Banned legacy patterns (project rules on modern stacks)
import Combine, ObservableObject, @Published - replace with @Observable + @State/@Environment. Flag any reintroduction.
DispatchQueue.main.async to hop to the UI - invisible to the concurrency checker; use Task { @MainActor in … } or await MainActor.run.
@unchecked Sendable, nonisolated(unsafe), MainActor.assumeIsolated without a one-line justification comment naming the invariant that makes it safe. These are escape hatches, not defaults.
4. Force-unwraps and unsafe boundaries
URL(string: "…")! and other force-unwraps (SwiftLint force_unwrapping). Replace static URL literals with a guard let … else { preconditionFailure(…) } helper that crashes loudly if the literal rots.
try! on ModelContainer(...) and similar - prefer do/catch + preconditionFailure("…: \(error)") so crash logs name the failure.
- Boundary input (
[String: Any], raw JSON) leaking past the networking layer instead of being decoded into typed models immediately.
5. SwiftData #Predicate macro limits
#Predicate rejects Optional<Date> nil-coalescing combined with other clauses ("cannot convert … to closure result type 'any StandardPredicateExpression'"). Narrow the predicate to clauses the macro accepts and finish the comparison in Swift on the fetch result.
#Predicate rejects member access without an explicit base ("Member access without an explicit base … from macro 'Predicate'"). Write Date.distantFuture, not .distantFuture.
- Upserts that insert
Cached.from(apiView) over an existing #Unique row orphan @Relationship links - mutate the existing row in place instead.
- A "never touch unread/starred" protection rule must be encoded in the predicate, not implied by the primary AND-chain, or protected rows get deleted when the implication does not hold.
6. Toolchain crash hazards
Binding(get: instanceMethod, set: instanceMethod) referencing @MainActor methods crashes swift-frontend on Swift 6.3.x (report_fatal_error in SyncCallEmission::setArgs). Lint passes; only xcodebuild shows it. Fix: closure literals Binding(get: { … }, set: { newValue in … }).
7. Cross-platform SwiftUI guards
- iOS-only view modifiers on a file that also compiles for macOS must be wrapped in
#if os(iOS) at the smallest scope: .keyboardType, .textInputAutocapitalization, .navigationBarTitleDisplayMode, .tabViewBottomAccessory. (.textContentType, .autocorrectionDisabled, .scrollDismissesKeyboard are cross-platform - do not over-guard.)
.windowResizability(.contentMinSize) does nothing without a paired .frame(minWidth:minHeight:), both under #if os(macOS).
import UIKit on a cross-platform file belongs inside #if os(iOS).
8. SwiftUI architecture
- Views importing from sibling views; views doing I/O or orchestrating other views (views should be leaves consuming injected state and emitting intents).
- Hardcoded colors/hex in views instead of theme tokens via
@Environment.
@State private var x = SomeService() expected to be shared across services - per-property @State initializers can diverge; construct shared services once and inject.
Output
Swift 6 / SwiftUI Review — 4 files
App/Services/AuthService.swift
:31 Force-unwrapped static URL → guard let url = URL(string: literal) else { preconditionFailure(…) }
Why: force_unwrapping fires under --strict and crashes opaquely if the literal rots.
:77 `@MainActor` fake conforms to non-isolated SessionTokenStoring → make it a plain `final class`
Why: a main-actor witness can't satisfy a nonisolated requirement; won't compile under Swift 6.
App/Views/ReaderView.swift
:120 `.keyboardType(.URL)` on a file that compiles for macOS → wrap in #if os(iOS)
Why: the modifier is unavailable on macOS; the file still type-checks for the Mac SDK.
Clean: App/Models/Article.swift, App/App.swift
Summary: 3 findings across 2 files. Run /swift-verify to confirm against the compiler.
This is a report-only review. Do not edit files. Where a finding is build-breaking, say so and recommend /swift-verify to confirm against xcodebuild (the authority over stale SourceKit diagnostics).