Xcode 27.0に含まれるSwift 6.4で、throwする式をkey pathのsubscript引数へ直接書くと、コンパイラが「Error! Found a leak due to a consuming post-dominance failure!」を出して停止することがあります。本記事では、swift-case-pathsやThe Composable Architecture(TCA)で使われるAnyCasePathを通るkey pathを中心に、原因、発生条件、現時点で安全な回避方法、修正版の確認方法を整理します。
表示されるエラー
Error! Found a leak due to a consuming post-dominance failure!
Value: %157 = keypath $KeyPath<Root.Action.AllCasePaths, AnyCasePath<...>>
Post Dominating Failure Blocks:
bb4
Found ownership error?!
<unknown>:0: error: fatal error encountered during compilation
...
While running pass #0 SILModuleTransform "SILGenCleanup".
通常のSwiftソースの型エラーではなく、Swiftコンパイラ内部の所有権検証が失敗したときのクラッシュログです。Issueの最小例では、テストターゲットのコンパイル中にSILGenCleanupで停止します。
原因
Swift 6.4のSIL生成処理にある回帰が原因です。key pathリテラルを組み立てている途中でsubscript引数の式がthrowすると、例外経路で部分的に生成されたkey path値を正しく破棄できず、所有権検証がリークとして検出します。
公式Issueでは、@CasePathable形式のAllCasePathsアクセサーとAnyCasePathを通り、subscript引数にthrowing expressionを直接指定した場合に再現しています。Swift Testingの#require(...)で最初に発見されましたが、通常のthrows関数でも再現するため、マクロだけの問題ではありません。
発生条件と対象バージョン
- Xcode 27.0(build 27A266a)
- Apple Swift 6.4(
swiftlang-6.4.0.34.1) - key pathが
AllCasePaths/AnyCasePath形式のアクセサーを通る - key pathのsubscript引数に、失敗してthrowする可能性のある式を直接書く
- 報告時の構成例:swift-composable-architecture 1.26.2、swift-case-paths 1.10.0
比較結果:同じコードはSwift 6.3.3/Xcode 26.6ではコンパイルできたと報告されています。したがって、アプリ側のロジック変更だけで発生したとは限らず、Swift 6.4への更新がきっかけになる回帰です。
問題が起きるコード例
func firstID(in state: Root.State) throws -> StackElementID {
guard let id = state.path.ids.first else {
throw MissingID()
}
return id
}
try await store.receive(
\.path[id: firstID(in: store.state)].popped
)
firstID(in:)がkey pathのsubscript内で実行され、throw経路を持つことがポイントです。
推奨する回避手順
- throwする式をkey pathの外で先に評価します。
- 成功した値をローカル定数へ保存します。
- その定数だけをsubscript引数に渡してkey pathを構築します。
- Clean Build Folder後に、問題のテストターゲットを再ビルドします。
let id = try firstID(in: store.state)
try await store.receive(
\.path[id: id].popped
)
公式Issueで報告された回避方法です。throwがkey path生成より前に完了するため、コンパイラが問題の例外経路を生成しなくなります。
Swift Testingの#requireを使う場合
#requireの結果をsubscriptへ直接埋め込まず、同様に一度ローカル変数へ取り出してください。
let id = try #require(store.state.path.ids.first)
try await store.receive(\.path[id: id].popped)
#requireを削除する必要はありません。重要なのは、throwing expressionをkey path内部で評価しないことです。
代替策
- Swift 6.3.3/Xcode 26.6へ一時的に戻す:Issueの報告環境では旧版で成功しています。ただし、チームやCIのツールチェーンを揃えてください。
- key pathを使わないAPIへ分離する:可能であれば、値を先に解決してから通常のaction比較や明示的なcase matchingを行います。
- 依存ライブラリの更新を確認する:ライブラリ側で回避実装が提供される可能性はありますが、コンパイラ本体の修正とは区別してください。
修正状況
Swift Issue #92351は2026年9月16日に登録され、確認時点ではOpenです。公式に修正版として特定されたSwiftまたはXcodeのバージョンはありません。そのため、「Xcodeを最新版へ更新すれば必ず直る」とは断定できません。Issueの状態とXcodeリリースノートで修正収録を確認してから回避コードを戻してください。
直ったことを確認する方法
xcodebuild -versionでXcodeのbuild番号を記録します。xcrun swift --versionでSwiftのバージョンを記録します。- throwする処理をkey path外へ移した状態でClean Build Folderを実行します。
- XcodeとCIの両方で対象テストを実行します。
- ログから
SILGenCleanup、post-dominance failure、Found ownership error?!が消えたことを確認します。
xcodebuild -version
xcrun swift --version
swift test
注意点
- DerivedDataの削除だけでは、同じソースと同じSwift 6.4コンパイラを使う限り再発する可能性があります。
- 代表ログ末尾の
Found ownership error?!だけで判断すると、別のSwiftコンパイラ不具合と混同します。直前のconsuming post-dominance failureとkey path値を確認してください。 - 純粋な標準ライブラリだけの似たsubscriptが必ず再現するわけではありません。Issueでは
AllCasePaths/AnyCasePathを通る構造が重要な条件です。 - 回避コードには、なぜ式を外へ移したのかをコメントしておくと、将来のリファクタリングで再発しにくくなります。
まとめ
「Error! Found a leak due to a consuming post-dominance failure!」は、Swift 6.4でthrowing expressionをAnyCasePath系key pathのsubscriptへ直接書いたときに発生するコンパイラ回帰です。throwする式を先に評価し、得られた値だけをkey pathへ渡すのが、公式Issueで確認された実用的な回避策です。修正版はまだ公式に特定されていないため、IssueとXcodeリリースノートを確認しながら運用してください。
