Skip to content

Latest commit

 

History

History
161 lines (109 loc) · 18.8 KB

Swift 将来追加予定の言語改善への段階的な適用.md

File metadata and controls

161 lines (109 loc) · 18.8 KB

Swift将来追加予定の言語改善への段階的な適用

概要

Swift6では、以前の言語モード(Swift4.xおよびSwift5.x)ではデフォルトで有効にできなかったソース互換性への影響がたくさんある、言語の多くの改善が積み上がっている。これらの改善は、Swift6言語モードの背後にあるSwiftコンパイラに既に実装されているが、Swift6が言語モードとして利用可能になるまでユーザはアクセスできない。これらの改善を、より早く利用できるようにすることを検討すべき理由がいくつかある。

  • 開発者は、Swift6が利用可能になるまで待たずに、これらの改善によるメリットをすぐに得たいと考えている
  • これらの変更をSwift6より前に開発者が利用できることで、より多くの知見が得られ、必要に応じてSwift6をさらに改善することができる
  • Swift6で変更されるものが積み上がると、モジュールによっては移行が面倒になる可能性があり、Swift4.x/5.xの内にこれらの言語の変更を1つずつ適用することで、移行をスムーズにすることができる

いくつかの提案では、移行に有利なソリューションを提供するオーダーメイドのソリューションがすでに導入されている。

  • SE-0337は、Swift4.x/5.xでSendable関連のチェックの警告を有効にするために-warn-concurrencyフラグを追加した
  • SE-0354-enable-bare-slash-regexフラグを追加して/.../をそのまま使った正規表現構文を有効にします。
  • プロポーザルの一部ではなかったが、SE-0335の議論には、すべての存在型でanyを必須にするコンパイラフラグの要件が含まれていた。

これらはすべて、既存のSwift4.x/5.xコードを、来たるSwift6での改善に事前に組み入れるという同じ趣向を持っている。

このプロポーザルでは、ソース互換性の理由でSwift6まで適用されない機能の断片を意図的かつ明示的に導入する。これは、Swift4.x/5.xコードベースでそのメリットを享受し、Swift6言語モードへの移行をスムーズにするために、Swift6の機能を1つずつ段階的に採用するための、直接的なパスを構築する。開発者は、新しいコンパイラフラグ-enable-upcoming-feature Xを使用して、そのモジュールで、Xという名前の特定の機能を有効にすることができる(複数可)。そして、次のメジャー言語バージョンに移行すると、Xはその言語バージョンに含まれているため、このコンパイラフラグは無効になる。このように、upcoming feature flagは、次の主要なSwift言語バージョンまでしか有効化されず、その後消去されるため、言語を互換性のない独自の言語バージョンにフォークすることはない。

言語バージョンとツールバージョン

「Swiftバージョン」には 2 つの関連する異なる種類のバージョンがあり、便宜上一緒に扱うことがよくあるが、このプロポーザルでは両方のバージョンがそれぞれ別々に関係している。

  • Swiftツールバージョン: コンパイラ自体のバージョン番号。例えば、Swift5.6コンパイラは 2022 年 3 月に導入された
  • Swift言語バージョン: ソース互換性を提供する言語バージョン。例えば、Swiftバージョン5は、Swiftツールバージョン5.6でサポートされている最新の言語バージョン

Swiftツールは、複数のSwift言語バージョンをサポートしている。最近のすべてのバージョン(Swiftツール5.0以降)は、複数のSwift言語バージョンをサポートしており、現在は4、4.2、および5の3つのみ。ツールが進化しても、一つの言語バージョン内でソース互換性のない変更を行わないようにしている。これは、Swiftエボリューションのプロセスにも反映されている。既存のソースコードセマンティクスを変更したり、無効にしたりするプロポーザルは、通常、既存の言語モードでは受け入れられない。多くのプロポーザルは、既存の言語モード内でSwift言語を拡張する。例えば、async/awaitは、Swiftツール5.5で利用できるようになったが、すべての言語バージョン(4、4.2、5)でも利用できる。

このプロポーザルには、新しいSwift言語バージョン(例:6)まで導入を待機しているソース互換性のない変更が含まれる。Swiftツール6.0は、Swift6を正式に使用できる最初のツールになるだろう。そして、引き続きSwift4、4.2、および5をサポートするだろう。Swift4、4.2、および5。Swiftツール6.0または6.1などを使用するために、コードをSwift6に移行する必要はない。また、Swift6で作成されたコードは、Swift4、4.2、または5で作成されたコードと相互運用できる。

導入バージョン

Swift5.8

内容

