Table of Contents

PHP 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

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

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

Proposed Voting Choices

Add never-typed parameters in the limited cases described above, and use them for backed enums?

Patches and Tests

https://github.com/php/php-src/pull/18016

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

Links to external references, discussions or RFCs