PhpStormに実装されてない未来のPHPDocとPSR-5ジェネリクス記法

[PhpStorm 2018.3RC]が11月14日にリリースされ、遠からず正式版がリリースされる見込みです。

<p><ins><strong>2018年11月23日追記:</strong> 正式版としてPhpStorm 2018.3がリリースされました。</ins></p>

[Coming in PhpStorm 2018.3 - PhpStorm]には次期バージョンに登載される見込みの輝かしい機能が並んで居ります。その中で目立つ位置にあるのが<q><strong>PHPDoc Improvements</strong></q>です。

<img width="441" alt="PHPDoc Improvements: PhpStorm adds Support for Intersection Types “&amp;”. If you annotate a variable as Foo&ampBar, then it is of type Foo and of type Bar at the same time. PhpStorm will also suggest members of both classes.  Also, PSR-5 &lt;generic&gt; collection type hints are now supported on the parser level." src="https://qiita-image-store.s3.amazonaws.com/0/4121/2d152f23-0d44-4edb-daf5-8790bd50409d.png">

ここにPSR-5についての言及がありますが、この記事ではこれが具体的に現在どのような状態かを説明します。

<p><ins><strong>2018年11月23日追記:</strong> 正式リリース後の<q>WHAT’S NEW IN PHPSTORM 2018.3</q>からはPSR-5についての記述は削除されました。</ins></p>

<p><ins><strong>2019年1月30日追記:</strong> 「<b>PhpStorm 2018.3でPSR-5ジェネリクス記法はサポートされない</b>」から「<b>PhpStormに実装されてない未来のPHPDocとPSR-5ジェネリクス記法</b>」に改題しました。実装されたらまた変更するのでは。</ins></p>

## PSR-5とPHPの型記法について

