PHP RFC: Never Parameters (v2)
- Version: 0.1
- Date: 2025-03-10
- Author: Daniel Scherzer, daniel.e.scherzer@gmail.com
- Status: Under Discussion
- First Published at: http://wiki.php.net/rfc/never-parameters-v2
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.
Introduction
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.
Proposal
never
can be used as a parameter type, subject to the following restrictions
- it cannot be used in the declaration of any method that has an implementation. Methods with
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. - it cannot be used in non-class functions. This follows from the restriction above - because non-class functions always have an implementation, they cannot use
never
parameters, or they would be unusable. - it cannot be used in the
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 havenever
types, so using this in a property hook would be impossible regardless, but it is worth pointing out. - it cannot be used for a parameter with a default. Because
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).
Differences from prior RFC
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
Backward Incompatible Changes
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.
Proposed PHP Version(s)
Next version of PHP (PHP 8.5 or PHP 9.0)
RFC Impact
To Reflection
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.
Unaffected PHP Functionality
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.
Future Scope
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 allownever
-typed property set hooks- Updating other existing core interfaces and abstract methods to use
never
-typed parameters; the case ofArrayAccess
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 thanBackedEnum
Proposed Voting Choices
Add never
-typed parameters in the limited cases described above, and use them for backed enums?
Patches and Tests
Implementation
After the project is implemented, this section should contain
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
- a link to the language specification section (if any)
References
Links to external references, discussions or RFCs
- Discussion from that RFC: https://externals.io/message/115712
- property-hooks RFC
- enumerations RFC