PHP RFC: Never For Parameter Types
- Version: 0.2
- Date: 2021-08-14
- Author: Jordan LeDoux, jordan.ledoux@gmail.com
- Status: Withdrawn
- First Published at: http://wiki.php.net/rfc/never_for_parameter_types
Introduction
Parameters in PHP are contravariant to preserve Liskov substitution. This means that if class B extends class A, then redefines a function call, the entire type of that parameter from class A must be present in the type of the parameter in class B:
<?php abstract class A { abstract public function foo(int $arg); } class B extends A { public function foo(int|float $arg) { return round($arg) + 1; } }
Thus, the more specific a type is for an parameter in a base class, the more broad it can be in an extending class with the requirement that it must also include the type from the base class.
Since never
is a bottom type within the PHP engine, all other types contain it. This RFC proposes allowing never
as a valid parameter type for functions.
Use Cases
Interfaces and Abstracts
With the never
type available, interfaces and abstracts would be able to allow implementing classes to provide a type without specifying any details about what that type must be. This would allow use cases such as the following:
<?php interface CollectionInterface { public function add(never $input): self; }
Implementers of the CollectionInterface
could then specify any type they want. Functions which use collections could then type against the interface, allowing any of the variously typed implementations to be provided, though additional docblocks would be required for static analysis as this feature does not actually solve the generics issue.
In this way, allowing the never type for interfaces and abstracts could be a method of providing minimal stop-gap support for generics type behavior (covariant polymorphism) while avoiding the challenges that providing generics represents. It would also not prevent or make it more difficult to provide full generics in the future.
Using never as a minimal form of generics in this way would require additional work, such as docblock information, for the static analysis to treat them as such, since this features is not generics.
Internal Classes and Interfaces
Providing internal interfaces that require a type but do not specify which type can be very beneficial. This has been encountered recently with attempts to update ArrayAccess
.
interface ArrayAccess { public function offsetExists(never $offset): bool; public function offsetGet(never $offset): mixed; public function offsetSet(never $offset, never $value): void; public function offsetUnset(never $offset): void; }
Currently, internally provided interfaces that must provide flexible parameters for implementers specify the parameter as mixed, however as mixed is the top type in PHP it cannot be narrowed at all, and any code which implements the interface can not fully utilize the type system in place in PHP.
Never as a parameter type allows us to provide interfaces in core that are correctly typed according to the type hierarchy and still follow LSP.
Design Considerations
Never vs A New Type
While it could be argued that the intended meaning of never was explicitly that code using this type would terminate, a new type offers several issues over using never:
never
already represents the concept of a bottom type in PHP due to its usage with return types. The engine already has the concept of this type built in, but is not currently exposing it for use with arguments. Having multiple bottom types not only doesn't make sense, but an example of another language doing this cannot be found.never
correctly indicates that the code which uses it for an argument type can never be called directly.- This usage has precedence in other languages; see below.
Other Languages
There are several languages which contain a bottom type, some of which even use never
as their bottom type. The behavior described in this RFC is in fact how never
behaves and can be used in TypeScript, which also uses never
as its bottom type.
Scala also uses the bottom type to denote covariant parameter polymorphism, though the bottom type in Scala is Nothing
.
Proposal
Allow the use of never
as a type for arguments in interfaces and classes. This would have the following semantics:
never
cannot be used in an intersection type or union type. Any intersection would reduce tonever
, and any union would reducenever
out of the union, asnever
is the identity type of unions.- Attempting to call code directly that uses the
never
type for an argument would result in aTypeError
, as no zval will match this type.
This means that an interface or class could allow implementers and subclasses to declare a type for an argument without restricting that type to anything particular.
Backward Incompatible Changes
None
Proposed PHP Version(s)
This change is proposed for PHP 8.2
RFC Impact
To SAPIs
None
To Existing Extensions
None
To Opcache
None
New Constants
None
php.ini Defaults
None
Unaffected PHP Functionality
Existing PHP typing will be unaffected. As never
is the bottom type in the PHP type hierarchy, all types contain it, and thus no existing typing will need to be changed or updated.
Future Scope
To fully function as a bottom type in PHP, never
could be allowed in other places such as class properties. This is left for future RFCs.
Proposed Voting Choices
Allow never as a parameter type as described: yes/no. A 2/3 vote is required to pass.
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
- https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
Changelog
- 0.1: Initial Proposal
- 0.2: Removed explicit widening