rfc:typed_properties_v2

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
rfc:typed_properties_v2 [2018/07/16 18:05]
nikic Remove stubs in implementation section
rfc:typed_properties_v2 [2019/01/11 16:16] (current)
nikic Implemented
Line 5: Line 5:
   * Proposed PHP version: PHP 7.4   * Proposed PHP version: PHP 7.4
   * Implementation: https://github.com/php/php-src/pull/3313   * Implementation: https://github.com/php/php-src/pull/3313
-  * Status: Under Discussion+  * Discussion: https://externals.io/message/102333 and https://externals.io/message/103148 
 +  * Status: Implemented (in PHP 7.4)
  
 ===== Introduction ===== ===== Introduction =====
Line 72: Line 73:
     // All types with the exception of "void" and "callable" are supported     // All types with the exception of "void" and "callable" are supported
     public int $scalarType;     public int $scalarType;
-    protected Type $type+    protected ClassName $classType
-    private ?Type $nullableType;+    private ?ClassName $nullableClassType;
          
     // Types are also legal on static properties     // Types are also legal on static properties
Line 95: Line 96:
 For a discussion of the syntax choice, see the Alternatives section. For a discussion of the syntax choice, see the Alternatives section.
  
-The fundamental invariant that is maintained by property type hints, is that a property read will always either return a value that satisfies the typehint, or throw a TypeError. While this sounds straightforward, the idiosyncrasies of the PHP language make enforcing this invariant non-trivial.+The fundamental invariant that is maintained by property type declaration, is that a property read will always either return a value that satisfies the declared type, or throw. While this sounds straightforward, the idiosyncrasies of the PHP language make enforcing this invariant non-trivial.
  
 In the following, the semantics of property type declarations are laid out in detail. In the following, the semantics of property type declarations are laid out in detail.
Line 134: Line 135:
 iterable iterable
 self, parent self, parent
-ClassOrInterface+any class or interface name
 ?type // where "type" may be any of the above ?type // where "type" may be any of the above
 </code> </code>
Line 228: Line 229:
 While textually the property types are the same, the resolved property types for both classes would be ''A'' and ''B'' respectively, which differ. While textually the property types are the same, the resolved property types for both classes would be ''A'' and ''B'' respectively, which differ.
  
