rfc:fetch_property_in_const_expressions

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:fetch_property_in_const_expressions [2022/06/02 12:33] – Add ?-> support ilutovrfc:fetch_property_in_const_expressions [2022/07/18 21:57] (current) – Move to implemented ilutov
Line 1: Line 1:
-====== PHP RFC: Fetch properties in const expressions ======+====== PHP RFC: Fetch properties of enums in const expressions ======
   * Date: 2022-05-27   * Date: 2022-05-27
   * Author: Ilija Tovilo <ilutov@php.net>   * Author: Ilija Tovilo <ilutov@php.net>
-  * Status: Under discussion+  * Status: Implemented
   * Proposed Version: PHP 8.2   * Proposed Version: PHP 8.2
   * Implementation: https://github.com/php/php-src/pull/8625   * Implementation: https://github.com/php/php-src/pull/8625
Line 8: Line 8:
 ===== Introduction ===== ===== Introduction =====
  
-This RFC proposes to allow use of ''->''/''?->'' to fetch properties in constant expressions. The primary motivation for this change is to allow fetching the ''name'' and ''value'' properties of enums in places where enum objects aren't allowed, like array keys (see https://github.com/php/php-src/issues/8344). There is currently no way to express this without repeating the value of the enum case.+This RFC proposes to allow the use of ''->''/''?->'' to fetch properties of enums in constant expressions. The primary motivation for this change is to allow fetching the ''name'' and ''value'' properties in places where enum objects aren't allowed, like array keys (see https://github.com/php/php-src/issues/8344). There is currently no way to express this without repeating the value of the enum case.
  
 <PHP> <PHP>
Line 20: Line 20:
 ===== Proposal ===== ===== Proposal =====
  
-This RFC proposes to allow the use of ''->'' to fetch properties inside all constant expressions. As ''->'' may only be used on objects there are two scenarios where ''->'' can be used: +This RFC proposes to allow the use of ''->'' to fetch properties of enums inside all constant expressions. Using ''->'' on all other objects will result in a runtime error.
- +
-  * Enums (for the ''name'' and ''value'' properties) +
-  * ''new'' (for any declared property or magic ''__get'')+
  
 Here are a few examples of code that will become valid if this RFC is accepted: Here are a few examples of code that will become valid if this RFC is accepted:
  
 <PHP> <PHP>
-// Where E::B is a unit enum +enum E: string { 
-const A E::B->name;+    case Foo 'foo'; 
 +}
  
-// Where E::C is a backed enum +const C = E::Foo->name; 
-function a() { + 
-    static $= E::C->value;+function f() { 
 +    static $= E::Foo->value;
 } }
  
-// Where B is a class with a property $c +#[Attr(E::Foo->name)] 
-#[A((new B)->c)] +class {}
-class {}+
  
-// Where C::$d is an object with a property $e +function f
-function a+    $E::Foo->value,
-    $(new C)->d->e+
 ) {} ) {}
 +
 +class C {
 +    public string $p = E::Foo->name;
 +}
  
 // The rhs of -> allows other constant expressions // The rhs of -> allows other constant expressions
-const = 'foo'; +const VALUE = 'value'; 
-const B = (new C)->{A};+class C { 
 +     const C = E::Foo->{VALUE}; 
 +}
 </PHP> </PHP>
- 
-As mentioned, the primary motivation for this feature are enums. However, the implementation for supporting ''new'' is identical and I don't believe arbitrarily restricting how ''->'' can be used in this context makes sense. 
  
 For the sake of completeness, this RFC also adds support for the nullsafe operator ''?->''. For the sake of completeness, this RFC also adds support for the nullsafe operator ''?->''.
Line 56: Line 57:
 ===== Semantics ===== ===== Semantics =====
  
