Swift Digest
SE-0330 | Swift Evolution

Conditionals in Collections

Proposal
SE-0330
Authors
John Holdsworth
Review Manager
Ted Kremenek
Status
Returned for revision

01 何が問題だったのか

配列や辞書のリテラルには、要素を直接並べる以外の記法がなく、ビルド構成やプラットフォームによって要素を出し分けたい場合に不便でした。たとえば Linux のときだけ 2 を含めた配列がほしい、DEBUG ビルドのときだけ特定のキーを辞書に含めたい、といったケースです。

これらを実現するには、一度変数を定義してから append したり、条件ごとに異なるリテラルを書き分けたりする必要があり、リテラル本来の見通しの良さが損なわれていました。Swift のソースコードでは関数本体や型宣言などで #if による条件付きコンパイルを自由に使えるにもかかわらず、コレクションリテラルの内側では使えないという不整合もありました。

代表的なユースケースのひとつは、XCTest の Swift 版でテストの一覧を条件付きで構築する場面です。プラットフォームや Swift バージョンごとに有効なテストが異なる場合に、テスト配列そのものをリテラルで定義したいという需要がありました。

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

配列リテラルと辞書リテラルの内部で、要素(または要素の並び)を囲む形で #if / #elseif / #else / #endif を書けるようにします。アクティブな分岐に含まれる要素だけが、最終的なコレクションの要素として残ります。

let array = [
    1,
    #if os(Linux)
    2,
    #endif
    3,
]

この例では、Linux 上でビルドした場合は [1, 2, 3]、それ以外では [1, 3] になります。

辞書リテラルでも同様に書けます。#if はネストも可能です。

let dictionary = [
    #if DEBUG
    "a": 1,
    #if swift(>=5.0)
    "b": 2,
    #endif
    #endif
    "c": 3,
]

末尾カンマの扱い

通常、コレクションリテラルの最後の要素に付く末尾カンマは省略可能ですが、#if の直前や #if ブロックの内部にある「条件分岐の末端要素」については、末尾カンマを省略できません。分岐が非アクティブになったときに、残る要素列が文法的に正しくつながるようにするための制約です。上記の例で、2,"b": 2, の末尾にカンマが付いているのはこのためです。

影響範囲

これは純粋な構文上の追加で、生成されるコレクションは従来通りの配列・辞書です。ただし、どの要素が含まれるかによってリテラル全体の型推論結果が変わり得る点には注意が必要です(たとえば、ある分岐にだけ Double が含まれると、コレクションの要素型が変わる可能性があります)。

なお、本提案はレビューの結果 Returned for revision となっており、現時点では提案された形のままで Swift に取り込まれているわけではありません。