LoginSignup
18
4

More than 3 years have passed since last update.

【PHP8.0】なんでもあり型が書けるようになる

Last updated at Posted at 2020-05-11

ジェネリクスではない…ジェネリクスではないのだよ………

ざっくり言うとvar_dump()の型引数です。
var_dumpにはプリミティブ値にオブジェクトにリソース型にと、どんな値でも渡すことができるのですが、PHP7.4時点の型システムではvar_dumpの引数の型を表すことができません。
PHP8.0で導入予定のunion型を使うとarray|bool|callable|int|float|null|object|resource|stringとなるのですが、実はresource型はPHP8.0でもまだ使えないので、mixed型を完全に再現することはできません。

ということでMixed Type v2のRFCが提出されました。
投票は2020/05/21まで、受理には2/3+1の賛成が必要です。
が2020/05/11時点では賛成35反対6で、おそらく受理されます。

Introduction

PHP7のスカラー型、7.1のnull許容型、7.2のobject型、そして最新8.0のUNION型とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。

しかし、PHPは常に型をサポートしてきたわけではありません。
そしてこれは、型情報が欠落している際にその意味が曖昧になってしまうという問題に繋がります。

・特定の型に決まっているが、プログラマが宣言を忘れてしまった
・特定の型に決まっているが、古いバージョンのPHPと互換を保つためにあえて省略している
・現在のPHPの型システムでは表現できない型である

明示的なmixed型を用意することで、引数や返り値、プロパティに型を追加して、型情報を忘れていたわけではなく、正確に指定できなかったりあえて広げているのだという主張をを示すことができます。

現在のところmixed型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocでmixed型が使用されている顕著な例としては、PHP標準ライブラリ関数の返り値などがあります。
ネイティブにmixed型があれば、これらをより正確に表現することができるでしょう。

またmixed型はPHPマニュアルにおいても広く使用されています。

var_dump ( mixed $expression [, mixed $... ] ) : void

Proposal

PHPの型システムにmixed型を追加します。
これはarray|bool|callable|int|float|null|object|resource|stringと等価です。
これはPHPの継承時の型検査の実装に適合する正しい動作です。

LSP, Covariance and Contravariance

このProposalは、リスコフの置換原則に準拠しています。

PHP7.4以降、PHPは共変戻り値と反変パラメータに対応しています。

PHPではLSP原則に従うように、パラメータの拡大を許容しています。
サブクラスにおいて、親クラスより広い、特殊でない型を使用することができます。

PHPではLSP原則に従うように、返り値の縮小を許容しています。
サブクラスにおいて、親クラスより狭い、特殊な型を使用することができます。

Parameter types are contravariant

引数は、特定の型からmixed型に拡大することができます。

// 正しい例
 class A
{
    public function foo(int $value) {}
}

class B extends A
{
    // intからmixedに拡大は許可
    public function foo(mixed $value) {}
}

引数の縮小は、LSP原則に違反するため許可されません。

// 不正な例
class A
{
    public function foo(mixed $value) {}
}

class B extends A
{
    // mixedからintに縮小は不可
    // Fatal errorが出る
    public function foo(int $value) {}
}

Return types are covariant

返り値は、mixed型から特定の型に縮小することができます。

// 正しい例
class A
{
    public function bar(): mixed {}
}

class B extends A
{
    // mixedからintに縮小は許可
    public function bar(): int {}
}

返り値の拡大は、LSP原則に違反するため許可されません。

// 不正な例
class C
{
    public function bar(): int {}
}

class D extends C
{
    // intからmixedに拡大は不可
    // Fatal errorが出る
    public function bar(): mixed {}
}

Property types are invariant

プロパティ型指定のRFCに従い、プロパティの型は不変です。

// 不正な例
class A
{
    public mixed $foo;
    public int $bar;
    public $baz;
}

class B extends A
{
    // プロパティ型は縮小不可
    // Fatal errorが出る
    public int $foo;
}

class C extends A
{
    // プロパティ型は拡大不可
    // Fatal errorが出る
    public mixed $bar;
}

class D extends A
{
    // 未指定にmixed型を追加するのも駄目
    // Fatal errorが出る
    public mixed $baz;
}

class E extends A
{
    // 型指定の削除も駄目
    // Fatal errorが出る
    public $foo;
}

