Remove final support in protocol extensions
01 何が問題だったのか
プロトコルのエクステンションで宣言された関数に final キーワードを書けてしまうものの、その final は何の意味も持たない、という状況を整理するProposalです。
プロトコルエクステンションの関数は常に静的ディスパッチ
プロトコルのエクステンションに書かれた関数は、そもそもオーバーライドされることがなく、呼び出しは常に直接(静的)ディスパッチになります。クラスのメソッドにおける final(サブクラスによるオーバーライドを禁止する)のような役割を担う余地がありません。
protocol P {}
extension P {
final func greet() { // final は何の効果もない
print("Hello")
}
}
それなのに final が書けてしまい、しかも警告もエラーも出ない
にもかかわらず、当時のコンパイラはプロトコルエクステンション内の関数に付けた final を受け入れ、ディスパッチ挙動を変えることもなければ、警告やエラーも出しませんでした。読み手からは「わざわざ final が付いているのだから何か意味があるのだろう」と誤解されかねず、ノイズの多いコードを招きます。
この挙動は歴史的な経緯によるものでした。元々 Swift では、プロトコルエクステンションのメソッドに動的ディスパッチが行われないことを明示するために final を要求していた時期があり、その後、プロトコルエクステンションのメソッドがプロトコル要件を満たせるようになった段階で、final を書くことが逆に意味を取りづらくする要因になっていた、という経緯です。そうした背景があっても現在の言語仕様では final を書く意味が無くなっているため、構造体や列挙型のエクステンションの関数に final を付けられない(エラーになる)のと同様に、プロトコルエクステンションでも禁止するのが自然だと再評価されました。
02 どのように解決されるのか
プロトコルエクステンション内の関数宣言に final を付けることをコンパイラが診断するようにします。これは、構造体や列挙型のエクステンションで final が禁止されているのと同じ扱いです。
final を書くとコンパイルエラー
Swift 4 モードでは、プロトコルエクステンションの関数に付けた final はコンパイルエラーになります。該当する final は単に取り除くだけで意味は変わりません。
protocol P {}
extension P {
final func greet() { // error: only classes and class members may be marked with 'final'
print("Hello")
}
}
修正後は次のように書きます。
protocol P {}
extension P {
func greet() {
print("Hello")
}
}
Swift 3 互換モードでは警告
既存コードへの影響を和らげるため、Swift 3 互換モード(-swift-version 3)ではエラーではなく警告が出ます。あわせてコンパイラがソースマイグレータ向けの fix-it を提供するため、余分な final を自動的に取り除けます。
利用者として知っておくべきこと
- プロトコルエクステンションのメソッドはもともと常に静的ディスパッチで呼ばれ、
finalを書いても書かなくても挙動は変わりません。このProposalはあくまでノイズとなっていたキーワードを禁止するものです。 - クラス本体やクラスのエクステンション上のメソッドに付ける
final(サブクラスでのオーバーライド禁止)の意味は従来どおりで、このProposalの影響を受けません。 - Swift 4 以降で既存コードを動かす際に
final付きの宣言があった場合は、単にfinalを削除してください。