====== 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: 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: 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 to ''never'', and any union would reduce ''never'' out of the union, as ''never'' is the identity type of unions. * Attempting to call code directly that uses the ''never'' type for an argument would result in a ''TypeError'', 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 ===== https://github.com/php/php-src/pull/7373 ===== 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://github.com/JordanRL/never-argument-type/blob/master/README.md * https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) * https://en.wikipedia.org/wiki/Bottom_type * https://blog.logrocket.com/when-to-use-never-and-unknown-in-typescript-5e4d6c5799ad/ * https://externals.io/message/115712 ===== Changelog ===== * 0.1: Initial Proposal * 0.2: Removed explicit widening