列挙型が継承できない理由

クラスは、メソッドの使い方を約束(契約)するものです:

<?php

class A {}
class B extends A {}

function foo(A $a) {}

function bar(B $b) {
    foo($b);
}
?>

上のコードは型安全といえます。なぜなら、 B は A との契約を守っており、かつ共変性/反変性のマジックによって、 メソッドへのあらゆる期待が満たされるからです。例外は別です。

一方で、列挙型は case について契約するものです。メソッドではありません:

<?php

enum ErrorCode {
    case SOMETHING_BROKE;
}

function quux(ErrorCode $errorCode)
{
    // 以下のようなコードを書くと、全ての case をカバーします
    match ($errorCode) {
        ErrorCode::SOMETHING_BROKE => true,
    }
}

?>

quux 関数内の match 式は、 ErrorCode の全ての case をカバーしているかを静的に解析できます。

ここで以下のように、列挙型が継承可能だとしましょう:

<?php

// 列挙型が final でなかったと仮定した、思考実験のコード
// このコードは実際には動作しないので注意
enum MoreErrorCode extends ErrorCode {
    case PEBKAC;
}

function fot(MoreErrorCode $errorCode) {
    quux($errorCode);
}

fot(MoreErrorCode::PEBKAC);

?>

通常の継承のルールでは、あるクラスの子クラスは親クラスの型チェックを通過します。

上のコードの問題点は、 quux() 関数の match 式が全ての case をカバーしてはいないということです。 なぜなら、MoreErrorCode::PEBKAC がカバーされていないので、match 式が例外をスローするからです。

こうした理由から、列挙型は final 扱いであり、継承できなくなっています。