Swift Digest
SE-0224 | Swift Evolution

Support ‘less than’ operator in compilation conditions

Proposal
SE-0224
Authors
Daniel Martín
Review Manager
Ted Kremenek
Status
Implemented (Swift 5.0)

01 何が問題だったのか

Swift には SE-0020 で導入された #if swift(...) と、その後に追加された #if compiler(...) があり、Swift 言語バージョンやコンパイラバージョンに応じて条件付きコンパイルを行えます。しかし、これらの条件式で使える単項演算子は >= だけで、「このバージョン未満の場合」を表したいときは毎回否定を組み合わせる必要がありました。

否定による記述はミスを招きやすい

たとえば「Swift バージョンが 4.2 未満のときだけコンパイルする」コードは、従来は次のように書くしかありませんでした。

#if !swift(>=4.2)
// Swift バージョンが 4.2 未満のときにコンパイルされる。
#endif

#if !compiler(>=4.2)
// Swift コンパイラバージョンが 4.2 未満のときにコンパイルされる。
#endif

この書き方では、条件全体の意味が先頭の ! 1 文字に依存します。コードレビューで ! を見落とすと意味が逆に読めてしまい、バグの温床になります。また、「4.2 未満」という素直な意図に対して、「4.2 以上ではない」と二重否定を頭の中でほどきながら読まなくてはならず、可読性の面でも負担がありました。

新旧の API を切り替えるライブラリなど、過去のバージョン向けに古い実装を残すケースでは、この種の条件は頻繁に登場します。そうした場所で繰り返し !swift(>=…) を書かされる状況は、Swift の条件付きコンパイルの使い勝手として望ましくないものでした。

02 どのように解決されるのか

#if swift(...)#if compiler(...) の条件式で使える単項演算子として < を追加し、「指定したバージョン未満のとき」をそのまま書けるようにします。

< による条件記述

先ほどの例は、< 演算子を使って次のように書き換えられます。

#if swift(<4.2)
// Swift バージョンが 4.2 未満のときにコンパイルされる。
#endif

#if compiler(<4.2)
// Swift コンパイラバージョンが 4.2 未満のときにコンパイルされる。
#endif

!swift(>=4.2) と等価ですが、先頭の ! に依存せず「4.2 未満」という意図が条件式そのものに現れるため、コードレビューでも読み誤りにくくなります。英語として読んでも “if swift less than 4.2” と素直に対応する形です。

サポートされる演算子は >=< のみ

条件式で使える単項比較演算子は、既存の >= と新たに追加される < の 2 つに限られます。<=> は追加されません。これは、<=> が「今後のリリース」に対する仮定を含んでしまい、パッチリリースを跨ぐような比較が意図せず成立・不成立してしまう恐れがあるためです。たとえば compiler(>4.2) のような条件は、将来の 4.2 系パッチリリースまで含めた挙動を事前に決め打ちすることになり、意図と合わなくなる可能性が高くなります。

そのため、条件として表現できるのは次の 2 通りだけです。

  • >=X.Y: 指定したバージョン以上
  • <X.Y: 指定したバージョン未満

不正な演算子に対する診断メッセージの更新

>= / < 以外の演算子を書いた場合の診断メッセージも、新しい構文を反映した内容に更新されます。従来は次のようなメッセージでした。

unexpected platform condition argument: expected a unary comparison, such as '>=2.2'

これが、< も利用できることが分かる形に改められます。

unexpected platform condition argument: expected a unary comparison '>=' or '<'; for example, '>=2.2' or '<2.2'

この機能は Swift 5.0 で実装され、Swift 5.0 以降のコンパイラであれば #if swift(<X.Y) / #if compiler(<X.Y) をそのまま利用できます。