PHP8.2でfalse型とnull型が単独で使えるようになったわけですが、こうなるとtrue型が存在しないのが片手落ちになってしまうわけですよ。
class Foo{
public bool $var1;
public false $var2;
public true $var3; // ←これだけ非対応
}
ということでtrue型も実装しようというRFCが提出されました。
投票期間は2022/05/29から2022/06/12であり、賛成33反対0の全会一致で可決されました。
PHP8.2から使用可能になります。
以下は該当のRFC、Add true typeの日本語訳です。
PHP RFC: Add true type
Introduction
PHPは、PHP8.2においてnull型とfalse型を単独サポートすることを決めました。
しかし、falseの対偶であるtrueは型として存在しません。
true型を実装しなかった理由はUNION型のRFCにおいて説明されています。
現在では、エラーや不正が起きた際の関数の戻り値はnullにすることが推奨されていますが、歴史的理由から多くの内部関数はfalseを返してきます。
Statisticsセクションで示すように、union型を返す内部関数は大部分がfalseを含んでいます。
一般的な例としてはint|falseを返すstrposなどです。
これを正しく表記するとint|boolですが、これは関数がtrueを返すこともあるという誤った印象を与えます。
そのため、このRFCにはfalseのみを表すfalse疑似型が含まれています。
trueのみを表すtrue疑似型は、それが必要となる歴史的理由が存在しないため、このRFCには含まれません。
しかし、今となってはtrue型をサポートする意義があると考えています。
PHP8.0においてValueErrorが導入され、多くのE_WARNINGがエラーや例外に昇格しました。
これによって、これまで失敗時にfalseを返していた内部関数の多くが失敗時にエラーを返すようになり、falseを返さないことが多くなりました。
理想的には、これらの関数はvoidを返すべきかもしれませんが、それは大きなBCブレークになります。
またvoidはfalsyな値であるため、以下のような使い方ができなくなります。
if (function_which_could_fail_before()) {
// 成功した
} else {
// 失敗した
}
従って、内部関数の返り値をtrue型にすれば、静的解析器などのツールは、上記コードが到達不能であることを検出できるでしょう。
もう一つの動機としては、LSPを満たしつつ、より正確な型情報を提供できることです。
例として、bool型の拡張で常にtrue型を返すメソッドなどです。
class User {
function isAdmin(): bool
}
class Admin extends User
{
function isAdmin(): true
{
return true;
}
}
Proposal
現在、型宣言が許可されている全ての場所において、true型を使用可能にします。
class Truthy {
public true $truthy = true;
public function foo(true $v): true { /* ... */ }
}
Compile time redundancy checks
コンパイル時の重複チェック。
既存のfalse型と同様、UNION型における冗長な記述はコンパイルエラーになります。
function test(): bool|true {} // エラー
これはFatal error: Duplicate type true is redundant in %s on line %d
のエラーになります。
Compile time error for using true|false instead of bool
bool型のかわりにtrue|false
のUNION型を書いた場合はエラーになります。
function test(): false|true {} // エラー
これはtrue|false
とbool
のサブタイプ関係を定義しなければならないからです。
従って、本RFCではtrue|false
の使用を単に禁止することにします。
Examples
内部関数やユーザランドでも、trueだけを返す関数は多く存在します。
例としてはarray_sort()
・mysqli_*()
・SplFixedArray::setSize()
等です。
ユーザランドでのtrue型導入が有効な例としては、composer等があります。
Reflection
true型のリフレクションの動作は、他の型と同じです。
Backward Incompatible Changes
互換性のない変更はありません。
Proposed PHP Version
PHP8.2。
Future Scope
この項目は、今後の展望であり、このRFCには含まれていません。
Literal Types
リテラル型。
今回導入されたtrue型、および以前導入されたfalse型は、TypeScriptで言うところのリテラル型の一種です。
これは、特定の型に限定されたENUM型のようなものです。
type ArrayFilterFlags = 0|ARRAY_FILTER_USE_KEY|ARRAY_FILTER_USE_BOTH;
array_filter(array $array, callable $callback, ArrayFilterFlags $flag): array;
ENUMではなくリテラル型を使用する利点は、中身のはっきりしないENUMではなく基本型の値を直接扱うことができることです。
そのため、互換性を壊すことなく後付けすることが可能です。
しかし、著者の思うところとしては、ENUMはリテラル型より優れています。
今回リテラル型であるtrueを追加する理由は、既にPHPエンジンにはfalse型が実装されているので実装のコストが極めて低いから、そしてfalse型があるのにtrue型が存在しないのはおかしいからということです。
Allowing usage of true|false
true|false
のUNION型を使用可能にする。
そのためにはtrue|false
型とbool型の間のサブタイピングを定義する必要があります。
Type Aliases
型エイリアス。
このように型が複雑になってくると、型宣言を再利用する価値が出てくるでしょう。
これには2つの方法が考えられます。
ひとつはローカルエイリアスです。
use int|float as number;
function foo(number $x) {}
このnumber
はローカルからのみ見えるエイリアスであり、コンパイル時に元のint|float
に展開されます。
もうひとつは型定義のエクスポートです。
namespace Foo;
type number = int|float;
こちらは\Foo\number
をどこからでも利用可能になります。
Implementation
感想
プルリクを見てみると、19ファイル300行弱というかなり小さな内容です。
しかもそのほとんどはテストコードであり、実質的な変更は11行でした。
マジかよ。
型の追加という、傍目にはとても大きな規模と思われる変更が、たったこれだけで済んでしまうんですね。
まあ流石に本体部分だけであり、たとえばsort関数の返り値をbool
からtrue
にする、みたいなのは入ってないみたいですが、それでもびっくりですよ。
due to the extremely low implementation complexity
ってのはこういう人たちにとってのって意味なんだろうなあと思っていたら文字どおりの意味だったでござる。
ということで、false型に続いてtrue型までもがPHPで使えるようになります。
もはやPHPには型がないなんて認識の人は古い人間なので相手にしないようにしましょう。
ちなみに、PHPのコードは型宣言を全て抜いても動きます。
そこまで互換を保ってるのは逆にびっくりですね。