Clarify interaction between comments & operators
01 何が問題だったのか
Swift の演算子は、前後のホワイトスペースの有無によって 前置(prefix) / 後置(postfix) / 中置(infix) のどれとして解釈されるかが決まります。たとえば !foo の ! は前置、foo! の ! は後置、a + b の + は中置、というように、演算子の両側のスペースの付き方がそのまま文法的な役割に結びついています。
問題は、この判定においてコメントがどう扱われるかが一貫していなかったことです。コメントは、ある場面ではホワイトスペースとして扱われ、別の場面では非ホワイトスペースとして扱われ、しかもその扱いが演算子の左右や、コメントの中身(// と /* */ の違い、コメント内のスペースの有無など)によって変わっていました。
具体例
次のコードは、感覚的には if !foo と同じはずですが、当時のコンパイラでは ! が前後ともに非ホワイトスペース(左はコメント、右は foo)と見なされて中置演算子扱いされ、コンパイルエラーになっていました。
if /* comment */!foo { ... }
一方で、次のコードは通っていました。
1 +/* comment */2
これは +/* が一つのトークンとして扱われ、その結果「左右にホワイトスペースがある中置演算子」と判定されるためです。直感的には前のコードが通って後ろのコードは怪しい、と感じる人のほうが多いにもかかわらず、実際の挙動は逆になっていました。
なぜ直したいのか
このように、コメントの扱いが場面ごとにぶれていると、
- 同じ意味のはずのコードが、コメントの書き方次第で通ったり通らなかったりする
- 言語リファレンスでは「コメントはホワイトスペースとして扱う」と説明されているのに、演算子まわりだけその原則が崩れている
という状況が生まれていました。SR-186 などのバグ報告として表面化していたこの問題を、統一的なルールで整理することが求められていました。
02 どのように解決されるのか
演算子の前置 / 後置 / 中置を決める際に、コメントは常にホワイトスペースとして扱う という一律のルールを採用します。// コメントでも /* */ コメントでも、またコメントの中身がどうであっても、この扱いは変わりません。! や ? の特殊なルールについても同様で、コメントをスペースに置き換えても演算子の解釈は変わらない、という一貫した挙動になります。
この整理により、言語リファレンスの一般原則である「コメントはホワイトスペースとして扱われる」が、演算子まわりでも例外なく成立するようになりました。
ルールの効果
コメントをスペースに置き換えたものと同じ解釈になる、というシンプルな原則で考えられます。たとえば次の2つは同じ意味になります。
if/* comment */!foo { ... }
if !foo { ... }
また、次の2つも同じ意味になります(どちらも左右にスペースがある中置 +)。
1 + 2
1 +/* comment */2
従来通らなかったが通るようになる例
/* */!foo
1/**/+ 2
1 /**/+ 2
1 +/**/2
いずれも、コメントをスペースに置き換えれば素直に解釈できる書き方です。
従来通っていたが通らなくなる例
逆に、コメントが演算子のホワイトスペースの「穴埋め」として機能することを当てにしていた書き方はエラーになります。
foo/* */?.description
foo/* */!
1/**/+2
1+/**/2
いずれも、コメントをスペースに置き換えてみると前後のスペースの付き方がアンバランスで、中置 / 前置 / 後置のいずれとしても素直に解釈できない形です。こうしたコードは、コメントを式の外に出すか、スペースの入れ方を整えることで修正できます(fix-it で支援することも想定されていました)。
引き続きエラーになる例・引き続き通る例
コメントの有無にかかわらず前後のスペースが釣り合わないものは、今回のルールでも当然エラーになります。
!/* */foo
1+/* */2
逆に、元々スペースの付き方だけで意味が決まっていた書き方は従来通り通ります。
foo!// comment
1 +/**/ 2
1 +/* */2
影響範囲
影響を受けるのは「演算子のすぐ隣にコメントを置いているコード」に限られ、そうしたコードはそれほど多くないと見込まれていました。修正もスペースを足すかコメントを式の外に出すだけで済むため、移行コストの小さい変更として Swift 3.0 で実装されています。