【PHP8.2】false疑似型およびnull型が単独で使えるようになる

PHP8.0において実装された[union型](https://www.php.net/manual/ja/language.types.declarations.php#language.types.declarations.composite.union)の導入過程において、[false疑似型](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7#false%E7%96%91%E4%BC%BC%E5%9E%8B)および[null型](https://qiita.com/rana_kualu/items/f75e254553157d8eb0b7#null%E8%A8%B1%E5%AE%B9%E5%9E%8B)が導入されました。
これらはunion型の一部としてのみ有効な型であり、単独で使用することはできません。

しかし、これを単独で使えるようにしようというRFCが提出されました。
投票期間は2022/03/12から2022/03/26であり、賛成38反対0の全会一致で可決されました。
従ってPHP8.2から単独型として使用可能になります。

以下は該当のRFC、[Allow null and false as stand-alone types](https://wiki.php.net/rfc/null-false-standalone-types)の日本語訳です。

# Allow null and false as stand-alone types

## Introduction

nullはPHPのUnit型です。
falseはbool型のリテラルです。

現在nullを単独で型宣言として使うことはできません。
nullはUnit型であるという性質上、いかなる情報も保持することはできないためです。

ところで関数がエラーであることを示すために、返り値として歴史的にfalseが使われてきました。
これが[UNION型](https://wiki.php.net/rfc/union_types_v2)においてfalse疑似型が導入された大きな理由です。

### Motivation

このRFCの動機です。

#### Type system completeness

型システムの完全性。

PHPは、PHP8.0でトップ型のmixed型を、PHP8.1でボトム型のnever型をサポートしました。
さらにPHP8.0でUNION型、PHP8.1で交差型という複合型もサポートしました。

PHPでUnit型を型付けできないのは、PHPに残されたわずかな欠陥です。

#### Edge case with regards to the literal type false

false疑似型のエッジケース。

false疑似型はUNION型でのみ使用できますが、`null|false`は許されないため完全ではありません。
現在のところ、このエッジケースは`bool|null`と表現するしかありません。
これはtrueを返すかもしれないという誤った印象を与えるため、人間や静的解析において有益ではありません。

PHPの組込関数にも`null|false`を必要とするものがあり、一例としては[gmp_random_seed](https://www.php.net/manual/ja/function.gmp-random-seed.php)です。

#### Providing precise type information while satisfying LSP

LSPを満たしたうえで正確な型情報を提供する。

親クラスで`public function foo(): ?T`が定義されている場合に、子クラスでは必ずTを返すのであれば`public function foo(): T`と、より正確な型情報を返すことができます。
しかし逆はできません。
子クラスが必ずnullを返す場合は、`public function foo(): null`とすることはできません。
この場合はシグネチャは元のままとし、返り値はドキュメントなどで示すしかありません。

PHPの組込関数では[SplFileObject::getChildren()](https://www.php.net/manual/ja/splfileobject.getchildren.php)等が該当します。


## Proposal

型宣言が許される位置全てにおいて、false型とnull型を使用可能にする。

```php
class Nil {
    public null $nil = null;
 
    public function foo(null $v): null { /* ... */ }
}

class Falsy {
    public false $nil = false;
 
    public function foo(false $v): false { /* ... */ }
}
```


### Redundancy of ?null

`?null`はコンパイルエラーになります。
これはPHPの現在の型宣言の解釈規則に沿ったものです。

### Reflection

リフレクションはサポートされます。
ただし、`null|T`はReflectionNamedTypeになりますが、`null|false`はReflectionUnionTypeになります。

以下はどのように見えるかの例です。

```php
function dumpType(ReflectionUnionType $rt) {
    echo "Type $rt:\n";
    echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
    foreach ($rt->getTypes() as $type) {
        echo "  Name: " . $type->getName() . "\n";
        echo "  String: " . (string) $type . "\n";
        echo "  Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n";
    }
}
 
function test1(): null|false { }
function test2(): ?false { }

dumpType((new ReflectionFunction('test1'))->getReturnType());
dumpType((new ReflectionFunction('test2'))->getReturnType());

/*
Type false|null:
Allows null: true
  Name: false
  String: false
  Allows Null: false
  Name: null
  String: null
  Allows Null: true

Type false|null:
Allows null: true
  Name: false
  String: false
  Allows Null: false
  Name: null
  String: null
  Allows Null: true
*/
```


### Example

以下はユニットテストの例です。

```php
class User {}
 
interface UserFinder
{
    function findUserByEmail(): User|null;
}
 
class AlwaysNullUserFinder implements UserFinder
{
    function findUserByEmail(): null
    {
        return null;
    }
}
```

現在、上記のコードはエラーになります。

```php
Fatal error: Null can not be used as a standalone type
```

すなわち、`findUserByEmail()`はnullしか返さないにもかかわらず、`User|null`という正しくない戻り値を設定しなければなりません。
ところが逆に静的アナライザはクラスを分析して`findUserByEmail()はUserクラスを返さない`の警告を出してしまうため、さらなる混乱に見舞われます。

同様の問題は、falseのUNION型にも存在します。


## Backward Incompatible Changes

後方互換性のない変更点はありません。

## Proposed PHP Version

PHP8.2

## Implementation

https://github.com/php/php-src/pull/7546


# 感想

[gmp_random_seed](https://www.php.net/manual/ja/function.gmp-random-seed.php)は`null|false`を返すのですが、これまでこの型を正しく表現することはできませんでした。
本RFCによって、その問題が解消されます。
また、常に`false`や`null`を返す型も正確に表現可能になります。

この改善によって、PHPの型システムに存在していた僅かな穴がふさがれ、より表現力豊かになりました。


実はもうひとつだけ大きな穴が残っていまして、[resource型](https://www.php.net/manual/ja/resource.php)と言うのですが、こいつは現行の型システムで表すことができません。
ただ、resouce型はどんどこ[削除されて](https://qiita.com/rana_kualu/items/e350b8ff80f929626eb9)[オブジェクトに置き換え](https://qiita.com/rana_kualu/items/c110cb244c3ee38c6859)られつつあるため、いずれは無くなると思います。
その時こそが、PHPの型システムの完成となるでしょう。

あとは`(A&B)|C`みたいな型パズルの領域ですが、こんなのが役に立つとも思えないのですがどうでしょうかね。
[交差型](https://qiita.com/rana_kualu/items/f49582bc94e0bae23d9c)において進められていた[実装も放棄されており](https://github.com/Girgias/php-src/pull/8)、今後復活するかは微妙なところです。