Precise Control Flags over Compiler Warnings
01 何が問題だったのか
Swift コンパイラには、警告の扱いを制御するオプションとして従来次の3つが用意されていました。
-warnings-as-errors: すべての警告をエラーに昇格させます-no-warnings-as-errors: 警告のエラー昇格をキャンセルします-suppress-warnings: すべての警告の出力を抑制します
しかし、これらはいずれも「すべての警告」を対象とする粗い粒度の制御しかできず、個々の警告を選んで扱いを変えることはできませんでした。そのため、-warnings-as-errors を有効にしたいプロジェクトでも、特定の種類の警告(例えば API の deprecation 警告)が出るだけで採用を断念せざるを得なかったり、コンパイラや SDK を新しいバージョンに上げると新たに出始める警告をすべて解消しない限りビルドが通らなくなったりする問題がありました。
特に顕著なのが @available(..., deprecated: ...) による非推奨警告です。ライブラリ側が新しい SDK で API を非推奨化した途端、それを使っている利用側のコードで警告が出るようになり、-warnings-as-errors 構成ではビルドが壊れてしまいます。この種の警告だけを警告のまま残し、それ以外はエラー扱いにしたい、といった現実的なニーズに既存のフラグでは応えられませんでした。
02 どのように解決されるのか
診断(diagnostic)を診断グループ(diagnostic group)という単位で分類し、そのグループを指定して警告の扱いを制御できる新しいコンパイラオプションを導入します。
-Werror <group>: 指定したグループに属する警告をエラーに昇格させます-Wwarning <group>: 指定したグループに属する警告を、たとえ抑制されていたりエラーに昇格されていたりしても警告のまま出力させます
<group> には診断グループを表す文字列(例: Deprecated, DeprecatedDeclaration, Concurrency など)を渡します。
あわせて、警告メッセージの末尾に所属する診断グループ名を表示する -print-diagnostic-groups オプションも追加されます。たとえば次のように表示されます。
main.swift:33:1: warning: 'f()' is deprecated [#DeprecatedDeclaration]
このグループ名をそのまま -Werror / -Wwarning の引数として使えます。
診断グループ
診断グループはコンパイラ内部の診断 ID とは別に用意された、ユーザー向けに安定した識別子です。コンパイラの実装変更に伴って内部の診断 ID が変わっても、グループ名は利用者から見て不変であることが保たれます。
グループは非循環グラフを成します。具体的には次のような構造になっています。
- 個々の警告・エラーはただ 1 つの診断グループに所属します
- 診断グループは他の診断グループを任意個含むことができます(例:
DeprecatedはDeprecatedDeclarationやUnsafeGlobalActorDeprecatedを含みます) - 1 つの診断グループは複数の上位グループに属することができます(例:
UnsafeGlobalActorDeprecatedはDeprecatedとConcurrencyの双方に属します)
グループの内部構造は将来変更される可能性がありますが、あるグループが(直接的・推移的に)含む診断の集合が狭くなることはないことが保証されます。たとえば既存の DeprecatedDeclaration 警告がより特化した DeprecatedDeclarationSameModule に分割されるような場合でも、新しいグループは DeprecatedDeclaration の下位として追加され、-Werror DeprecatedDeclaration を指定していたユーザーの挙動は以前と変わりません。
「後勝ち」の評価ルール
新しいフラグと既存の -warnings-as-errors / -no-warnings-as-errors は、すべてコマンドラインに現れる順にまとめて処理され、同じ警告に対して複数のフラグが適用される場合は最後のものが勝つ(last one wins)ルールで最終的な扱いが決まります。
-warnings-as-errors -Wwarning Deprecated
この指定では、Deprecated グループの警告は警告のまま残り、それ以外のすべての警告がエラーに昇格します。順序を逆にした
-Wwarning Deprecated -warnings-as-errors
では、最後に書かれた -warnings-as-errors が勝つため、Deprecated を含むすべての警告がエラーになります。
複数の上位グループに属するグループがある場合、順序の影響が直観に反することもあるため注意が必要です。たとえば UnsafeGlobalActorDeprecated は Deprecated と Concurrency の両方に属するため、
-Wwarning Deprecated -Werror Concurrency: 後に来るConcurrencyが勝ち、UnsafeGlobalActorDeprecatedはエラーになります-Werror Concurrency -Wwarning Deprecated: 後に来るDeprecatedが勝ち、UnsafeGlobalActorDeprecatedは警告のままになります
より細かい指定例として、
-Werror Deprecated -Wwarning DeprecatedDeclaration
では、Deprecated グループの警告は基本的にエラーになりますが、後から上書きされる DeprecatedDeclaration グループの警告だけは警告のまま残ります。
-suppress-warnings との関係
既存の -suppress-warnings は、今回の統一モデルにはあえて組み込まれず、次のように扱われます。
-suppress-warningsと-Wwarning/-Werrorを同時に指定することは禁止され、コンパイラがエラーを出します-suppress-warningsと-no-warnings-as-errors(あるいは-warnings-as-errors -no-warnings-as-errors)の併用はこれまでどおり許容されます-suppress-warningsと-no-warnings-as-errorsを併用した場合、順序にかかわらず常に-suppress-warningsが優先されます
-print-diagnostic-groups と -debug-diagnostic-names
コンパイラには従来から -debug-diagnostic-names というオプションがあり、これも警告メッセージの末尾に角括弧付きで識別子を表示します。ただし、こちらが出すのはコンパイラ内部の診断 ID であって診断グループ名ではありません。たとえば次のコードを考えます。
@available(iOS, deprecated: 10.0, renamed: "newFunction")
func oldFunction() { ... }
oldFunction()
-debug-diagnostic-names付きだと[#RenamedDeprecatedDeclaration](内部診断 ID)が表示されます。この文字列は-Werror/-Wwarningの引数としては使えません-print-diagnostic-groups付きだと[#DeprecatedDeclaration](診断グループ)が表示されます。こちらは新しいフラグにそのまま渡せます
内部診断 ID と診断グループ名は一致することも多いですが、常に一致するとは限りません。混同を避けるため、-debug-diagnostic-names と -print-diagnostic-groups を同時に指定することはできません。
Future Directions
本提案で導入される診断グループは、将来的に言語側からの制御(C++ の #pragma diagnostic や属性に相当するもの)や、SwiftPM の SwiftSetting 経由での設定に拡張されていく可能性があります。ただし、これらはあくまで今後の展望であり、まずは診断グループを運用した経験を積んだうえで検討されるものです。