-The semantics of ''->'' in constant expressions are identical to outside of constant expressions, including access on non-object warnings, undefined property warnings, etc.+The semantics of ''->'' in constant expressions are identical to outside of constant expressions, including access on non-object warnings, undefined property warnings, etc. One exception is that in constant expressions the operators may only be used on enums. The rationale for this decision is described in [[https://wiki.php.net/rfc/fetch_property_in_const_expressions#supporting_all_objects|Supporting all objects]].
  
 <PHP> <PHP>
 +enum E {
 +    case Foo;
 +}
 +
 class A {} class A {}
  
-// Warning: Undefined property: A::$c +// Warning: Undefined property: E::$c 
-const (new A)->c; // NULL+const E::Foo->c; // NULL
 // Note that this will change to an error in PHP 9 https://wiki.php.net/rfc/undefined_property_error_promotion // Note that this will change to an error in PHP 9 https://wiki.php.net/rfc/undefined_property_error_promotion
  
 // Warning: Attempt to read property "e" on null // Warning: Attempt to read property "e" on null
-const = (null)->e; // NULL+const = (null)->e; // NULL
  
 // Warning: Attempt to read property "" on null // Warning: Attempt to read property "" on null
 // Fatal error: Uncaught Error: Object of class A could not be converted to string // Fatal error: Uncaught Error: Object of class A could not be converted to string
-const = (null)->{new A};+const = (null)->{new A}
 + 
 +// Error: Fetching properties on non-enums in constant expressions is not allowed 
 +const C = (new A)->foo;
 </PHP> </PHP>
  
Line 76: Line 84:
  
 <PHP> <PHP>
-const = (null)?->b; // NULL +const = (null)?->prop; // NULL 
-const C = (null)?->d->e; // NULL +const C = (null)?->prop1->prop2; // NULL 
-const = (null)?->{new NotInstanciated}; // NULL+const = (null)?->{new NotEvaluated}; // NULL 
 +const C = (null)?->prop['foo']; // NULL 
 +</PHP> 
 + 
 +===== Supporting all objects ===== 
 + 
 +A previous version of this RFC allowed fetching properties on all objects, not just enums. There are two primary reasons support for all object was dropped. 
 + 
 +  - Order of evaluation 
 +  - Caching of constant expression values 
 + 
 +Once we have a solution for these two problems we can extend support of ''->'' in constant expressions to all objects. 
 + 
 +==== Order of evaluation ==== 
 + 
 +This problem was previously described in [[https://wiki.php.net/rfc/new_in_initializers#unsupported_positions|New in initializers RFC - unsupported positions]]. 
 + 
 +<blockquote> 
 +New expressions continue to not be supported in (static and non-static) property initializers and class constant initializers. The reasons for this are twofold: 
 + 
 +For non-static property initializers, the initializer expression needs to be evaluated on each object creation. There are currently two places where this could happen: As part of object creation, and as part of the constructor call. Doing this as part of object creation can create issues for unserialization and any other process that is based on newInstanceWithoutConstructor() and does not want to implicitly execute potential side-effects. 
 + 
 +Performing the initialization by injecting code in the constructor avoids the issue, but requires that constructor to actually be called. In particular, this would necessitate generating constructors for classes that do not explicitly declare them, and the disciplined invocation of such constructors from potential child constructors. The third option would be to introduce an additional initialization phase between creation and construction. 
 + 
 +For static property initializers and class constant initializers a different evaluation order issue arises. Currently, these initializers are evaluated lazily the first time a class is used in a certain way (e.g. instantiated). Once initializers can contain potentially side-effecting expressions, it would be preferable to have a more well-defined evaluation order. However, the straightforward approach of evaluating initilizers when the class is declared would break certain existing code patterns. In particular, referencing a class that is declared later in the same file would no longer work. 
 + 
 +As such support in these contexts is delayed until such a time as a consensus on the preferred behavior can be reached. 
 +</blockquote> 
 + 
 +The ''->'' operator suffers from the same problem as it may invoke the ''%%__get%%'' magic method with arbitrary side-effects. I incorrectly assumed this wasn't possible because ''%%__get%%'' can only be invoked in combination with ''new'' (as enums don't allow ''%%__get%%'') which are disallowed in these contexts. This assumption was incorrect though as the LHS of the ''->'' operator might be another constant which might be an object. 
 + 
 +<PHP> 
 +class Foo { 
 +    public function __get(string $name) { 
 +        echo "Side effect!\n"; 
 +    } 
 +
 + 
 +const FOO = new Foo(); 
 + 
 +class C { 
 +    const C = FOO->bar; 
 +
 +</PHP> 
 + 
 +==== Caching of constant expression values ==== 
 + 
 +The engine caches the result of default arguments between function calls to avoid re-evaluation if result is not ref-counted [1]. This works nicely because there is currently no way to produce a non-pure, non-ref-counted result in a constant expression. This would no longer be true if allowing ''->'' on all objects in constant expressions. 
 + 
 +<PHP> 
 +class Foo { 
 +    public function __get(string $name) { 
 +        return rand(0, 100); 
 +    } 
 +
 + 
 +function test($bar = (new Foo)->bar) { 
 +    var_dump($bar); 
 +
 +</PHP> 
 + 
 +We could remove the caching mechanism from function default arguments but that would negatively hurt performance for a small edge-case. 
 + 
 +Similarly, property initializers are currently only evaluated once per class, and then the result is copied when an instance of the given class is created. Allowing side-effects and non-pure results means we must re-evaluate the property initializer once per instance. 
 + 
 +[1] Ref-counted values consist of non-interned strings, arrays, resources and objects. 
 + 
 +==== Allow all readonly properties ==== 
 + 
 +It was suggested that instead of restricting ''->'' to be used on all non-enums to restrict access to just non-''readonly'' properties. Unfortunately, this doesn't solve the caching problem described above as ''readonly'' properties can be initialized after object construction and thus becomes non-pure. The enum ''name'' and ''value'' properties are automatically initialized by the engine and thus are truly immutable. 
 + 
 +<PHP> 
 +class Foo { 
 +    public readonly string $bar; 
 + 
 +    public function init() { 
 +        $this->bar = 'bar'; 
 +    } 
 +
 + 
 +const FOO = new Foo(); 
 +function test($bar = FOO->bar ?? 'default') {} 
 + 
 +test(); 
 +FOO->init(); 
 +test();
 </PHP> </PHP>
  
Line 95: Line 188:
 ===== Vote ===== ===== Vote =====
  
-Voting opened on xxx and closes on xxx.+Voting opened on 2022-07-01 and closes on 2022-07-15.
  
-<doodle title="Add support for fetching properties in constant expressions?" auth="ilutov" voteType="single" closed="true">+<doodle title="Add support for fetching properties of enums in constant expressions?" auth="ilutov" voteType="single" closed="true">
    * Yes    * Yes
    * No    * No
 </doodle> </doodle>
  
rfc/fetch_property_in_const_expressions.1654173229.txt.gz · Last modified: 2022/06/02 12:33 by ilutov