kphp
kphp copied to clipboard
Exhaustive `switch` operator
Problem
class Var {
public const A = 0x1;
public const B = 0x2;
}
...
switch ($var) {
case Var::A:
return a_work();
case Var::B:
return b_work();
}
Here we have a class that is basically an enum
(though PHP doesn't have one). it has two variants, A
and B
. Also there's a switch
that is supposed to cover all possible variants of Var
. Now you want to add new variant C = 0x3
, but you forget to add it to the switch
by some reason - now you have a bug with uncovered variant.
Solution
Inspired by Rust's match
operator.
Introduce new annotation @kphp-match Type $v
like following:
/** @kphp-match Var $var */
switch ($var) {
...
}
Now the compiler will check whether all of the possible are covered by the switch
and fail compilation if they are not.
Of course there are cases when only a subset of variant is need to be processed. In this case we can still use default
to cover them:
class Var {
public const A = 0x1;
public const B = 0x2;
public const C = 0x3;
public const D = 0x4;
}
...
/** @kphp-match Var $var */
switch ($var) {
case Var::A:
return a_work();
case Var::B:
return b_work();
default:
// We do not care about anything except but A and B.
// Or can raise a warning, if we want to.
}
More use cases
Switch by class
Except const
as enum
use case, another possible case is coverage of all variants for switch (get_class($o))
, because if $o
has type T
, compiler knows all possible subtypes (child classes) or implementation classes (if T
is an interface`):
/** @kphp-match-type $o */ // Or use @kphp-match here too, depending on implementation details.
switch (get_class($o)) {
case A::class:
// $o can be smart-casted to A here.
return a_work();
case B::class:
return b_work();
}
Const map keys
Sometimes it may be necessary to require a map to have all possible keys:
class Mappings {
/** @kphp-match Var */
private const MAPPING = [
Var::A => 'a',
Var::B => 'b',
Var::C => 'c',
// Compile time error if there's no Var::D
];
public static map(int $v): ?string {
return self::MAPPING[$v] ?? null;
}
}
Other languages support
Languages that support similar feature:
Will be partially implemented in PHP 8's match
operator (#303), but it doesn't cover cases with exhaustion over class constants.