-[TODO: The following is not true in the current implementation. We may also want to punt on this in light of issues like [[https://bugs.php.net/bug.php?id=76451|Bug #76451]].] +If two different type declarations to aliased classes are used, they are considered equalif the alias is known at the time of inheritance checking. Depending on the usual early-binding rules, this may either be at compile-time or at run-time. The following code is legal:
- +
-If two different type hints to aliased classes are used, they are considered equal if the alias is known at the time of inheritance checking. Depending on the usual early-binding rules, this may either be at compile-time or at run-time. The following code is legal:+
  
 <code php> <code php>
Line 244: Line 243:
 class B extends A { class B extends A {
     public Bar $prop;     public Bar $prop;
 +}
 +</code>
 +
 +This is subject to the usual limitations affecting aliases during inheritance checks, such as [[https://bugs.php.net/bug.php?id=76451|Bug #76451]].
 +
 +When two traits imported in the same class define the same property, then their property types must match, similar to the existing requirement that the default value must be the same. As such, the following code is invalid:
 +
 +<code php>
 +trait T1 {
 +    public int $prop;
 +}
 +trait T2 {
 +    public string $prop;
 +}
 +class C {
 +    use T1, T2;
 } }
 </code> </code>
Line 687: Line 702:
  
 None. None.
- 
-===== Vote ===== 
- 
-As this is a language change, a 2/3 majority is required. 
- 
-Add support for typed properties as described in this RFC? 
  
 ===== Alternatives ===== ===== Alternatives =====
Line 789: Line 798:
 The third option is to take the visibility of the property into account when performing the callability check. That is, if the property is ''public callable $cb'', then only callables that are callable from public scopes will be considered callable. If the property is ''private callable $cb'', then private methods will also be accepted. The third option is to take the visibility of the property into account when performing the callability check. That is, if the property is ''public callable $cb'', then only callables that are callable from public scopes will be considered callable. If the property is ''private callable $cb'', then private methods will also be accepted.
  
-The advantage of this solution is that it is quite ergonomic and even solves a part of the overall problem of ''callable''. The disadvantage is, apart from introducing special behavior that ''callable'' does not exhibit elsewhere, that this creates a tight coupling between the visibility of the property and its type. For example, this means that increasing the visibility of a property in an inheriting class (''protected callable $cb'' to ''public callable $cb''), an operation that is otherwise always legal, would not be permissible for ''callable'' properties. Even without inheritance, changing a private callable property into a protected one could not require further code modifications, as existing assignments to the property may no longer be legal.+The advantage of this solution is that it is quite ergonomic and even solves a part of the overall problem of ''callable''. The disadvantage is, apart from introducing special behavior that ''callable'' does not exhibit elsewhere, that this creates a tight coupling between the visibility of the property and its type. For example, this means that increasing the visibility of a property in an inheriting class (''protected callable $cb'' to ''public callable $cb''), an operation that is otherwise always legal, would not be permissible for ''callable'' properties. Even without inheritance, changing a private callable property into a protected one could require further code modifications, as existing assignments to the property may no longer be legal.
  
 The fourth option is to automatically wrap assignments to ''callable'' properties into ''Closure::fromCallable()''. This would ensure that any values that were callable at the time of write would remain callable at the time of read. However, if we would like to introduce such a behavior, we believe that it should be introduced for all places where the ''callable'' type may occur, not just typed properties. Furthermore, performing this automatic wrapping would further increase the cost of ''callable'' types. The fourth option is to automatically wrap assignments to ''callable'' properties into ''Closure::fromCallable()''. This would ensure that any values that were callable at the time of write would remain callable at the time of read. However, if we would like to introduce such a behavior, we believe that it should be introduced for all places where the ''callable'' type may occur, not just typed properties. Furthermore, performing this automatic wrapping would further increase the cost of ''callable'' types.
Line 873: Line 882:
 The previous RFC on typed properties did not permit acquiring references to typed properties. This has the significant disadvantage of creating an inconsistency and segregating the language. You can have typed properties, you can have references, but you can't have both. The previous RFC on typed properties did not permit acquiring references to typed properties. This has the significant disadvantage of creating an inconsistency and segregating the language. You can have typed properties, you can have references, but you can't have both.
  
-Specifically, as already mentioned in the main section, it prevents use of internal functions that accept parameters by reference, such as ''sort''. We've also been assured that references to properties play some important and irreplacable role for proxy objects in Doctrine, though the details remain elusive.+Specifically, as already mentioned in the main section, it prevents use of internal functions that accept parameters by reference, such as ''sort''. We've also been assured that references to properties play some important and irreplaceable role for proxy objects in Doctrine, though the details remain elusive.
  
 On the other hand, supporting references to typed properties makes the proposal significantly more complicated. A very large fraction of this proposal text is concerned with the behavior of references, and the large design space surrounding them. Additionally, handling of references also makes up most of the implementation complexity of the proposal. On the other hand, supporting references to typed properties makes the proposal significantly more complicated. A very large fraction of this proposal text is concerned with the behavior of references, and the large design space surrounding them. Additionally, handling of references also makes up most of the implementation complexity of the proposal.
Line 1226: Line 1235:
  
 The different implementations need to be appropriately guarded by PHP version. The different implementations need to be appropriately guarded by PHP version.
 +
 +===== Performance =====
 +
 +Dmitry has performed some benchmarks of the current typed properties implementation. The results are available at https://gist.github.com/dstogov/b9fc0fdccfb8bf7bae121ce3d3ff1db1. (Note: The reported MediaWiki result was not reproducible on repeated runs, the actual slowdown seems to be about 2-3%.)
 +
 +The main takeaway is that typed properties have an impact on performance even if they are not used. For applications, the impact is around 1-2%. We expect that performance will be improved prior to landing, but at least //some// impact is probably not avoidable.
 +
 +===== Vote =====
 +
 +As this is a language change, a 2/3 majority is required.
 +
 +<doodle title="Add support for typed properties as described in this RFC?" auth="bwoebi" voteType="single" closed="true">
 +   * Yes
 +   * No
 +</doodle>
 +
 +===== Errata =====
 +
 +During final implementation work after the RFC was accepted, a number of cases were encountered which weren't explicitly specified in the original RFC text. They are documented here instead.
 +
 +==== Automatic promotion of arrays and objects ====
 +
 +PHP automatically initializes falsy values that are used as arrays or objects to an empty array or ''stdClass'' respectively. When this happens with a typed property or a reference to a typed property, the property type must be compatible with an array or stdClass object.
 +
 +<code php>
 +class Test {
 +    public ?int $prop = null;
 +}
 +
 +$test = new Test;
 +$test->prop[] = 123;       // TypeError, because we can't assign array to ?int
 +$test->prop->foobar = 123; // TypeError, because we can't assign stdClass to ?int
 +
 +$prop =& $test->prop;
 +$prop[] = 123;       // TypeError, because we can't assign array to ?int
 +$prop->foobar = 123; // TypeError, because we can't assign stdClass to ?int
 +</code>
 +
 +==== Strictness of runtime-evaluated default values ====
 +
 +Default values for both parameters and properties always follow the strict typing semantics, independently of the strict typing mode that applies in a particular file. However, there is one exception to this rule: If a constant expression parameter default value cannot be evaluated during compilation, it follows the strictness mode of the file instead:
 +
 +<code php>
 +function foo(int $x = FOO) { // currently allowed
 +    var_dump($x);
 +}
 +define('FOO', '42');
 +foo();
 +</code>
 +
 +For typed properties we do not make such an exception and following code will generate a TypeError:
 +
 +<code php>
 +class Test {
 +    public int $x = FOO; // TypeError
 +}
 +define('FOO', '42');
 +var_dump(new Test);
 +</code>
 +
 +The reason for this choice is that evaluation of constant expressions at compile-time vs run-time is an optimization choice and should not result in behavioral differences. Whether a constant expression is evaluated during compilation depends on many factors, including code order and whether or not opcache is enabled. We believe that the current behavior of parameters is a bug, not an intentional choice.
 +
 +==== Incrementing/decrementing beyond the maximal/minimal value ====
 +
 +When a value is incremented beyond ''PHP_INT_MAX'' or decremented beyond ''PHP_INT_MIN'' it is converted into a floating-point value and incremented/decremented as a floating-point value. Additionally, under PHP's type verification rules (both strict //and// weak), assigning an out-of-range floating point value to an integer is illegal.
 +
 +As stated, this would result in the following peculiar behavior: Incrementing an ''int'' property past the maximal value would always be an error, because ''(float)PHP_INT_MAX + 1'' exceeds the integer range. However, decrementing an ''int'' property past the minimal value would only error on 32-bit systems. The reason is that on 64-bit systems ''(float)PHP_INT_MIN - 1'' is the same as ''(float)PHP_INT_MIN'', which is accurately representable as a double-precision floating point number and as such can be assigned back to an ''int'' property without error.
 +
 +As such, we would always generate an error on increment/decrement overflow, apart from the case of decrements on 64-bit systems.
 +
 +To avoid this, we instead define that incrementing/decrementing an ''int'' property past the maximal/minimal value always generates an error. It should be noted that this only affects the ''++'' and ''%%--%%'' operators. Overflows caused by other means are not handled specially.
  
 ===== Changelog ===== ===== Changelog =====
Line 1231: Line 1311:
 Significant changes to the RFC are noted here. Significant changes to the RFC are noted here.
  
 +  * 2019-01-07: Add errata: Increment/decrement overflow behavior.
 +  * 2019-01-03: Add errata: Strictness of runtime-evaluated default values.
 +  * 2019-01-03: Add errata: Automatic promotion of arrays and objects.
 +  * 2018-07-16: Add note about compatibility requirement on properties coming from traits.
   * 2018-07-10: Add shim header to make porting extension easy.   * 2018-07-10: Add shim header to make porting extension easy.
   * 2018-07-10: Note that write_property signature has changed.   * 2018-07-10: Note that write_property signature has changed.
rfc/typed_properties_v2.1531764343.txt.gz · Last modified: 2018/07/16 18:05 by nikic