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


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

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

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

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

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

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

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

2018年のPHPDoc事情とPSR-5に詳しく書いた内容と重複する内容と、そこからアップデートされた情報もあるので先に読んできてください。

PhpStorm 2018.3

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

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

PhpStorm adds Support for Intersection Types “&”. If you annotate a variable as Foo&Bar, 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 <generic> collection type hints are now supported on the parser level.

交叉型 (Intersection Types)

Intersection Typesは意外と古くからある概念で、「型システム入門1」によれば、初出は実に1978年の論文2だそうです。TypeScriptには1.6の新機能として2015年に実装3されました。

PHPの静的解析ツールであるPHPStanには2017年11月30日にリリースされた0.9の新機能としてリリースされました。その思いは開発者自身によるUnion Types vs. Intersection Types – Ondřej Mirtes – Mediumにある通りです。

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

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

これで、$array_likeArrayAccessCountableの両方を実装した値を期待することを意味します。

この記法は現状PHP本体もPhpStormなどもサポートしてないわけですが、この機能追加の変更によって検査結果が壊れたと訴えるUnion vs Intersection issues · Issue #700 · phpstan/phpstanで、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

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

c()->

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

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です)

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

ちなみに現在のPSR-5ドラフト(飽くまで策定作業中であることには気をつけてください)にはIntersection Typesが含まれますが、細かい仕様は議論中なので、あまり複雑な式は書かないことをおすすめします。また、PhanのIntersection Typesの実装は現状未定6です。

PSR-5 <generic> collection type hints

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

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

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

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

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

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

<?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 collection type hints formatting & parsing : 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.

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

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

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

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

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

まとめ

PhpStorm 2018.3(EAP)でPSR-5:PHPDocのジェネリクス構文や交差型がサポートされた - Qiitaを読んでwktkして全裸待機してたみなさまは残念でした。まだ来ません。

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

おまけ

この記事の内容は、だいたいリアルタイムで把握しながらEAPが落ちてくる度にSlackのphpusers-ja teamで動かねえぞとぼやいてたことです。



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

脚注


  1. 型システム入門 −プログラミング言語と型の理論 

  2. “A new type assignment for λ-terms”. Web上にフリーアクセスできる原文はありませんが、共著者のProf.ssa Mariangiola Dezaniのページには多数の論文があります。 

  3. C#やDelphiで有名なAnders Hejlsbergが実装したのがまたエモいですね 

  4. 別にこのissueはPhpStormが&をサポートしないせいで起こったわけではなく、単に実装を変更した副作用として起こっただけですが 

  5. VimとEmacsからPHPStanを利用する方法についてはPHPで開発が捗るリアルタイムエラーチェックに書きました 

  6. Look into support for intersection types · Issue #1629 · phan/phan 

  7. 実際には2017.3のリリース後にまた新しいEAPがリリースされるので、そこでちゃんと有効化されてくれると嬉しいな