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

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

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

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


## Introduction

PHP7の[スカラー型](https://qiita.com/hnw/items/8631c99349509c504d35)、7.1の[null許容型](https://qiita.com/a-nishimura/items/8784bff55b4f3f867520)、7.2の[object型](https://www.php.net/manual/ja/migration72.new-features.php)、そして最新8.0の[UNION型](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7)とPHPの型システムは進化し続けており、PHPの開発者はほとんどの関数において引数と返り値、そしてプロパティについて明示的に型情報を宣言することができるようになりました。

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

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

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

現在のところ`mixed`型はPHPDocの中でのみ使用することができますが、これは適切ではありません。
PHPDocで`mixed`型が使用されている顕著な例としては、[PHP標準ライブラリ関数](https://github.com/php/php-src/blob/dfd0acf0d722fbbebeab349b0d4366dd8f30bbee/ext/standard/basic_functions.stub.php#L94)の返り値などがあります。
ネイティブに`mixed`型があれば、これらをより正確に表現することができるでしょう。

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

```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は、[リスコフの置換原則](https://en.wikipedia.org/wiki/Liskov_substitution_principle)に準拠しています。

PHP7.4以降、PHPは[共変戻り値と反変パラメータ](https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters)に対応しています。

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

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

### Parameter types are contravariant

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

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

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

```php
// 不正な例
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`型から特定の型に縮小することができます。

```php
// 正しい例
class A
{
    public function bar(): mixed {}
}
 
class B extends A
{
    // mixedからintに縮小は許可
    public function bar(): int {}
}
```

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

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

### Property types are invariant

[プロパティ型指定のRFC](https://wiki.php.net/rfc/typed_properties_v2#inheritance_and_variance)に従い、プロパティの型は不変です。

```php
// 不正な例
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に適合していたとしても拡張は許可されません。

```php
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`型が指定されたかのように動作します。

```php
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で[型を定義できるようになれば](https://github.com/Danack/RfcCodex/blob/master/typedef_callables.md)、他のところでも動くようになるかもしれません。

### Signature checking of function when no return type present

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

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

```php
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許容型を許容しないという立場です。
今後ユースケースが見つかれば許可される可能性はありますが、その際はどの冗長な型指定を許可して、どの冗長な型指定は許可しない、という議論が必要になるでしょう。

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

### Explicit returns

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

```php
function foo(): mixed {}

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

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

```php
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以降[弱い予約語](https://www.php.net/manual/ja/reserved.other-reserved-words.php)とされていますが、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](https://github.com/php/php-src/pull/5313)

## References

・[PHP RFC: Reserve Even More Types in PHP 7](https://wiki.php.net/rfc/reserve_even_more_types_in_php_7)
・[phpDocumentor type reference](https://www.phpdoc.org/docs/latest/guides/types.html)


# 感想

これ、[UNION型のRFC](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7)の将来の展望にある[型宣言](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7#type-aliases)そのものですよね。
ついでだから型宣言自体もできるようにしてしまえばいいのでは。

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

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

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