PHP8.0において実装されたunion型の導入過程において、false疑似型およびnull型が導入されました。
これらはunion型の一部としてのみ有効な型であり、単独で使用することはできません。
しかし、これを単独で使えるようにしようというRFCが提出されました。
投票期間は2022/03/12から2022/03/26であり、賛成38反対0の全会一致で可決されました。
従ってPHP8.2から単独型として使用可能になります。
以下は該当のRFC、Allow null and false as stand-alone typesの日本語訳です。
Allow null and false as stand-alone types
Introduction
nullはPHPのUnit型です。
falseはbool型のリテラルです。
現在nullを単独で型宣言として使うことはできません。
nullはUnit型であるという性質上、いかなる情報も保持することはできないためです。
ところで関数がエラーであることを示すために、返り値として歴史的にfalseが使われてきました。
これがUNION型において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です。
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()等が該当します。
Proposal
型宣言が許される位置全てにおいて、false型とnull型を使用可能にする。
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になります。
以下はどのように見えるかの例です。
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
以下はユニットテストの例です。
class User {}
interface UserFinder
{
function findUserByEmail(): User|null;
}
class AlwaysNullUserFinder implements UserFinder
{
function findUserByEmail(): null
{
return null;
}
}
現在、上記のコードはエラーになります。
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
感想
gmp_random_seedはnull|false
を返すのですが、これまでこの型を正しく表現することはできませんでした。
本RFCによって、その問題が解消されます。
また、常にfalse
やnull
を返す型も正確に表現可能になります。
この改善によって、PHPの型システムに存在していた僅かな穴がふさがれ、より表現力豊かになりました。
実はもうひとつだけ大きな穴が残っていまして、resource型と言うのですが、こいつは現行の型システムで表すことができません。
ただ、resouce型はどんどこ削除されてオブジェクトに置き換えられつつあるため、いずれは無くなると思います。
その時こそが、PHPの型システムの完成となるでしょう。
あとは(A&B)|C
みたいな型パズルの領域ですが、こんなのが役に立つとも思えないのですがどうでしょうかね。
交差型において進められていた実装も放棄されており、今後復活するかは微妙なところです。