From bf47cb743046b708379e8c4cdc83937aa4e60880 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 2 Sep 2019 15:43:37 +0200 Subject: [PATCH 01/24] Union types proposal --- rfcs/0000-union-types-v2.md | 388 ++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 rfcs/0000-union-types-v2.md diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md new file mode 100644 index 0000000..aa39558 --- /dev/null +++ b/rfcs/0000-union-types-v2.md @@ -0,0 +1,388 @@ + * Name: `union_types_v2` + * Date: 2019-09-02 + * Author: Nikita Popov + * Proposed Version: PHP 8.0 + * RFC PR: [php-src/rfcs#0000](https://github.com/php-src/rfcs/pull/0000) + +# Introduction + +A "union type" accepts values of multiple different types, rather than a single one. PHP already supports two special union types: + + * `Type` or `null`, using the special `?Type` syntax. + * `array` or `Traversable`, using the special `iterable` type. + +However, arbitrary union types are currently not supported by the language. Instead, phpdoc annotations have to be used, such as in the following example: + +```php +class Number { + /** + * @var int|float $number + */ + private $number; + + /** + * @param int|float $number + */ + public function setNumber($number) { + $this->number = $number; + } + + /** + * @return int|float + */ + public function getNumber() { + return $this->number; + } +} +``` + +The [statistics section](#statistics) shows that the use of union types is indeed pervasive in the open-source ecosystem, as well as PHP's own standard library. + +Supporting union types in the language allows us to move more type information from phpdoc into function signatures, with the usual advantages this brings: + + * Types are actually enforced, so mistakes can be caught early. + * Because they are enforced, type information is less likely to become outdated or miss edge-cases. + * Types are checked during inheritance, enforcing the Liskov Substitution Principle. + * Types are available through Reflection. + * The syntax is a lot less boilerplate-y than phpdoc. + +After generics, union types are currently the largest "hole" in our type declaration system. + +# Proposal + +Union types are specified using the syntax `T1|T2|...` and can be used in all positions where types are currently accepted: + +```php +class Number { + private int|float $number; + + public function setNumber(int|float $number): void { + $this->number = $number; + } + + public function getNumber(): int|float { + return $this->number; + } +} +``` + +## Supported Types + +Union types support all types currently supported by PHP, with some caveats outlined in the following. + +### `void` type + +The `void` type can never be part of a union. As such, types like `T|void` are illegal in all positions, including return types. + +The `void` type indicates that the function has no return value, and enforces that argument-less `return;` is used to return from the function. It is fundamentally incompatible with non-void return types. + +What is likely intended instead is `?T`, which allows returning either `T` or `null`. + +### `false` pseudo-type + +While we nowadays encourage the use of `null` over `false` as an error or absence return value, for historical reasons many internal functions continue to use `false` instead. As shown in the [statistics section](#statistics), the vast majority of union return types for internal functions include `false`. + +A classical example is the `strpos()` family of functions, which returns `int|false`. + +While it would be possible to model this less accurately as `int|bool`, this gives the false impression that the function can also return a `true` value, which makes this type information significantly less useful to humans and static analyzers both. + +For this reason, support for the `false` pseudo-type is included in this proposal. A `true` pseudo-type is *not* part of the proposal, because similar historical reasons for its necessity do not exist. + +The `false` pseudo-type cannot be used as a standalone type, it can only be used as part of a union. + +### Nullable union types + +Nullable types in PHP use the syntax `?T`. Nullable union types are required to be written as `?(T1|T2)`, while variations such as `?T1|T2`, `T1|?T2` or even `?T1|?T2` are disallowed. This avoids issues of "symmetry" where the nullability is syntactically associated with a specific type, while in reality it could equally apply to all. + +An alternative would be to allow an explicit `null` type for use in unions only, such that the above example could be written `T1|T2|null`, and the existing syntax `?T` could be written `T|null`. While this might have been a good option if union types were introduced earlier, I think it is preferable not to introduce a second type nullability syntax at this point in time. + +### Duplicate types + +Each literal type may only occur once inside a union. As such, types like `int|string|INT` are not permitted. Additionally, only one of `false` and `bool` may be used. + +This is a purely syntactical restriction that is intended to catch simple bugs in the type specification. It does not ensure that the union type is in some sense "minimal". + +For example, if `A` and `B` are class aliases, then `A|B` remains a legal union type, even though it could be reduced to either `A` or `B`. Similarly, if `class B extends A {}`, then `A|B` is also a legal union type, even though it could be reduced to just `A`. Detecting these cases would require loading all types at the point of declaration. + +### Type grammar + +Excluding the special `void` type, PHP's type syntax may now be described by the following grammar: + +``` +type: simple_type + | union_type + | "?" simple_type + | "?" "(" union_type ")" + ; + +union_type: simple_type "|" simple_type + | union_type "|" simple_type + ; + +simple_type: "false" # only legal in unions + | "bool" + | "int" + | "float" + | "string" + | "array" + | "object" + | "iterable" + | "callable" # not legal in property types + | "self" + | "parent" + | namespaced_name + ; +``` + +At this point in time, parentheses in types are only allowed in the one case where they are necessary, which is the `?(T1|T2|...)` syntax. With further extensions to the type system (such as intersection types) it may make sense to allow parentheses in more arbitrary positions. + +## Variance + +Union types follow the existing variance rules: + + * Return types are covariant (child must be subtype). + * Parameter types are contravariant (child must be supertype). + * Property types are invariant (child must be subtype and supertype). + +The only change is in how union types interact with subtyping. A union `U_1|...|U_n` is a subtype of `V_1|...|V_n` if for each `U_i` there exists a `V_j` such that `U_i` is a subtype of `V_j`. + +Additionally, the `iterable` type is considered to be the same (i.e. both subtype and supertype) as `array|Traversable`. + +In the following, some examples of what is allowed and what isn't are given. + +### Property types + +Property types are invariant, which means that types must stay the same during inheritance. However, the "same" type may be expressed in different ways. Prior to union types, one such possibility was to have two aliased classes `A` and `B`, in which case a property type may legally change from `A` to `B` or vice versa. + +Union types expand the possibilities in this area: For example `int|string` and `string|int` represent the same type. The following example shows a more complex case: + +```php +class A {} +class B extends A {} + +class Test { + public A|B $prop; +} +class Test2 extends Test { + public A $prop; +} +``` + +In this example, the union `A|B` actually represents the same type as just `A`, and this inheritance is legal, despite the type not being syntactically the same. + +Formally, we arrive at this result as follows: First, `A` is a subtype of `A|B`, because it is a subtype of `A`. Second, `A|B` is a subtype of `A`, because `A` is a subtype of `A` and `B` is a subtype of `A`. + +### Adding and removing union types + +It is legal to remove union types in return position and add union types in parameter position: + +```php +class Test { + public function param1(int $param) {} + public function param2(int|float $param) {} + + public function return1(): int|float {} + public function return2(): int {} +} + +class Test2 extends Test { + public function param1(int|float $param) {} // Allowed: Adding extra param type + public function param2(int $param) {} // FORBIDDEN: Removing param type + + public function return1(): int {} // Allowed: Removing return type + public function return2(): int|float {} // FORBIDDEN: Adding extra return type +} +``` + +### Variance of individual union members + +Similarly, it is possible to restrict a union member in return position, or widen a union member in parameter position: + +```php +class A {} +class B extends A {} + +class Test { + public function param1(B|string $param) {} + public function param2(A|string $param) {} + + public function return1(): A|string {} + public function return2(): B|string {} +} + +class Test2 extends Test { + public function param1(A|string $param) {} // Allowed: Widening union member B -> A + public function param2(B|string $param) {} // FORBIDDEN: Restricting union member A -> B + + public function return1(): B|string {} // Allowed: Restricting union member A -> B + public function return2(): A|string {} // FORBIDDEN: Widening union member B -> A +} +``` + +Of course, the same can also be done with multiple union members at a time, and be combined with the addition/removal of types mentioned previously. + +## Coercive typing mode + +When `strict_types` is not enabled, scalar type declarations are subject to limited implicit type coercions. These are problematic in conjunction with union types, because it is not always obvious which type the input should be converted to. For example, when passing a boolean to an `int|string` argument, both `0` and `""` would be viable coercion candidates. + +If the exact type of the value is not part of the union, then the target type is chosen in the following order of preference: + +1. `int` +2. `float` +3. `string` +4. `bool` + +If the type both exists in the union, and the value can be coerced to the type under PHPs existing type checking semantics, then the type is chosen. Otherwise the next type is tried. + +As an exception, if the value is a string and both `int` and `float` are part of the union, the preferred type is determined by the existing "numeric string" semantics. For example, for `"42"` we choose `int`, while for `"42.0"` we choose `float`. + +### Examples + +```php +// int|string +42 --> 42 // exact type +"42" --> "42" // exact type +new ObjectWithToString --> "Result of __toString()" + // object never compatible with int, fall back to string +42.0 --> 42 // float compatible with int +42.1 --> 42 // float compatible with int +1e100 --> "1.0E+100" // float too large for int type, fall back to string +INF --> "INF" // float too large for int type, fall back to string +true --> 1 // bool compatible with int +[] --> TypeError // array not compatible with int or string + +// int|float|bool +"45" --> 45 // int numeric string +"45.0" --> 45.0 // float numeric string +"45X" --> 45 + Notice: Non well formed numeric string + // int numeric string +"" --> false // not numeric string, fall back to bool +"X" --> true // not numeric string, fall back to bool +[] --> TypeError // array not compatible with int, float or bool +``` + +## Property types and references + +References to typed properties with union types follow the semantics outlined in the [typed properties RFC](https://wiki.php.net/rfc/typed_properties_v2#general_semantics): + +> If typed properties are part of the reference set, then the value is checked against each property type. If a type check fails, a TypeError is generated and the value of the reference remains unchanged. +> +> There is one additional caveat: If a type check requires a coercion of the assigned value, it may happen that all type checks succeed, but result in different coerced values. As a reference can only have a single value, this situation also leads to a TypeError. + +The [interaction with union types](https://wiki.php.net/rfc/typed_properties_v2#future_interaction_with_union_types) was already considered at the time, because it impacts the the detailed reference semantics. Repeating the example given there: + +```php +class Test { + public int|string $x; + public float|string $y; +} +$test = new Test; +$r = "foobar"; +$test->x =& $r; +$test->y =& $r; + +// Reference set: { $r, $test->x, $test->y } +// Types: { mixed, int|string, float|string } + +$r = 42; // TypeError +``` + +The basic issue is that the final assigned value (after type coercions have been performed) must be compatible with all types that are part of the reference set. However, in this case the coerced value will be `int(42)` for property `Test::$x`, while it will be `float(42.0)` for property `Test::$y`. Because these values are not the same, this is considered illegal and a `TypeError` is thrown. + +An alternative approach would be to cast the value to the only common type `string` instead, with the major disadvantage that this matches *neither* of the values you would get from a direct property assignment. + +## Reflection + +To support union types, a new class `ReflectionUnionType` is added: + +```php +class ReflectionUnionType extends ReflectionType { + /** @return ReflectionType[] */ + public function getTypes(); + + /** @return bool */ + public function allowsNull(); + + /** @return string */ + public function __toString(); +} +``` + +The `getTypes()` method returns an array of `ReflectionType`s that are part of the union. The types may be returned in an arbitrary order that does not match the original type declaration. The types may also be subject to equivalence transformations. + +For example, the type `int|string` may return types in the order `["string", "int"]` instead. The type `iterable|array|string` might be canonicalized to `iterable|string` or `Traversable|array|string`. The only requirement on the Reflection API is that the ultimately represented type is equivalent. + +The `allowsNull()` method returns whether the union additionally contains the type null. A possible alternative would be to introduce a separate `ReflectionNullableType` to represent the `?T` wrapper explicitly. I would prefer this, but we would probably not be able to use it for non-union types for backwards compatibility reasons. + +The `__toString()` method returns a string representation of the type that constitus a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. Notably this *will* contain the leading `?` for nullable types and not be bug-compatible with `ReflectionNamedType`. + +# Backwards Incompatible Changes + +This RFC does not contain any backwards incompatible changes. However, existing ReflectionType based code will have to be adjusted in order to support processing of code that uses union types. + +# Future Scope + +## Intersection Types + +Intersection types are logically conjugated with union types. Instead of requiring that (at least) a single type constraints is satisfied, all of them must be. + +For example `Traversable|Countable` requires that the passed value is either `Traversable` or `Countable`, while `Traversable&Countable` requires that it is both. + +## Mixed Type + +The `mixed` type allows to explicitly annotate that any value is acceptable. While specifying no type has the same behavior on the surface, it does not make clear whether the type is simply missing (because nobody bothered adding it yet, or because it can't be added for backwards compatibility reasons), or whether genuinely any value is acceptable. + +We've held off on adding a `mixed` type out of fear that it would be used in cases where a more specific union could have been specified. Once union types are supported, it would probably also make sense to add the `mixed` type. + +## Literal Types + +The `false` pseudo-type introduced in this RFC is a special case of a "literal type", such as supported by [TypeScript](https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types). They allow specifying enum-like types, which are limited to specific values. + +```php +type ArrayFilterFlags = 0|ARRAY_FILTER_USE_KEY|ARRAY_FILTER_USE_BOTH; +array_filter(array $array, callable $callback, ArrayFilterFlags $flag): array; +``` + +Proper enums are likely a better solution to this problem space, though depending on the implementation they may not be retrofitted to existing functions for backwards-compatibility reasons. + +## Type Aliases + +As types become increasingly complex, it may be worthwhile to allow reusing type declarations. There are two general ways in which this could work. One is a local alias, such as: + +```php +use int|float as number; + +function foo(number $x) {} +``` + +In this case `number` is a symbol that is only visible locally and will be resolved to the original `int|float` type during compilation. + +The second possibility is an exported typedef: + +```php +namespace Foo; +type number = int|float; + +// Usable as \Foo\number from elsewhere +``` + +# Proposed Voting Choices + +Simple yes/no vote. + +# Statistics + +To illustrate the use of union types in the wild, the use of union types in `@param` and `@return` annotations in phpdoc comments has been analyzed. + +In the top two thousand composer packages there are: + + * 25k parameter union types: [Full JSON data](https://gist.github.com/nikic/64ff90c5038522606643eac1259a9dae#file-param_union_types-json) + * 14k return union types: [Full JSON data](https://gist.github.com/nikic/64ff90c5038522606643eac1259a9dae#file-return_union_types-json) + +In the PHP stubs for internal functions (these are incomplete right now, so the actual numbers should be at least twice as large) there are: + + * 336 union return types + * of which 312 include `false` as a value + +This illustrates that the `false` pseudo-type in unions is nececessary to express the return type of many existing internal functions. + From 16a72d9b2345460bf03827057a57f7fe03881bcb Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 10:45:09 +0200 Subject: [PATCH 02/24] Fix typos Co-Authored-By: Gabriel Caruso --- rfcs/0000-union-types-v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index aa39558..7319d24 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -269,7 +269,7 @@ References to typed properties with union types follow the semantics outlined in > > There is one additional caveat: If a type check requires a coercion of the assigned value, it may happen that all type checks succeed, but result in different coerced values. As a reference can only have a single value, this situation also leads to a TypeError. -The [interaction with union types](https://wiki.php.net/rfc/typed_properties_v2#future_interaction_with_union_types) was already considered at the time, because it impacts the the detailed reference semantics. Repeating the example given there: +The [interaction with union types](https://wiki.php.net/rfc/typed_properties_v2#future_interaction_with_union_types) was already considered at the time, because it impacts the detailed reference semantics. Repeating the example given there: ```php class Test { @@ -384,5 +384,5 @@ In the PHP stubs for internal functions (these are incomplete right now, so the * 336 union return types * of which 312 include `false` as a value -This illustrates that the `false` pseudo-type in unions is nececessary to express the return type of many existing internal functions. +This illustrates that the `false` pseudo-type in unions is necessary to express the return type of many existing internal functions. From 8839f654923a56b8c589aff24e76a8d5323ad92d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 10:45:44 +0200 Subject: [PATCH 03/24] Fix another typo --- rfcs/0000-union-types-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 7319d24..3e2cc36 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -314,7 +314,7 @@ For example, the type `int|string` may return types in the order `["string", "in The `allowsNull()` method returns whether the union additionally contains the type null. A possible alternative would be to introduce a separate `ReflectionNullableType` to represent the `?T` wrapper explicitly. I would prefer this, but we would probably not be able to use it for non-union types for backwards compatibility reasons. -The `__toString()` method returns a string representation of the type that constitus a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. Notably this *will* contain the leading `?` for nullable types and not be bug-compatible with `ReflectionNamedType`. +The `__toString()` method returns a string representation of the type that constitutes a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. Notably this *will* contain the leading `?` for nullable types and not be bug-compatible with `ReflectionNamedType`. # Backwards Incompatible Changes From cf76009e2b153ba02f29a025f820bef86ae16f1f Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 13:00:11 +0200 Subject: [PATCH 04/24] false is a subtype of bool --- rfcs/0000-union-types-v2.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 3e2cc36..4136e00 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -144,9 +144,11 @@ Union types follow the existing variance rules: * Parameter types are contravariant (child must be supertype). * Property types are invariant (child must be subtype and supertype). -The only change is in how union types interact with subtyping. A union `U_1|...|U_n` is a subtype of `V_1|...|V_n` if for each `U_i` there exists a `V_j` such that `U_i` is a subtype of `V_j`. +The only change is in how union types interact with subtyping, with three additional rules: -Additionally, the `iterable` type is considered to be the same (i.e. both subtype and supertype) as `array|Traversable`. + * A union `U_1|...|U_n` is a subtype of `V_1|...|V_n` if for each `U_i` there exists a `V_j` such that `U_i` is a subtype of `V_j`. + * The `iterable` type is considered to be the same (i.e. both subtype and supertype) as `array|Traversable`. + * The `false` pseudo-type is considered a subtype of `bool`. In the following, some examples of what is allowed and what isn't are given. From cf59fa5964f987d21c7d038d41791b80bd9e9b4d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 13:12:50 +0200 Subject: [PATCH 05/24] Mention alternatives for handling of type coercions --- rfcs/0000-union-types-v2.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 4136e00..54f673a 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -263,6 +263,14 @@ true --> 1 // bool compatible with int [] --> TypeError // array not compatible with int, float or bool ``` +### Alternatives + +There are two main alternatives to the preference-based approach used by this proposal: + +The first is to specify that union types *always* use strict typing, thus avoiding any complicated coercion semantics altogether. Apart from the inconsistency this introduces in the language, this has two main disadvantages: First going from a type like `int` to `int|float` would actually *reduce* the number of valid inputs, which is highly unintuitive. Second, it breaks the variance model for union types, because we can no longer say that `int` is a subtype of `int|float`. + +The second is to perform the coercions based on the order of types. This would mean that `int|string` and `string|int` are distinct types, where the former would favor integers and the latter strings. Depending on whether exact type matches are still prioritized, the string type would *always* be used for the latter case. Once again, this is unintuitive and has very unclear implications for the subtyping relationship on which variance is based. + ## Property types and references References to typed properties with union types follow the semantics outlined in the [typed properties RFC](https://wiki.php.net/rfc/typed_properties_v2#general_semantics): From 256535b7f09239a106331374dc63028fd73029b1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 15:40:15 +0200 Subject: [PATCH 06/24] Note methods that are inherited from ReflectionTypes --- rfcs/0000-union-types-v2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 54f673a..6411487 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -310,9 +310,11 @@ class ReflectionUnionType extends ReflectionType { /** @return ReflectionType[] */ public function getTypes(); + /* Inherited from ReflectionType */ /** @return bool */ public function allowsNull(); + /* Inherited from ReflectionType */ /** @return string */ public function __toString(); } From 5bdd11ddb82cb6d4873accac0ccc03a16350cc1b Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 4 Sep 2019 15:45:50 +0200 Subject: [PATCH 07/24] Add reflection examples --- rfcs/0000-union-types-v2.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 6411487..4efd5ab 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -328,6 +328,25 @@ The `allowsNull()` method returns whether the union additionally contains the ty The `__toString()` method returns a string representation of the type that constitutes a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. Notably this *will* contain the leading `?` for nullable types and not be bug-compatible with `ReflectionNamedType`. +### Examples + +```php +function test(): float|int {} +function test2(): ?(float|int) {} + +// This is one possible output, getTypes() and __toString() could +// also provide the types in the reverse order instead. +$rt = new ReflectionFunction('test')->getReturnType(); +var_dump($rt->allowsNull()); // false +var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] +var_dump((string) $rt); // "int|float" + +$rt2 = new ReflectionFunction('test')->getReturnType(); +var_dump($rt->allowsNull()); // true +var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] +var_dump((string) $rt); // "?(int|float)" +``` + # Backwards Incompatible Changes This RFC does not contain any backwards incompatible changes. However, existing ReflectionType based code will have to be adjusted in order to support processing of code that uses union types. From 1d13bf1210323496141f08b19c45e0aedbbc04d7 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 5 Sep 2019 11:13:46 +0200 Subject: [PATCH 08/24] Clarify that "use" is part of duplicate detection --- rfcs/0000-union-types-v2.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 4efd5ab..afd1d34 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -98,12 +98,22 @@ An alternative would be to allow an explicit `null` type for use in unions only, ### Duplicate types -Each literal type may only occur once inside a union. As such, types like `int|string|INT` are not permitted. Additionally, only one of `false` and `bool` may be used. +Each name-resolved type may only occur once inside a union. As such, types like `int|string|INT` are not permitted. Additionally, only one of `false` and `bool` may be used. This is a purely syntactical restriction that is intended to catch simple bugs in the type specification. It does not ensure that the union type is in some sense "minimal". For example, if `A` and `B` are class aliases, then `A|B` remains a legal union type, even though it could be reduced to either `A` or `B`. Similarly, if `class B extends A {}`, then `A|B` is also a legal union type, even though it could be reduced to just `A`. Detecting these cases would require loading all types at the point of declaration. +```php +function foo(): int|INT {} // Disallowed + +use A as B; +function foo(): A|B {} // Disallowed ("use" is part of name resolution) + +class_alias('X', 'Y'); +function foo(): X|Y {} // Allowed (redundancy is only known at runtime) +``` + ### Type grammar Excluding the special `void` type, PHP's type syntax may now be described by the following grammar: From 452daa5685b9c7c684ea3233873d22d58760a4b7 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 5 Sep 2019 11:17:10 +0200 Subject: [PATCH 09/24] Clarify that future scope is future scope... --- rfcs/0000-union-types-v2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index afd1d34..ed48475 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -363,6 +363,8 @@ This RFC does not contain any backwards incompatible changes. However, existing # Future Scope +The features discussed in the following are **not** part of this proposal. + ## Intersection Types Intersection types are logically conjugated with union types. Instead of requiring that (at least) a single type constraints is satisfied, all of them must be. From 12eb8e40dddf6dd99df402f8fefcd76c3a2c9180 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 5 Sep 2019 11:23:48 +0200 Subject: [PATCH 10/24] Update RFC header --- rfcs/0000-union-types-v2.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index ed48475..c3d9fd5 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -2,7 +2,8 @@ * Date: 2019-09-02 * Author: Nikita Popov * Proposed Version: PHP 8.0 - * RFC PR: [php-src/rfcs#0000](https://github.com/php-src/rfcs/pull/0000) + * RFC PR: [php/php-rfcs#0001](https://github.com/php/php-rfcs/pull/1) + * Mailing list thread: https://externals.io/message/106844 # Introduction From f8bee2184b5c857f4efc7443b61548ffbc0ec773 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 5 Sep 2019 11:25:32 +0200 Subject: [PATCH 11/24] Fix typos in reflection examples --- rfcs/0000-union-types-v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index c3d9fd5..a58069c 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -347,12 +347,12 @@ function test2(): ?(float|int) {} // This is one possible output, getTypes() and __toString() could // also provide the types in the reverse order instead. -$rt = new ReflectionFunction('test')->getReturnType(); +$rt = (new ReflectionFunction('test'))->getReturnType(); var_dump($rt->allowsNull()); // false var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] var_dump((string) $rt); // "int|float" -$rt2 = new ReflectionFunction('test')->getReturnType(); +$rt2 = (new ReflectionFunction('test2'))->getReturnType(); var_dump($rt->allowsNull()); // true var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] var_dump((string) $rt); // "?(int|float)" From 698cbd3d18d0c887eb680df1e2d1a4b8988b36b8 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 15:17:14 +0200 Subject: [PATCH 12/24] Also mention object|T as a redundant type pair --- rfcs/0000-union-types-v2.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index a58069c..94690d6 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -97,16 +97,21 @@ Nullable types in PHP use the syntax `?T`. Nullable union types are required to An alternative would be to allow an explicit `null` type for use in unions only, such that the above example could be written `T1|T2|null`, and the existing syntax `?T` could be written `T|null`. While this might have been a good option if union types were introduced earlier, I think it is preferable not to introduce a second type nullability syntax at this point in time. -### Duplicate types +### Duplicate and redundant types -Each name-resolved type may only occur once inside a union. As such, types like `int|string|INT` are not permitted. Additionally, only one of `false` and `bool` may be used. +To catch some simple bugs in union type declarations, redundant types that can be detected without performing class loading will result in a compile-time error. This includes: -This is a purely syntactical restriction that is intended to catch simple bugs in the type specification. It does not ensure that the union type is in some sense "minimal". + * Each name-resolved type may only occur once. Types like `int|string|INT` result in an error. + * If `bool` is used, `false` cannot be used additionally. + * If `object` is used, class types cannot be used additionally. -For example, if `A` and `B` are class aliases, then `A|B` remains a legal union type, even though it could be reduced to either `A` or `B`. Similarly, if `class B extends A {}`, then `A|B` is also a legal union type, even though it could be reduced to just `A`. Detecting these cases would require loading all types at the point of declaration. +This does not guarantee that the type is "minimal", because doing so would require loading all used class types. + +For example, if `A` and `B` are class aliases, then `A|B` remains a legal union type, even though it could be reduced to either `A` or `B`. Similarly, if `class B extends A {}`, then `A|B` is also a legal union type, even though it could be reduced to just `A`. ```php function foo(): int|INT {} // Disallowed +function foo(): bool|false {} // Disallowed use A as B; function foo(): A|B {} // Disallowed ("use" is part of name resolution) From 0a2782bd1293c6e669b2c35df38fec354d42c949 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 15:42:09 +0200 Subject: [PATCH 13/24] Use T1|T2|null instead of ?(T1|T2) --- rfcs/0000-union-types-v2.md | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 94690d6..f03fc08 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -93,9 +93,15 @@ The `false` pseudo-type cannot be used as a standalone type, it can only be used ### Nullable union types -Nullable types in PHP use the syntax `?T`. Nullable union types are required to be written as `?(T1|T2)`, while variations such as `?T1|T2`, `T1|?T2` or even `?T1|?T2` are disallowed. This avoids issues of "symmetry" where the nullability is syntactically associated with a specific type, while in reality it could equally apply to all. +The `null` type is supported as part of unions, such that `T1|T2|null` can be used to create a nullable union. The existing `?T` notation is considered a shorthand for the common case of `T|null`. -An alternative would be to allow an explicit `null` type for use in unions only, such that the above example could be written `T1|T2|null`, and the existing syntax `?T` could be written `T|null`. While this might have been a good option if union types were introduced earlier, I think it is preferable not to introduce a second type nullability syntax at this point in time. +An earlier version of this RFC proposed to use `?(T1|T2)` for nullable union types instead, to avoid having two ways of expressing nullability in PHP. However, this notation is both rather awkward syntactically, and differs from the well-established `T1|T2|null` syntax used by phpdoc comments. The discussion feedback was overwhelmingly in favor of supporting the `T1|T2|null` notation. + +`?T` remains valid syntax that denotes the same type as `T|null`. It is neither discouraged nor deprecated, and there are no plans to deprecate it in the future. It is merely a shorthand alias for a particularly common union type. + +The `null` type is only allowed as part of a union, and can not be used as a standalone type. Allowing it as a standalone type would make both `function foo(): void` and `function foo(): null` legal function signatures, with similar but not identical semantics. This would negatively impact teachability for an unclear benefit. + +Union types and the `?T` nullable type notation cannot be mixed. Writing `?T1|T2`, `T1|?T2` or `?(T1|T2)` is not supported and `T1|T2|null` needs to be used instead. I'm open to permitting the `?(T1|T2)` syntax though, if this is considered desirable. ### Duplicate and redundant types @@ -126,9 +132,8 @@ Excluding the special `void` type, PHP's type syntax may now be described by the ``` type: simple_type - | union_type | "?" simple_type - | "?" "(" union_type ")" + | union_type ; union_type: simple_type "|" simple_type @@ -136,6 +141,7 @@ union_type: simple_type "|" simple_type ; simple_type: "false" # only legal in unions + | "null" # only legal in unions | "bool" | "int" | "float" @@ -340,27 +346,38 @@ The `getTypes()` method returns an array of `ReflectionType`s that are part of t For example, the type `int|string` may return types in the order `["string", "int"]` instead. The type `iterable|array|string` might be canonicalized to `iterable|string` or `Traversable|array|string`. The only requirement on the Reflection API is that the ultimately represented type is equivalent. -The `allowsNull()` method returns whether the union additionally contains the type null. A possible alternative would be to introduce a separate `ReflectionNullableType` to represent the `?T` wrapper explicitly. I would prefer this, but we would probably not be able to use it for non-union types for backwards compatibility reasons. +The `allowsNull()` method returns whether the union contains the type `null`. + +The `__toString()` method returns a string representation of the type that constitutes a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. -The `__toString()` method returns a string representation of the type that constitutes a valid code representation of the type in a non-namespaced context. It is not necessarily the same as what was used in the original code. Notably this *will* contain the leading `?` for nullable types and not be bug-compatible with `ReflectionNamedType`. +For backwards-compatibility reasons, union types that only include `null` and one other type (written as `?T`, `T|null`, or through implicit parameter nullability), will instead use `ReflectionNamedType`. ### Examples ```php -function test(): float|int {} -function test2(): ?(float|int) {} - // This is one possible output, getTypes() and __toString() could // also provide the types in the reverse order instead. +function test(): float|int {} $rt = (new ReflectionFunction('test'))->getReturnType(); +var_dump(get_class($rt)); // "ReflectionUnionType" var_dump($rt->allowsNull()); // false -var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] -var_dump((string) $rt); // "int|float" +var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] +var_dump((string) $rt); // "int|float" + +function test2(): float|int|null {} +$rt = (new ReflectionFunction('test2'))->getReturnType(); +var_dump(get_class($rt)); // "ReflectionUnionType" +var_dump($rt->allowsNull()); // true +var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float"), + // ReflectionType("null")] +var_dump((string) $rt); // "int|float|null" -$rt2 = (new ReflectionFunction('test2'))->getReturnType(); +function test3(): int|null {} +$rt = (new ReflectionFunction('test3'))->getReturnType(); +var_dump(get_class($rt)); // "ReflectionNamedType" var_dump($rt->allowsNull()); // true -var_dump($rt->getTypes()); // [ReflectionType("int"), ReflectionType("float")] -var_dump((string) $rt); // "?(int|float)" +var_dump($rt->getName()); // "int" +var_dump((string) $rt); // "int" (deprecated) ``` # Backwards Incompatible Changes From 931afdd6af4144a1ef815835af04362d44fdf0c1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 15:57:28 +0200 Subject: [PATCH 14/24] Add type conversion table Provided by Claude Pache. --- rfcs/0000-union-types-v2.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index f03fc08..d5d001a 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -260,6 +260,18 @@ If the type both exists in the union, and the value can be coerced to the type u As an exception, if the value is a string and both `int` and `float` are part of the union, the preferred type is determined by the existing "numeric string" semantics. For example, for `"42"` we choose `int`, while for `"42.0"` we choose `float`. +### Conversion Table + +The following table shows how the above order of preference plays out for different input types, assuming that the exact type is not part of the union: + +Original type | 1st try | 2nd try | 3rd try +--------|-------|--------|------- +bool | int | float | string +int | float | string | bool +float | int | string | bool +string | int/float | bool +object | string + ### Examples ```php From 60fc4d08aa4e04db36e6d30a590fabd5f0704ef2 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 18:15:33 +0200 Subject: [PATCH 15/24] Fix typo in variance description Co-Authored-By: Alexander M. Turek --- rfcs/0000-union-types-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index d5d001a..ac5df13 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -168,7 +168,7 @@ Union types follow the existing variance rules: The only change is in how union types interact with subtyping, with three additional rules: - * A union `U_1|...|U_n` is a subtype of `V_1|...|V_n` if for each `U_i` there exists a `V_j` such that `U_i` is a subtype of `V_j`. + * A union `U_1|...|U_n` is a subtype of `V_1|...|V_m` if for each `U_i` there exists a `V_j` such that `U_i` is a subtype of `V_j`. * The `iterable` type is considered to be the same (i.e. both subtype and supertype) as `array|Traversable`. * The `false` pseudo-type is considered a subtype of `bool`. From 3622f8c58229c0c9c290f9921f830dafa56d07fd Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 18:34:32 +0200 Subject: [PATCH 16/24] s/int/float in coercive typing alternative example --- rfcs/0000-union-types-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index ac5df13..f4b05b0 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -301,7 +301,7 @@ true --> 1 // bool compatible with int There are two main alternatives to the preference-based approach used by this proposal: -The first is to specify that union types *always* use strict typing, thus avoiding any complicated coercion semantics altogether. Apart from the inconsistency this introduces in the language, this has two main disadvantages: First going from a type like `int` to `int|float` would actually *reduce* the number of valid inputs, which is highly unintuitive. Second, it breaks the variance model for union types, because we can no longer say that `int` is a subtype of `int|float`. +The first is to specify that union types *always* use strict typing, thus avoiding any complicated coercion semantics altogether. Apart from the inconsistency this introduces in the language, this has two main disadvantages: First going from a type like `float` to `int|float` would actually *reduce* the number of valid inputs, which is highly unintuitive. Second, it breaks the variance model for union types, because we can no longer say that `float` is a subtype of `int|float`. The second is to perform the coercions based on the order of types. This would mean that `int|string` and `string|int` are distinct types, where the former would favor integers and the latter strings. Depending on whether exact type matches are still prioritized, the string type would *always* be used for the latter case. Once again, this is unintuitive and has very unclear implications for the subtyping relationship on which variance is based. From edcc2df3fb9bcb676c742d626e1e1600ab92c634 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 6 Sep 2019 18:55:25 +0200 Subject: [PATCH 17/24] Also mention iterable + array/Traversable as redundant types --- rfcs/0000-union-types-v2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index f4b05b0..b5d063c 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -110,6 +110,7 @@ To catch some simple bugs in union type declarations, redundant types that can b * Each name-resolved type may only occur once. Types like `int|string|INT` result in an error. * If `bool` is used, `false` cannot be used additionally. * If `object` is used, class types cannot be used additionally. + * If `iterable` is used, `array` and `Traversable` cannot be used additionally. This does not guarantee that the type is "minimal", because doing so would require loading all used class types. From 7829b45b5dacf961ae3bfd659da873272801aa1a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 18 Sep 2019 15:01:33 +0200 Subject: [PATCH 18/24] Remove obsolete note about parentheses --- rfcs/0000-union-types-v2.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index b5d063c..112f8c3 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -157,8 +157,6 @@ simple_type: "false" # only legal in unions ; ``` -At this point in time, parentheses in types are only allowed in the one case where they are necessary, which is the `?(T1|T2|...)` syntax. With further extensions to the type system (such as intersection types) it may make sense to allow parentheses in more arbitrary positions. - ## Variance Union types follow the existing variance rules: From 76736e5bef42ad54691b32e98b6f6c9453250ba8 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 18 Sep 2019 15:03:30 +0200 Subject: [PATCH 19/24] Swap sections on nullable types and false types --- rfcs/0000-union-types-v2.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 112f8c3..e397757 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -79,18 +79,6 @@ The `void` type indicates that the function has no return value, and enforces th What is likely intended instead is `?T`, which allows returning either `T` or `null`. -### `false` pseudo-type - -While we nowadays encourage the use of `null` over `false` as an error or absence return value, for historical reasons many internal functions continue to use `false` instead. As shown in the [statistics section](#statistics), the vast majority of union return types for internal functions include `false`. - -A classical example is the `strpos()` family of functions, which returns `int|false`. - -While it would be possible to model this less accurately as `int|bool`, this gives the false impression that the function can also return a `true` value, which makes this type information significantly less useful to humans and static analyzers both. - -For this reason, support for the `false` pseudo-type is included in this proposal. A `true` pseudo-type is *not* part of the proposal, because similar historical reasons for its necessity do not exist. - -The `false` pseudo-type cannot be used as a standalone type, it can only be used as part of a union. - ### Nullable union types The `null` type is supported as part of unions, such that `T1|T2|null` can be used to create a nullable union. The existing `?T` notation is considered a shorthand for the common case of `T|null`. @@ -103,6 +91,18 @@ The `null` type is only allowed as part of a union, and can not be used as a sta Union types and the `?T` nullable type notation cannot be mixed. Writing `?T1|T2`, `T1|?T2` or `?(T1|T2)` is not supported and `T1|T2|null` needs to be used instead. I'm open to permitting the `?(T1|T2)` syntax though, if this is considered desirable. +### `false` pseudo-type + +While we nowadays encourage the use of `null` over `false` as an error or absence return value, for historical reasons many internal functions continue to use `false` instead. As shown in the [statistics section](#statistics), the vast majority of union return types for internal functions include `false`. + +A classical example is the `strpos()` family of functions, which returns `int|false`. + +While it would be possible to model this less accurately as `int|bool`, this gives the false impression that the function can also return a `true` value, which makes this type information significantly less useful to humans and static analyzers both. + +For this reason, support for the `false` pseudo-type is included in this proposal. A `true` pseudo-type is *not* part of the proposal, because similar historical reasons for its necessity do not exist. + +The `false` pseudo-type cannot be used as a standalone type, it can only be used as part of a union. + ### Duplicate and redundant types To catch some simple bugs in union type declarations, redundant types that can be detected without performing class loading will result in a compile-time error. This includes: From 36dcf3873ae6d107998ac67296bf585387571304 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 18 Sep 2019 15:06:23 +0200 Subject: [PATCH 20/24] Clarify that "false|null" and friends are also not allowed --- rfcs/0000-union-types-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index e397757..7edd84c 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -101,7 +101,7 @@ While it would be possible to model this less accurately as `int|bool`, this giv For this reason, support for the `false` pseudo-type is included in this proposal. A `true` pseudo-type is *not* part of the proposal, because similar historical reasons for its necessity do not exist. -The `false` pseudo-type cannot be used as a standalone type, it can only be used as part of a union. +The `false` pseudo-type cannot be used as a standalone type (including nullable standalone type). As such, all of `false`, `false|null` and `?false` are not permitted. ### Duplicate and redundant types From fc3b6a59b855a8d2e00a395954d54a1377dcdea7 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 18 Sep 2019 15:09:03 +0200 Subject: [PATCH 21/24] Clarify that no implicit coercions to "false" occur --- rfcs/0000-union-types-v2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 7edd84c..c627a20 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -259,6 +259,8 @@ If the type both exists in the union, and the value can be coerced to the type u As an exception, if the value is a string and both `int` and `float` are part of the union, the preferred type is determined by the existing "numeric string" semantics. For example, for `"42"` we choose `int`, while for `"42.0"` we choose `float`. +Types that are not part of the above preference list are not eligible targets for implicit coercion. In particular no implicit coercions to the `null` and `false` types occur. + ### Conversion Table The following table shows how the above order of preference plays out for different input types, assuming that the exact type is not part of the union: From 6b29bdfa6e14411c09eef0a60d4a2a40e330314c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 18 Sep 2019 15:21:06 +0200 Subject: [PATCH 22/24] Slightly expand literal types section --- rfcs/0000-union-types-v2.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index c627a20..8dce58e 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -422,7 +422,12 @@ type ArrayFilterFlags = 0|ARRAY_FILTER_USE_KEY|ARRAY_FILTER_USE_BOTH; array_filter(array $array, callable $callback, ArrayFilterFlags $flag): array; ``` -Proper enums are likely a better solution to this problem space, though depending on the implementation they may not be retrofitted to existing functions for backwards-compatibility reasons. +A benefit of using a union of literal types instead of an enum, is that it works directly with values of the underlying type, rather than an opaque enum value. As such, it is easier to retrofit without breaking backwards-compatibility. + +This RFC intentionally supports the `false` type in a maximally restricted form, which is enough to model internal function return values, but avoids unnecessarily constraining a future proposal for introducing first-class literal types. In particular: + + * No values implicitly coerce to `false`, while it would also be possible to follow `bool` parameter coercion semantics, restricted to input values that coerce to `false`. Both approaches have advantages, but we pick the conservative option, which permits future extension, here. + * Only `false` is supported, but not `true`. Once both are supported, the subtyping relationship between `false|true` and `bool` needs to be defined (which is also tightly related to the question of implicit coercions). ## Type Aliases From 19a12d0e215595d8118ac40c05d3b9f3dcb6c98a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 25 Oct 2019 14:54:06 +0200 Subject: [PATCH 23/24] Update reflection output for recent master change --- rfcs/0000-union-types-v2.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 8dce58e..5d91566 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -4,6 +4,7 @@ * Proposed Version: PHP 8.0 * RFC PR: [php/php-rfcs#0001](https://github.com/php/php-rfcs/pull/1) * Mailing list thread: https://externals.io/message/106844 + * Implementation: [php/php-src#4838](https://github.com/php/php-src/pull/4838) # Introduction @@ -390,7 +391,7 @@ $rt = (new ReflectionFunction('test3'))->getReturnType(); var_dump(get_class($rt)); // "ReflectionNamedType" var_dump($rt->allowsNull()); // true var_dump($rt->getName()); // "int" -var_dump((string) $rt); // "int" (deprecated) +var_dump((string) $rt); // "?int" ``` # Backwards Incompatible Changes From edf6ac2af9988389397419042afaae010d23532e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 8 Nov 2019 10:26:11 +0100 Subject: [PATCH 24/24] Add link to final RFC document --- rfcs/0000-union-types-v2.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/0000-union-types-v2.md b/rfcs/0000-union-types-v2.md index 5d91566..ccd813a 100644 --- a/rfcs/0000-union-types-v2.md +++ b/rfcs/0000-union-types-v2.md @@ -2,6 +2,7 @@ * Date: 2019-09-02 * Author: Nikita Popov * Proposed Version: PHP 8.0 + * **Final RFC and Vote**: https://wiki.php.net/rfc/union_types_v2 * RFC PR: [php/php-rfcs#0001](https://github.com/php/php-rfcs/pull/1) * Mailing list thread: https://externals.io/message/106844 * Implementation: [php/php-src#4838](https://github.com/php/php-src/pull/4838)