[2018年のPHPDoc事情とPSR-5](https://qiita.com/tadsan/items/72b02339d12120ca37d7)に詳しく書いた内容と重複する内容と、そこからアップデートされた情報もあるので先に読んできてください。

## PhpStorm 2018.3

このバージョンでのPHPDocサポートの肝は以下の二点です。

 * 交叉型(Intersection Types)のサポート
 * PSR-5形式のジェネリック型表記の**パーサーレベル**サポート


<blockquote><p>PhpStorm adds <strong>Support for Intersection Types “&amp;”</strong>. If you annotate a variable as <code>Foo&amp;Bar</code>, then it is of type <code>Foo</code> and of type <code>Bar</code> at the same time. PhpStorm will also suggest members of both classes.</p><p>Also, <strong>PSR-5 &lt;generic&gt; collection type hints</strong> are now supported on the parser level.</p></blockquote>

### 交叉型 (Intersection Types)

Intersection Typesは意外と古くからある概念で、「型システム入門[^1]」によれば、初出は実に1978年の論文[^2]だそうです。TypeScriptには[1.6の新機能](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#intersection-types)として[2015年に実装](https://github.com/Microsoft/TypeScript/pull/3622)[^3]されました。

PHPの静的解析ツールであるPHPStanには2017年11月30日にリリースされた0.9の新機能としてリリースされました。その思いは開発者自身による[Union Types vs. Intersection Types – Ondřej Mirtes – Medium](https://medium.com/@ondrejmirtes/union-types-vs-intersection-types-fd44a8eacbb)にある通りです。

具体的には「`A`と`B`の両方を実装した値」を期待することを明記できます。

```php
/**
 * @param \ArrayAccess&\Countable $array_like
 */
```

これで、`$array_like` は `ArrayAccess`と`Countable`の両方を実装した値を期待することを意味します。

この記法は現状PHP本体もPhpStormなどもサポートしてないわけですが、この機能追加の変更によって検査結果が壊れたと訴える[Union vs Intersection issues · Issue #700 · phpstan/phpstan](https://github.com/phpstan/phpstan/issues/700)で、PHPStan作者による、おもしろい(?)やりとりがありました。

> Hi, what exact error reported by this code do you have in mind?
>
> Someone has to drive the innovation 😊 If I had to make a choice of sacrificing either PhpStorm or PHPStan, I'd sacrifice PhpStorm 😊 So the fact that the code is understood by PHPStan is more important to me.
>
> If you're not comfortable with this, you can always use PHPStan on level 5 which does not report errors on union types.

意訳

> やあ、正確なエラー検出ができるようになりましたが、何に困ってますか？
>
> 誰かがイノベーションを推進しなければいけません😊 PhpStormかPHPStanのどちらかを犠牲にしなければいけないなら、私はPhpStormを犠牲にします😊 PHPStanにコードが理解される事実の方が私には大事ですからね。
>
> これに慣れてなければ、PHPStanを常にレベル5にすれば合併型(Union)のエラーを報告しないようにできます。

なるほど😊で煽っていくスタイルですが正論。

そんなこんなの悶着があってこのissueは解決したのですが[^4]、ともあれそれから一年近く経ってこんな発言をされたPhpStorm側もIntersection Typesを実装するようになったのは感慨深いことですね。

さて、PhpStormが(不完全ではありますが)Intersection Typesをサポートしたことは喜ぶべきことです。この記法で記述された関数から得た値のメソッド補完が期待通りに動作することは、以下のようなコードで簡単に確かめられます。

```php
<?php

/**
 * @return ArrayAccess&Countable
 */
function c()
{
    return new ArrayObject();
}

c()->
```

![スクリーンショット 2018-11-21 午後11.59.05.png](https://qiita-image-store.s3.amazonaws.com/0/4121/1852e323-0f8b-4a8d-6c8b-106f8c9b633b.png)

一方で、PhpStormのIntersectionTypesサポートはまだ不完全です。
以下のように`ArrayAccess&Countable&Traversable`のうち`Countable`だけを実装したクラスを渡しても**PhpStormは警告を出さない**のです。

```php
class Foo implements Countable
{
    public function count()
    {
        return 0;
    }
}

/**
 * @param ArrayAccess&Countable&Traversable $array_like
 */
function d($array_like)
{
    foreach ($array_like as $a) {
        var_dump($a);
    }
}

d(new Foo);
```

一方でPHPStanは以下のように警告を出します。(画像はEmacsからPHPStanをインラインで表示したところ[^5]です)

<img width="844" alt="スクリーンショット 2018-11-22 午前1.43.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/4a3b5783-a9f7-ec81-7005-d250d0339800.png">

そんなわけで、`&`を使って書けそうな箇所があれば、どんどん使っていくといいんじゃないですかね。(PHPStanを併用していくと、もっとよい)

ちなみに現在のPSR-5ドラフト(飽くまで策定作業中であることには気をつけてください)には[Intersection Typesが含まれます](https://github.com/php-fig/fig-standards/pull/1104)が、[細かい仕様は議論中](https://groups.google.com/forum/#!topic/php-fig/W1VyAtoqGQ8)なので、あまり複雑な式は書かないことをおすすめします。また、PhanのIntersection Typesの実装は現状未定[^6]です。

### PSR-5 &lt;generic&gt; collection type hints

この仕様の話をするのは、めちゃくちゃ厄介です。そもそも型記述の仕様は“PSR-5 Appendix A. Types”に含まれるものです。AppendixなのでPSR-5の本文ではありません。しかもジェネリクスに関しては議論が再開されたあとの[10月16日にドラフトから削除](https://github.com/php-fig/fig-standards/pull/1102)、つまり**議論再開のために白紙撤回**されました。

よってこれから**「PSR-5」と通称される**「PSR-5形式のジェネリクス記法」について話すときは、正確には「**PSR-5の10月16日以前のドラフトに含まれたAppendix A.Typesで定義されてたジェネリクス記法**」となります。もう何がなんだか。

で、その時点の仕様では、次のように書けることになってました。

```php
/**
 * @return \ArrayObject<\DateTimeInterface>
 */
```

もともとドラフト段階であり、現在となっては白紙撤回されてしまった仕様ですが、[phpDocumentor/TypeResolver](https://github.com/phpDocumentor/TypeResolver)、PHPStan、Phanなど解釈する処理系はわりとあります。

この機能が動作するかどうかは以下のようなコードを変形すると確認できます。

```php
<?php

/**
 * @return \ArrayObject|\DateTimeInterface[]
 * @phan-return \ArrayObject<\DateTimeInterface>
 */
function x() {
    $a = new ArrayObject();
    foreach (range(1, 10) as $n) {
        $a[] = new DateTimeImmutable();
    }

    return $a;
}

foreach (x() as $x) {
    $x->
}
```

この状態では `$x->` の後で `DateTimeInterface` のメソッドが入力補完されれば成功です。しかしながら、このコードを `@return \ArrayObject<\DateTimeInterface>` を変形すると、**補完は機能しません**。

これはどうしたことか。その謎を解く鍵は[PHPDoc PSR-5 <generic> collection type hints formatting & parsing : WI-31960](https://youtrack.jetbrains.com/issue/WI-31960)にあります。

> Parsing amended, so generics shall not break anything, but are not used yet.
> Also fixes other syntax-tree-dependent stuff such as rename, sig change etc.

つまり新機能の宣伝文にある<q><strong>PSR-5 &lt;generic&gt; collection type hints</strong> are now supported on the parser level.</q> *(パーサーレベルのサポート？)* とは、現状ユーザーが享受できる機能としては「コメントの`@param array<foo>`が`Reformat Code`しても破壊されなくなる」だけです。 <q>supported on the parser level</q>とは断ってるけど、「**そんなもん新機能一覧に堂々と載せて糠喜びさせるな**」が正直な感想です。

この件についてまとめると、「**JetBrainsは存在しない仕様と動かない機能についての誇大広告をやめろ**」ってことになります。

<p><ins><strong>2018年11月23日追記:</strong> 正式リリース後の<q>WHAT’S NEW IN PHPSTORM 2018.3</q>からはPSR-5についての記述は削除されたので、現在は誇大広告の状態は<strong>解消されました</strong>。</ins></p>

### PSR-5ジェネリクス記法についての個人的見解

 * 実際サポートしてる処理系はPhan, PHPStanなどあります
 * PhpStormがサポートしてないだけなので、使ってやっていいんじゃないでしょうか
 * メソッドやプロパティの補完があまり重要じゃない型 `array<int,string>` などから導入していくのも現実的な気がしてます。

## まとめ

[PhpStorm 2018.3(EAP)でPSR-5:PHPDocのジェネリクス構文や交差型がサポートされた - Qiita](https://qiita.com/suin/items/462b5829f0b575348d46)を読んでｗｋｔｋして[全裸待機](https://ja.uncyclopedia.info/wiki/%E5%85%A8%E8%A3%B8%E5%BE%85%E6%A9%9F)してたみなさまは残念でした。まだ来ません。

ちなみに今月無事にPhpStorm 2018.3がリリースされ、今後も2017年・2018年と同じスケジュールでリリースされるとすると、次の大きなバージョンアップは2019年3月にPhpStorm 2019.1がリリースされる見込みです。また数ヶ月待つ[^7]の……。

## おまけ

この記事の内容は、だいたいリアルタイムで把握しながらEAPが落ちてくる度に[Slackのphpusers-ja team](https://www.msng.info/archives/2016/02/phpusers-ja-on-slack.php)で動かねえぞとぼやいてたことです。

<img width="706" alt="スクリーンショット 2018-11-22 11.37.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/af07f3a2-af4e-039d-a146-9a50e3d33da1.png">
<img width="943" alt="スクリーンショット 2018-11-22 11.37.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/11a491dc-5881-2dd3-7986-2c1f69745c77.png">
<img width="831" alt="スクリーンショット 2018-11-22 11.39.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/4121/e4d2cf1c-f00a-d93d-c935-b1675dd285d2.png">


また、Twitterでこのような発言をしたのは、今回説明したこと以上に**エラー検出はPhpStormよりもPHPStanの方が能力が高い**のでコーディング中にエラーに気付けることが多くあるからです。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">学生とか若手にPhpStormを渡すんじゃなくてEmacsとPHPStanのセットアップをした方がいいんじゃないかって気持ちがちょっと沸いてきてる (今回のインターンシップは参加者の強い希望がなければPhpStormでやります)</p>&mdash; にゃんだーすわん (@tadsan) <a href="https://twitter.com/tadsan/status/1031747290048880640?ref_src=twsrc%5Etfw">August 21, 2018</a></blockquote>


## 脚注

[^1]: [型システム入門 −プログラミング言語と型の理論](https://amzn.to/2DPFxdx)
[^2]: “A new type assignment for λ-terms”. Web上にフリーアクセスできる原文はありませんが、共著者の[Prof.ssa Mariangiola Dezaniのページ](http://www.di.unito.it/~dezani/)には多数の論文があります。
[^3]: C#やDelphiで有名な[Anders Hejlsberg](https://github.com/ahejlsberg)が実装したのがまたエモいですね
[^4]: 別にこのissueはPhpStormが`&`をサポートしないせいで起こったわけではなく、単に実装を変更した副作用として起こっただけですが
[^5]: VimとEmacsからPHPStanを利用する方法については[PHPで開発が捗るリアルタイムエラーチェック](https://qiita.com/tadsan/items/16aaaaaf4e99d0a994c9)に書きました
[^6]: [Look into support for intersection types · Issue #1629 · phan/phan](https://github.com/phan/phan/issues/1629)
[^7]: 実際には2017.3のリリース後にまた新しいEAPがリリースされるので、そこでちゃんと有効化されてくれると嬉しいな

[PhpStorm 2018.3RC]: https://blog.jetbrains.com/phpstorm/2018/11/phpstorm-2018-3-release-candidate-is-available/
[Coming in PhpStorm 2018.3 - PhpStorm]: https://www.jetbrains.com/phpstorm/nextversion/