У классов есть контракты на их методы:
<?php
class A {}
class B extends A {}
function foo(A $a) {}
function bar(B $b) {
foo($b);
}
?>
Этот код безопасен для типов, поскольку B следует контракту A, и, благодаря магии ковариантности и контравариантности, любое ожидание, которое может быть у человека в отношении методов, будет сохранено, за исключением исключений.
У перечислений есть контракты на их варианты, а не методы:
<?php
enum ErrorCode {
case SOMETHING_BROKE;
}
function quux(ErrorCode $errorCode)
{
// Кажется, что этот код охватывает все варианты
match ($errorCode) {
ErrorCode::SOMETHING_BROKE => true,
}
}
?>
Оператор match в функции quux
может быть статически проанализирован,
чтобы охватить все случаи в ErrorCode.
Но представьте, что было бы разрешено расширять перечисления:
<?php
// Экспериментальный код, в котором перечисления не являются конечными.
// Обратите внимание, что это не будет работать в PHP.
enum MoreErrorCode extends ErrorCode {
case PEBKAC;
}
function fot(MoreErrorCode $errorCode) {
quux($errorCode);
}
fot(MoreErrorCode::PEBKAC);
?>
Согласно обычным правилам наследования, класс, расширяющий другой класс, пройдёт проверку типа.
Проблема заключается в том, что оператор match в quux()
уже не охватывает все случаи.
Поскольку оно не знает о MoreErrorCode::PEBKAC
, match выбросит исключение.
Из-за этого перечисления являются окончательными и не могут быть расширены.