Swift Digest
SE-0034 | Swift Evolution

Disambiguating Line Control Statements from Debugging Identifiers

Proposal
SE-0034
Authors
Erica Sadun
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift には、ソース中のファイル名や行番号をコンパイラから見て差し替えるための line control statement が用意されていました。コード生成ツールなどが出力する中間ソースで、「このコードは本当はこのファイルのこの行から生成された」という情報を上書きするためのものです。当時の構文は次の通りです。

line-control-statement → #line
line-control-statement → #line line-number file-name

一方で SE-0028 により、ログ出力などで現在のソース位置を取得するための識別子 __LINE__#line にリネームされることが決まりました。これにより #line という同じ綴りが、

  • 式として現れたとき: その行の行番号に展開される識別子
  • 行頭の最初のトークンとして現れたとき: ファイル名と行番号を差し替えるディレクティブ

という二つの意味を持つことになり、曖昧さを解消するために「行頭に書かれた #line だけディレクティブ扱いする」というホワイトスペース依存のルールが導入されました。

行頭かどうかで意味が変わる不自然さ

ホワイトスペースが構文の意味に影響するこの扱いは、Swift の他の機能と整合しません。コアチームもこれを暫定的な回避策と位置づけており、より適切な名前にリネームすることを前提に、SE-0028 が受け入れられていました。Chris Lattner は「名前と構文が決まり次第、ディレクティブをリネームしてホワイトスペース依存の規則を取り除く」ことを宣言しており、本提案はそのフォローアップとして位置づけられています。

ツール向けのごく限定的な機能

line control statement は、自動生成されたソースを扱うツールのために存在する機能で、一般の Swift 利用者が手で書くことはほとんどありません。そのため、構文を極端に一般化する必要はなく、生成が容易で読みやすく、#line との衝突を解消する ことが主な要件でした。

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

line control statement を #line とは別の名前にリネームし、識別子としての #line とディレクティブとしての #line の衝突を解消します。当初のレビューでは #setline が提案されていましたが、採択時には # 系ディレクティブの他の構文(#available / #selector など)と揃える形で、lower camel case の #sourceLocation + 括弧 + ラベル付き引数 の形に修正されました。

新しい構文

#sourceLocation(file: "foo.swift", line: 42)
// ...以降の行は foo.swift の 42 行目から始まっているものとして扱われる

#sourceLocation()
// 差し替えをリセットし、実際のファイル名と行番号に戻す
  • #sourceLocation(file:line:) の形で、それ以降のコードに適用するファイル名と行番号を指定します。
  • 引数なしの #sourceLocation() を書くと、差し替えをリセットして本来のファイル名と行番号に戻ります。
  • line には 1 以上の十進整数、file には静的文字列リテラルを渡します。

命名に lower camel case が採用されたのは、#-namespace の識別子全体について命名規則を整理した結果です。以降の Swift でもこの方針が踏襲されています。

#line との関係

このリネームにより、#line は完全に「式として現在の行番号に展開される識別子」専用となり、SE-0028 で導入されていた「行頭に書かれた #line だけディレクティブ扱いする」というホワイトスペース依存のルールは撤廃されます。結果として、#file / #line / #column / #function といったソース位置系識別子を、どこに書いても一貫した意味で使えるようになりました。

func log(_ message: String,
         file: String = #file,
         line: Int = #line) {
    print("[\(file):\(line)] \(message)")
}

使いどころ

#sourceLocation を手で書く機会はほとんどありません。実際の用途は、別言語や DSL から Swift ソースを生成するツールが、生成結果の各部分を元の入力ファイルの位置にマッピングするために出力するケースです。デバッガやコンパイラのエラーメッセージは、#sourceLocation で指定された位置を元に表示を行います。