新しいコンパイラフラグ-enable-upcoming-feature Xを導入する(Xは有効にする機能の名前)。各プロポーザルでは、Xが何かを明示するため、その機能を有効にする方法は明白になっている。例えば、SE-0274 はConciseMagicFileを使用できるようにしたため、-enable-upcoming-feature ConciseMagicFileを設定すると、セマンティクスの変更を有効にする。もちろん、複数の-enable-upcoming-featureフラグをコンパイラに渡して、複数の機能を有効にすることもできる。

認識できないupcoming featureに関してコンパイラは無視されるだろう。これにより、古いツールは、新しい機能を採用したSwiftコード用の新しいツールと同じコマンドラインを使用できるが、古いツールを引き続き使用するための適切なワークアラウンドがある。古いコンパイラでもコードを適切に解釈できるため、これが可能な場合もあれば、ソースコード内の機能を検出する方法が必要な場合もある。これについては、後のセクションで説明する。

一部の言語バージョンでは、すべてのupcoming featureがデフォルトで有効になっている。-enable-upcoming-feature Xが指定されていて、言語バージョンがデフォルトで機能Xを有効にしている場合、コンパイラはエラーを生成する。こうすることで、いつ機能が利用可能になるかが明確になり、以前の言語バージョンから進化し、機能を少しずつ採用し、その後、新しい言語バージョンに移行すると、プロジェクトとマニフェストがきれいに片付く。

プロポーザルでそれぞれの機能の識別子を定義する

機能識別子を定義する新しいオプションフィールドを使用して、Swiftプロポーザルのテンプレートを修正する。

  • Feature identifier:UpperCamelCaseFeatureName

Swift6まで部分的または全体的に延期している次のプロポーザルを、下記の機能識別子で修正する:

  • SE-0274 "Concise magic file names"(ConciseMagicFile)は、Swift6まで#fileのセマンティクスの変更を延期する。この機能を有効にすると、#file#filePathではなく#fileIDを意味するように変更される
  • SE-0286"Forward-scan matching for trailing closures"(ForwardTrailingClosures)は、Swift6まで、後続クロージャの「後方スキャンマッチング」ルールの削除を延期する。この機能を有効にすると、後方スキャンマッチングルールが削除される
  • SE-0335 "Introduce existential any"(ExistentialAny)は、Swift6まで、すべての存在型にanyを使用する要件を延期する。この機能を有効にすると、すべての存在型にanyが必要になる
  • SE-0337 "Incremental migration to concurrency checking"(StrictConcurrency)は、Swift6までConcurrencyモデルの一部のチェックを延期する(Swift5.xでは、このフラグを使用すると警告を発する)。この機能を有効にすると、完全なConcurrencyチェックを実行する-warn-concurrencyと同等の意味を持つ
  • SE-0352 "Implicitly Opened Existentials"(ImplicitOpenExistentials)は、Swift5.xで十分に機能しているコードのセマンティクスを変更したくなかったため、Swift6で「存在型の暗黙的開示」をより多くのケースに拡張する。この機能を有効にすると、これらの追加のケースで「存在型の暗黙的開示」が実行される
  • SE-0354 "RegexLiterals"(BareSlashRegexLiterals)は、Swift6まで/.../正規表現リテラル構文の導入を延期する。この機能を有効にすることは、-enable-bare-regex-syntaxと同等で、/.../正規表現リテラル構文が利用可能になる。このプロポーザルとSE-0354が同じリリースで導入される場合、このプロポーザルのアプローチをサポートして-enable-bare-regex-syntaxは完全に削除できる

### Swift Package Managerのサポート

SwiftPMのターゲットに、必要なupcoming featureを指定できる必要がある。SwiftSettingのAPIを拡張して、upcoming featureを有効にする。

extensionSwiftSetting {
    public static func enableUpcomingFeature(
        _ name: String,
        _ condition: BuildSettingCondition? = nil
    )->SwiftSetting
}

SwiftPM は、この設定を使用してモジュールをビルドするときに、そこにリストされているupcoming featureを-enable-upcoming-featureフラグを介してコンパイラに渡す。これに依存する他のターゲットは、ビルド時に機能を渡す必要はない。というのも、upcoming featureの影響範囲はモジュールの境界を越えない

新しい機能がコンパイラに追加されるたびにSwiftPMのマニフェスト形式を変更する必要がないように、機能は文字列として提供される。パッケージの作成者は、バージョン管理された新しいマニフェストを作成しなくても、古いツールを引き続きサポートしながら、upcoming featureを追加できる

ソース上での機能の検知

新しい機能を導入する場合、その機能が利用できない古いツールでもコードをコンパイルしたいというのはよくあること。このためには、-enable-upcoming-featureまたは適切な言語バージョンを有効にすることによって、機能が有効になっているかどうかを確認する方法が必要になる。

