rfc:union_types
Differences
This shows you the differences between two versions of the page.
rfc:union_types [2016/06/03 12:38] bwoebi [Weak Scalar Types] |
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/ | ||
- | * While string is more lossless than float for big values, we have to match behavior with strict types enabled here\\ | ||
- | < | ||
- | 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 and still the first problem in the list above. 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)