Currently, there is no way to specify a parameter with a bottom type, indicating that a function has a parameter but not providing any guarantees on the types of values it accepts. This RFC proposes allowing the never
type to be used on methods in places where methods are just declared to exist, like interfaces and abstract class methods.
For a use case, and the inspiration for this RFC, consider the case of the BackedEnum
interface that was added in PHP 8.1. It is defined as:
interface BackedEnum extends UnitEnum { public string $value; public static function from(int|string $value): static; public static function tryFrom(int|string $value): ?static; }
However, this interface is incorrect from a type perspective. Backed enums are backed by string or by integers, and in either scenario, all cases of the enum have values of that type. For enums that are string-backed, the ::from()
and ::tryFrom()
methods should be typed with string parameters; for enums that are int-backed, the methods should be typed with integer parameters.
But, since parameter types are contravariant, for enums to have the right signatures the base BackedEnum
methods would need to have types that are more restrictive than either string
or int
- something like string&int
, or, since that is impossible (and just a confusing way of saying “absolutely no values”), what is proposed in this RFC:
interface BackedEnum extends UnitEnum { public string $value; public static function from(never $value): static; public static function tryFrom(never $value): ?static; }
That way, for enums that were backed by strings, the methods could be
public static function from(string $value): static; public static function tryFrom(string $value): ?static;
While, for enums backed by ints, the methods could be
public static function from(int $value): static; public static function tryFrom(int $value): ?static;
And, importantly, LSP is followed - since never
is the bottom type, having a string
or int
parameter widens the acceptable types.
never
can be used as a parameter type, subject to the following restrictions
never
can never (pun intended) be called, because there are no values that satisfy the type constraint. Instead of delaying the error until runtime when the user attempts to call the method, just require that the method not have a body. In other words, this can be used in interfaces and in the declaration of abstract methods in both traits and abstract classes.never
parameters, or they would be unusable.set
property hook. Because property hook parameter types are contravariant with the type of the property itself, the property hook parameter cannot be declared with a narrower type than the property itself. At the moment, properties cannot be declared to have never
types, so using this in a property hook would be impossible regardless, but it is worth pointing out.never
does not accept any values, there is no possible default value that would be accepted by the current validation that the default value be legal, but it is worth pointing out.
Additionally, making use of this new feature, the definitions of the BackedEnum::from()
and BackedEnum::tryFrom()
methods are updated to use never
parameters, and for any individual backed enum, the signature documents that the method accepts only string parameters (or only int parameters).
In developing this RFC the author discovered a prior RFC, never_for_parameter_types, with a similar goal. Differences from that proposal include
never
parameters can only be used when on methods (not free-standing functions)never
parameters can only be used when a method has no body associated with the declarationnever
parameters cannot have default valuesBackedEnum
is updated as part of this proposal
Anything previously relying on the exact type information for BackedEnum::from()
and BackedEnum::tryFrom()
, or the corresponding inherited methods on any backed enum, may need to be updated to account for the fact that the information is correct now.
As with all RFCs that make code that previously had a compile-time error become valid, this RFC requires updates to static analyzers and IDEs to understand the semantics of never parameters.
Next version of PHP (PHP 8.5 or PHP 9.0)
Information about BackedEnum::from()
, BackedEnum::tryFrom()
, and the inherited versions thereof on individual backed enums will be updated.
Any code that relied on the assumption that parameters did not have never
types will need to be updated to check the actual type.
Since never
was previously not allowed as a parameter, the behavior of existing legal code is unchanged.
Since PHP internal methods (in this case BackedEnum::from()
and BackedEnum::tryFrom()
) do their type checking manually in their implementations rather than having signatures enforced by the engine upon calling, the types of values allowed were already restricted based on the backing type of enums. Thus, the behavior of calling BackedEnum::from()
or BackedEnum::tryFrom()
is unchanged.
Further work might include
never
-typed properties, to indicate that a property exists but subclasses are free to impose any restrictions desired on the type of the parameter; this would also allow never
-typed property set hooksnever
-typed parameters; the case of ArrayAccess
was heavily discussed on the mailing list with regard to the previous RFC. This RFC explicitly does not change any interface interface or abstract class other than BackedEnum
Add never
-typed parameters in the limited cases described above, and use them for backed enums?
After the project is implemented, this section should contain
Links to external references, discussions or RFCs