rfc:union_types
Differences
This shows you the differences between two versions of the page.
rfc:union_types [2016/06/03 12:26] bwoebi |
rfc:union_types [2017/09/22 13:28] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Union Types ====== | ||
- | * Version: 1.0 | ||
- | * Date: 2015-02-14 | ||
- | * Author: Levi Morrison < | ||
- | * Status: Under Discussion | ||
- | * First Published at: http:// | ||
- | ===== Introduction ===== | ||
- | In PHP using "type hints" to define either the allowed parameter types for a function, or the return type of a function, performs two useful roles: | ||
- | |||
- | * Using types allows the PHP engine to enforce the correct type of variable passed to, or returned from, a function. | ||
- | * Using types makes it easy to reason about what types need to be passed to, or can be returned from a function. This makes it easier for both humans, and static code analysis tools, to determine about whether code is correct or not. | ||
- | |||
- | For a lot of functions in PHP, each parameter will only be one type. Similarly, for the majority of functions, the return value of a function, will only ever be of one type. | ||
- | |||
- | However, for a significant number of functions, the acceptable parameters, or the possible return values, can be of more than one type. For example consider the '' | ||
- | |||
- | * if the needle exists it returns an integer. | ||
- | * if the needle is not found, false is returned. | ||
- | |||
- | In the documentation on php.net, the two possible return types are documented as '' | ||
- | |||
- | Currently in userland code, when a parameter for a function can be one of a multiple but limited set of types, or the return value from a function can be one of a multiple but limited set of types, there can be no type information supplied, and so it is not possible for the PHP engine to enforce any types passed to/from that function, and it is not easy for people using that function to reason about the types passed to/returned from that function. | ||
- | |||
- | This RFC seeks to address this limitation. | ||
- | |||
- | ==== Proposal ==== | ||
- | This RFC proposes the ability to define multiple possible types for parameter and return types. To define a 'union type' a single vertical bar (OR) is placed between types e.g. '' | ||
- | |||
- | Additionally this RFC proposes that the values '' | ||
- | |||
- | There can be more than two types in the union. | ||
- | |||
- | === Parameter type examples === | ||
- | A function that requires either a string or an array is passed to it as the parameter: | ||
- | <PHP> | ||
- | function print_each(array | string $in) { | ||
- | foreach ((array) $in as $value) { | ||
- | echo $value, PHP_EOL; | ||
- | } | ||
- | } | ||
- | |||
- | print_each([' | ||
- | print_each(' | ||
- | print_each(new stdclass()); | ||
- | </ | ||
- | For this example, it is clear to both static analysis tools and humans that passing anything other than an array or a string to this function, would be an error. (or will be weakly cast to a string if strict_types are disabled, see also [[# | ||
- | |||
- | A class instance method that requires that either a string or a ParameterGenerator object is passed as the parameter. | ||
- | <PHP> | ||
- | // From zend-code | ||
- | class MethodGenerator extends AbstractMemberGenerator { | ||
- | ... | ||
- | public function setParameter(ParameterGenerator|string $parameter) { | ||
- | ... | ||
- | } | ||
- | } | ||
- | </ | ||
- | For this example, it is clear to both static analysis tools and humans that passing anything other than a ParameterGenerator object or a string to this function, would be an error. | ||
- | |||
- | === Return type example === | ||
- | A userland definition of '' | ||
- | |||
- | <PHP> | ||
- | function stripos(string $haystack, string $needle, int $offset = 0): int|false { | ||
- | $lowerHaystack = strtolower($haystack); | ||
- | $lowerNeedle = strtolower($needle); | ||
- | return strpos($lowerHaystack, | ||
- | } | ||
- | </ | ||
- | For this example, it is clear to both static analysis tools and humans this function can return either an integer or the value ' | ||
- | |||
- | ==== Nullable types ==== | ||
- | To cover the common use-case of returning some type or '' | ||
- | |||
- | <PHP> | ||
- | function lookup_user(string $id): User | null; | ||
- | </ | ||
- | |||
- | This is currently possible via the short-hand nullable type support ''? | ||
- | |||
- | - ''? | ||
- | - Allowing '' | ||
- | - '' | ||
- | |||
- | To address some of these issues this RFC disallows ''?'' | ||
- | |||
- | This RFC proposes a vote on whether ''? | ||
- | |||
- | ==== True/False ==== | ||
- | It may be helpful to be able to explicitly use '' | ||
- | <PHP> | ||
- | // from | ||
- | strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ): mixed | ||
- | // to | ||
- | strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ): int | false | ||
- | </ | ||
- | |||
- | This now allows to perfectly forward any internal signature and allows users to be more explicit. | ||
- | |||
- | Also '' | ||
- | |||
- | This RFC proposes a vote to decide if '' | ||
- | |||
- | ==== Weak Scalar Types ==== | ||
- | === Problem === | ||
- | PHP 7 allows weak scalar types. There is a question of how things will get converted in some situations when used in unions. As an example, if we have a union type of '' | ||
- | |||
- | <PHP> | ||
- | function f(int | float $number) { | ||
- | return $number * 2; | ||
- | } | ||
- | f(" | ||
- | </ | ||
- | |||
- | Would it be converted to '' | ||
- | |||
- | === Solution === | ||
- | Primarily, this issue is avoided if a parameter type exactly matches the input type or if PHP is in strict type mode. | ||
- | |||
- | With the only exception that an '' | ||
- | |||
- | Otherwise PHP's casting rules are applied in an order to be as lossless as possible. PHP's weak-type casting rules are complex, which leads to a seemingly complex set of rules for casting types, however these rules are not an invention of this proposal. This RFC applies PHP casting rules in a sane way to convert a value to a type accepted by the union whenever possible. | ||
- | |||
- | ^ Passed type ^ Union type #1 ^ #2 ^ #3 ^ | ||
- | | object | string (''< | ||
- | | boolean | int | float | string | | ||
- | | int | float | string | boolean | | ||
- | | float | string | int | boolean | | ||
- | | string | int/ | ||
- | < | ||
- | Respecting the order, for each type check if it is available in the union, else throw a '' | ||
- | |||
- | == Problems with left-to-right == | ||
- | Left-to-right conversion has been proposed multiple times. But this is not a viable solution for the following reasons: | ||
- | * (string|float) would convert to a string if passed an integer, which would be inconsistent with strict types converting it to a float. This type of inconsistency must be avoided. | ||
- | * Also, in strict left-to-right, | ||
- | * Ultimately, (float|int) would, even in strict types mode, lead to a conversion to float in any case upon passing integer; this is very counterintuitive. | ||
- | |||
- | It might be possible to exempt exact matches, but then we have yet another rule. At which point it just is much simpler to have well-defined conversion order depending on the passed type. | ||
- | |||
- | ==== Variance ==== | ||
- | Return types are covariant: it is possible to remove types from the union in child functions. | ||
- | |||
- | Parameter types are contravariant: | ||
- | |||
- | <PHP> | ||
- | interface Foo { | ||
- | function pos(string $baz): int | false; | ||
- | } | ||
- | interface Bar extends Foo { | ||
- | function pos(string | Stringable $baz): int; | ||
- | } | ||
- | </ | ||
- | |||
- | ==== Reflection ==== | ||
- | This RFC proposes the addition of < | ||
- | |||
- | ReflectionType:: | ||
- | |||
- | ===== Proposed PHP Version(s) ===== | ||
- | This RFC targets PHP version 7.1. | ||
- | |||
- | ===== Proposed Voting Choices ===== | ||
- | This RFC requires that two-thirds of voters vote in favor of the RFC to pass. | ||
- | |||
- | Additionally, | ||
- | |||
- | * on replacing ''? | ||
- | * and the true/false types (for unions only). | ||
- | |||
- | ===== Patches and Tests ===== | ||
- | Bob Weinand and Joe Watkins have created a patch: https:// | ||
- | |||
- | ===== Future Scope ===== | ||
- | This sections details areas where the feature might be improved in future, but that are not currently proposed in this RFC. | ||
- | |||
- | ==== Long Type Expressions ==== | ||
- | Since you can create a chain of types the names can get quite lengthy. Even the fairly short union type of '' | ||
- | |||
- | <PHP> | ||
- | type Iterable = Array | Traversable; | ||
- | |||
- | function map(Callable $f, Iterable $input): Iterable { | ||
- | foreach ($input as $key => $value) { | ||
- | yield $key => $f($value); | ||
- | } | ||
- | } | ||
- | |||
- | function filter(Callable $f, Iterable $input): Iterable { | ||
- | foreach ($input as $key => $value) { | ||
- | if ($value) { | ||
- | yield $key => $value; | ||
- | } | ||
- | } | ||
- | } | ||
- | </ | ||
- | |||
- | It may also be advantageous for implementation reasons to define a type name for an expression. | ||
- | |||
- | ===== References ===== | ||
- | |||
- | * Original announcement on Mailing List of rationale for this feature: http:// | ||
- | * Official Under Discussion Announcment: |
rfc/union_types.txt · Last modified: 2017/09/22 13:28 (external edit)