Void return type

void型の返り値については、LSPに適合していたとしても拡張は許可されません。

class A
{
    public function bar(): void {}
}

class B extends A
{
    // Fatal error: Declaration of B::bar(): int must be compatible with A::bar(): void
    public function bar(): int {}
}

このRFCは、既存の振る舞いに従います。
すなわち、void型をmixed型に広げることはできません。

Signature checking of function when no parameter type present

引数に型が存在しない場合の型チェックは、mixed型が指定されたかのように動作します。

class A
{
    // 引数の型がないのでmixedとみなす
    public function foo($value) {}
}

class B extends A
{
    // mixed型を追加したが、親クラスと動きは同じ
    public function foo(mixed $value) {}
}

class C extends B
{
    // mixed型を削除したが、親クラスと動きは同じ
    public function foo($value) {}
}

class D extends B
{
    public function foo(mixed $value = null) {}
}

現在のところ、これはクラスの継承に限った動作です。

PHPで型を定義できるようになれば、他のところでも動くようになるかもしれません。

Signature checking of function when no return type present

返り値に型が指定されていない場合の型チェックは、mixed|void型が指定されたかのように動作します。

サブクラスでオーバーロードする際は、返り値を未指定にするか、void型にするか、mixed型およびそのサブタイプの何れかを指定しなければなりません。
そして、一度変更したあとの型を未指定に戻すことはできません。

class A
{
    // 返り値に型がないのでmixed|voidとみなす
    public function foo() {}
}

class B extends A
{
    // mixedを指定した。voidは禁止になる
    public function foo(): mixed {}
}

class C extends B
{
    // mixed|voidはmixedより広いのでNG
    // Fatal errorが出る
    public function foo() {}
}

class D extends B
{
    // voidはmixedのサブタイプではないのでNG
    // Fatal errorが出る
    public function foo(): void {}
}

The mixed|void union type

このRFCは、mixed|voidのUNION型は必要ないので許可しないという立場です。
今後ユースケースが見つかれば許可される可能性はあります。

Nullability

mixed型にはnullが含まれます。
従ってmixed型のnull許容型は情報の重複となります。

このRFCは、mixed型のnull許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。

// NG、既にnull許容なので
function foo(?mixed $arg) {}

// NG、既にnull許容なので
function bar(): ?mixed {}

Explicit returns

返り値にmixed型を使用する場合、明示的にreturnを記述する必要があります。
さもなければTypeErrorが発生します。

function foo(): mixed {}

foo(); // Uncaught TypeError: Return value of foo() must be of the type mixed, none returned

既存のnull許容型と同じ動作です。

function bar(): ?int {}
bar(); // Uncaught TypeError: Return value of bar() must be of the type int or null, none returned

Resource 'type'

PHPではresource型の値を変数に割り当てることができますが、ユーザランドでは引数、返り値、プロパティの型としてresource型を使用することができません。
このRFCの立場としては、resource型はresource型チェックをパスすべきである、というものです。

Mixed vs any

PHPではマニュアルやPHPStanなどの静的解析ツールで広くmixed型が使われているため、mixedになりました。
またmixedはPHP7以降弱い予約語とされていますが、anyは予約語に含まれません。

RFC Impact

Proposed PHP Version(s)

PHP8.0

Backward Incompatible Changes

クラス名としてのmixedが禁止されますが、PHP7.0以降mixedは弱い予約語です。

To SAPIs

特になし。

To Existing Extensions

特になし。

To Opcache

特になし。

Vote

2020/05/07に投票開始、2020/05/21に投票終了。
受理には2/3+1の賛成が必要です。

Patches and Tests

GitHub Pull request #5313

References

PHP RFC: Reserve Even More Types in PHP 7
phpDocumentor type reference

感想

これ、UNION型のRFCの将来の展望にある型宣言そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。

ということでPHP8からmixed型が使えるようになります。

var_dumpのような、仕様としてあらゆる値を受け取る必要のある関数のためにこれが必要なことは確かでしょう。

しかし、ユーザランドで何も考えず適当にこれを使って大惨事、という未来が目に見えますね。
まあでも、そんな人はそもそも型引数を書かないだろうから問題ないかな?

18
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
4