Swiftの#ifhasFeature(X)チェックを明示的にできるように拡張する必要がある。これは、識別子Xを持つ機能が利用可能な場合は常にtrueと評価される。特定の機能をチェックする必要があるコードは、次のように#if hasFeatureを使用できる。

#if hasFeature(ImplicitOpenExistentials)
    f(aCollectionOfInts)
#else
    f(AnyCollection<Int>(aCollectionOfInts))
#endif

hasFeature(X)チェックは機能の有無を示すが、古いコンパイラ自体は機能が不明であっても#if分岐を解析しようとする。この機能(ImplicitOpenExistentials)は新しい構文を追加しないため問題ないが、構文を追加する他の機能では、さらに何かが必要になる場合がある。hasFeatureSE-0212で導入したcompilerディレクティブと組みわせることができる

#if compiler(>=5.7)&& hasFeature(BareSlashRegexLiterals)
let regex= /.../
#else
let regex= try NSRegularExpression(pattern: "...")
#endif

hasFeature自体はこのプロポーザルより前のツールでは理解されないため、上記のコードには問題がある。そのため、上記のコードはhasFeatureの導入より前のSwiftコンパイラではコンパイルできない。次のようにhasFeatureチェックをネストすることで、この問題を回避することができる(Swift5.7 でhasFeatureが導入されたと仮定)。

#if compiler(>=5.7)
  #if hasFeature(BareSlashRegexLiterals)
  let regex= /.../
  #else
  let regex= #/.../#
  #endif
#else
let regex= try NSRegularExpression(pattern: "...")
#endif

最悪の場合、これには、hasFeatureの導入前のSwiftバージョンで動作する必要があるライブラリのコードと重複してしまうが、コンパイラは処理可能になり、時間の経過とともにその制限はなくなる。

今後のどんなupcoming featureに対しても、#if構文のこの問題を回避するために、左側がcompiler(>=5.7)swift(>=6.0)などの#if本文の解析を不可能にして、右側の項が式全体の結果を決定する必要がない場合、コンパイラは&&または||の右側にある「呼び出し」構文を解釈するべきではない。例えば、将来#if hasAttribute(Y)のようなものを導入した場合、次の式が使用できる。

#if compiler(>=5.8)&& hasAttribute(Sendable)
...
#endif

(hasAttributeをサポートすると想定した)Swift5.8 以降のコンパイラでは、完全な条件が評価されます。それ以前のSwiftコンパイラ(つまり、このプロポーザルをサポートしているがhasAttributeのような新しい機能が存在していないバージョン)では、&&または||の後のコードを式として解析するが、評価されないためコンパイラはこの#if条件をエラーにしない。

実験的な機能の採用

コンパイラの言語機能は、開発時に「実験的」フラグに隠れた状態でステージングされるのが一般的。これは、通常アドホックな方法で行われ、機能が最終的にリリースされる前にフラグを削除する。ただし、この実験的な機能モデルをもっと活用するべき。そこで、機能が開発中の場合は、新しいフラグ-enable-experimental-feature Xまたは対応するSwiftPMenableExperimentalFeatureで有効にできる機能識別子を提供する。

実験的な機能はまだ不安定であると考えられており、リリースされたコンパイラでは利用できないはず。ただし、実験的な機能とupcoming featureを導入する方法を統一することで、同じステージングの仕組みを利用することができる。つまり、機能を有効にし、ソースコードで簡単に試すことができる。機能がサポートされている完全な言語機能に「卒業」した場合、hasFeatureはその機能に対してtrueを返せる。また、機能の一部が次の主要な言語バージョンまで延期された場合、-enable-upcoming-featureも機能上手く働く。

ソース互換性

言語自体については、hasFeatureが唯一の追加であり、ユーザ定義可能な関数がない制約された構文空間(#if)でのみ利用される。したがって、新しいコンパイラが既存の適切な形式のコードをエラーにするという従来の意味でのソース互換性の問題ない。

SwiftPM の場合、enableUpcomingFeatureおよびenableExperimentalFeature関数をSwiftSettingに追加することは、1度、マニフェストファイルフォーマットを分ける必要がある。これらの関数を採用し、enableUpcomingFeatureおよびenableExperimentalFeatureの導入よりも前のバージョンのツールをサポートしたいパッケージは、バージョン管理されたマニフェスト(Package@swift-5.6.swiftなど)を使用して、新しいツールバージョンの機能の導入が可能。enableUpcomingFeatureenableExperimentalFeatureが追加されても、追加の機能を導入するためにマニフェストファイルの別のコピーは必要ない。

参考リンク

Forums

プロポーザルドキュメント