実践Swiftコンパイラ #swtws

本エントリは [Swift Tweets 2017 Summer](https://swift-tweets.github.io/2017-summer) でのツイート[^swtws]をまとめ、加筆修正したものです。内容は 2017/07/22 時点でのものであることにご注意ください。

[^swtws]: 実際のツイートは[2017/7/22 #swtws Swift Tweets 2017 Summer - Togetterまとめ](https://togetter.com/li/1132511) をご覧ください。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">それでは、『実践Swiftコンパイラ』というタイトルで発表します。よろしくお願いします。 <a href="https://twitter.com/hashtag/swtws?src=hash">#swtws</a> <a href="https://t.co/Lx6pbZlLfO">pic.twitter.com/Lx6pbZlLfO</a></p>&mdash; Rintaro Ishizaki (@rintaro) <a href="https://twitter.com/rintaro/status/888737727780343808">July 22, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


Swift がオープンソース化されてしばらく経ちます。コンパイラにコントリビュートしたくても、どこから手をつけていいのやらと思ってませんか？
コンパイラのコードを修正していくときに、具体的に何を手がかりにすれば良いのか、実際のバグを修正しながらご紹介します。


僕がコントリビュート始めたのは2016年の3月です。これが僕の初めてのPR。
日付に注目ください。完全に try! Swift 2016 きっかけでした。
https://github.com/apple/swift/pull/1543

try! Swift のプレゼンテーション [『Dive into Swift Ecosystem』](https://news.realm.io/news/tryswift-syo-ikeda-swift-ecosystem/) や [『Contributing to Open Source Swift』](https://news.realm.io/jp/news/tryswift-jesse-squires-contributing-open-source-swift/) に影響されまくり、 Jesse に Q&A セッションで「どう思います？」って聞いたら「PR投げちゃいなよ！」って言われ、翌日には PR 出してました。


Linux上でのコンパイル警告つぶすだけの簡単な PR でしたが、サクッとマージされ、一人で「ぉおおー」ってなってました。

というわけで、「ぉおおー」なってみたい人のために、実際どんな感じで修正していくのか説明していきます。
ちなみに C++ 経験なくてもなんとかなります。実際のところ、僕が10行以上の C++ コード書いたのはこのプロジェクトが始めてです！



## 1⃣ 環境構築

省略しますが、 別記事に環境構築方法をアップしているので、よかったら見てください。初回ビルドまで済ませば、この内容を実際に試せます。

[『Swiftコンパイラ開発環境構築』](http://qiita.com/rintaro/items/2047a9b88d9249459d9a)

## 2⃣ 何をするのか？

コンパイラの実コードに手を付ける修正で、わりと初心者向けのジャンルに「診断メッセージの改善」っていうのがあります。 DiagnosticQoI とか単に QoI[^qoi] って呼ばれてます。

同テーマのIssue:
https://bugs.swift.org/issues/?jql=labels%20%3D%20%22DiagnosticsQoI%22
同テーマのマージ済みプルリクエスト:
https://github.com/apple/swift/pulls?q=is%3Apr+QoI+is%3Amerged

[^qoi]: Quality of Implementation の略で、診断に限らない用語だと思うのですが、なぜかこの文脈以外では使われていないです。


例えば、インタンスに `.init` を呼ぶと発生するエラーがあります。この [Fix] ボタンを押すと、

<img width="518" alt="1_error.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/6541e459-18c4-7bac-6ada-22c725f54e82.png">

こうならなければいけないのに・・・

<img width="518" alt="2_fixed.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/1ca4ddd4-1e93-2be4-e574-35b58dbb77c5.png">


実際にはこんなことになってしまいます。 `init` が `type(of: )` で囲まれてしまっていますね。

<img width="518" alt="3_actual_result.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/5d954e74-499b-cd90-a9d8-20d8e842d46b.png">



実はこういうエラーは、メッセージだけではなく `[Fix]` の内容も Swift のコンパイラが出しているものなのです。つまり私たちが直せるものです！これを修正し、改善するのが `DiagnosticsQoI` です。

## 3⃣ 修正！

まずはこのエラーがどこで発行されているかを調べます。
エラーメッセージで grep してみましょう!

```shell-session
$ git grep "is a member of the type"
```

[<img width="627" alt="code_errordef.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/48c7a2e5-49b7-108b-fe1b-4e4e65f89ea0.png">](https://github.com/apple/swift/blob/3f308b7/include/swift/AST/DiagnosticsSema.def#L790-L792)


一行上にエラーの ID っぽいものが見つかったので、それをまた grep!

```
$ git grep "init_not_instance_member"
```

<img width="670" alt="4_grep_error_id.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/c26a400b-afd7-d816-7d67-12a9ad6d0fa8.png">

[<img width="684" alt="code_csdiag.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/b794c6ef-f4c9-cfc5-212c-744014d571c9.png">](https://github.com/apple/swift/blob/3f308b7/lib/Sema/CSDiag.cpp#L8445-L8452)

見つかりました。 何も知らなくても、なにやら `fixItRng` を `"type(of: "` と `")"` で囲んでいるというのくらいは読み取れると思います。

ほかにも:

- `fixItRng` は `SourceRange` 型で、ソース上の範囲を示している
- 現在の挙動から、これは `foo.init` の `init` の部分の範囲を示している

というのもなんとなくわかると思います。そこまでわかったら周りのソース見てみましょう。

- foo の部分はどの変数に入っているのかな？ → `baseExpr` っていうのがある！
- どうやって そのソース範囲を取得するのかな？ → `getSourceRange()` っぽい！

という感じでなんとなく分かることも多いです。

結果こんな感じになりました。一行修正しただけですね。

[<img width="985" alt="5_fixedcode.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/94ab4598-e68b-6447-11a7-0388a29fceef.png">](https://github.com/rintaro/swift/commit/9bae27052aa616902a52a9cedb4258d17b3652a1)

## 4⃣ テスト！

```shell-session
$ utils/build-script -Rt
```

でビルドとテストを実行して、何が起こるか見てみましょう。

<img width="1343" alt="6_test_error.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/a160eb09-e735-a61c-3c5e-13a71485dfe2.png">

エラーになりました。どうも元々のテストケースが間違っていたみたいです😢
ということは、修正だけすればOKですね。テストを追加する必要はなさそうです。
テストについては、 別エントリにも書いたので参考にしてください。

[『Swiftコンパイラのテスト環境』](http://qiita.com/rintaro/items/2f84776cf1629150b312#diagnostic-verify)

修正の結果はこんな感じになります。

[<img width="993" alt="7_test_fix.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/82a334db-7dd6-9b0f-ca14-fdbc3ee1a89c.png">](https://github.com/rintaro/swift/commit/7cc591d74b81a78fb3301e78a3b7e0aaa3d5032b)


再テストしたら、通るようになりました。👏

<img width="297" alt="8_test_success.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/61c370eb-f861-dc62-b114-3055d9fe50c1.png">

あとは、PR投げるだけ...

ちょっとまって! 本当にこれでよい？
これを `Optional` の `foo?.init()` に適用したら。。。
こんなことになってしまいます。 `Optional<Foo>` に `init(x:)` は存在しないためです。

<img width="426" alt="9_optional.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/2561c78d-3bb4-e818-846e-dcd1a04bd83f.png">

これに対する対応としては

- `baseExpr` が `Optional` チェーンだったらエラーメッセージのみを出し、 Fix-it を出さない
- `foo.map({ type(of: $0) })?.init(x: 12)` になるようにする

などですが、決めかねます。

こういうときは、少なくとも現状よりはマシになっているのを盾にとりあえずPR出しちゃいましょう。そこに現状の問題点記載しておけば誰かがアドバイスくれるはず。

## 5⃣ PRの出し方

まずは、GitHub の自分のフォークリポジトリに push ですね。
ブランチ作って、コミットして、 push する。まあ普通です。

```shell-session
$ git checkout -b fixit-init-instance
M  lib/Sema/CSDiag.cpp
M  test/expr/postfix/dot/init_ref_delegation.swift
Switched to a new branch 'fixit-init-instance'
$ git add lib/Sema/CSDiag.cpp test/expr/postfix/dot/init_ref_delegation.swift
$ git commit -F - <<EOF
> [QoI] Fix misplaced fix-it for calling initializer on instances
>
> - Fixed the position for fix-it
> - Updated existing test cases
> EOF
[fixit-init-instance 28d1093f35] [QoI] Fix misplaced fix-it for calling initializer on instances
2 files changed, 10 insertions(+), 10 deletions(-)
$ git push private
Enter passphrase for key '/Users/rintaro/.ssh/id_rsa':
Counting objects: 10, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 945 bytes | 0 bytes/s, done.
Total 10 (delta 8), reused 0 (delta 0)
remote: Resolving deltas: 100% (8/8), completed with 8 local objects.
To github.com:rintaro/swift.git
* [new branch]            fixit-init-instance -> fixit-init-instance
$
```

Swift プロジェクトでは、コミットメッセージはほぼ自由で、あまり体裁にこだわる必要はありません。

```
[カテゴリ] お題

...説明...
```

くらいです。簡単なコミットだったら説明なくても大丈夫。


最終的な PR 用ブランチはこんな感じになりました。

https://github.com/apple/swift/compare/master...rintaro:fixit-init-instance

### PR は必ず master ブランチに。

全ての変更はまず master ブランチにマージされます。 Swift4.0 に必要なら master から 必要な部分だけ swift-4.0-branch に cherry-pick されます。なので、 まずは master に PR 出しましょう。

### 適当な英語でも意味さえ通じれば誰も気にしない。

PRのコメントですが、意味さえ通じればなんとかなります。今回のような修正だったらエラー対象のコード書いておくだけでも大丈夫でしょう。

<img width="778" alt="11_pullrequest.png" src="https://qiita-image-store.s3.amazonaws.com/0/55874/c65da6c8-ead6-1ba1-6054-105ac268afad.png">

ちなみに master ブランチでは Label や Assignee などは必要ありません。担当つけなくても誰かが拾って、なんとかしてくれます。

## 6⃣ プルリクエストのテスト

テストはそれなりに時間かかることもあり、コミット権持っているユーザーだけが起動できます。
だれかが swift-ci 様に Please test してくれるのを待ちましょう。

https://github.com/apple/swift/blob/master/docs/ContinuousIntegration.md

---

以上です。地味ですが、こういう Fix-it の修正は利用者の利便性に大きく関わってくるため、ひじょーに重要なのです。
ニッチ過ぎる！などと思わず、見つけたら積極的に直してPR投げてみてください。


コードリーディング中に typo 見つけたらそれだけでも PR 投げてみてください。積極的にマージされます。
https://github.com/apple/swift/pulls?q=is%3Apr+gardening+is%3Aclosed

バグ見つけたけど直し方が分からない場合は是非バグ報告してください。バグ報告も貴重なコントリビュートです！
https://bugs.swift.org

ちなみにここで修正した件も既にIssueトラッカーに上がっています。自分アサインした人がなかなか修正してくれないので止まっちゃってる案件。
https://bugs.swift.org/browse/SR-3245

最後に、コントリビュートへのモチベーションあげるプレゼンテーションを2つご紹介しておきます。

[『Contributing to Swift: From Proposal to Shipped』Russ Bishop](https://news.realm.io/news/slug-russ-bishop-contributing-open-source-swift-proposal/)

[『Contributing to the Swift compiler』 Ayaka Nonaka](https://www.skilled.io/u/swiftsummit/contributing-to-the-swift-compiler)

なにか分からないことがあったら @mono0926 [さん主宰の Discord](https://medium.com/swift-column/discord-ios-20d586e373c0) にいるので、遠慮無く質問ください。


**try! Swift compiler**

---