rfc:fetch_property_in_const_expressions
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:fetch_property_in_const_expressions [2022/06/02 12:39] – Add short-circuiting DIM example ilutov | rfc:fetch_property_in_const_expressions [2022/07/15 18:14] – Move to accepted ilutov | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Fetch properties in const expressions ====== | + | ====== PHP RFC: Fetch properties |
* Date: 2022-05-27 | * Date: 2022-05-27 | ||
* Author: Ilija Tovilo < | * Author: Ilija Tovilo < | ||
- | * Status: | + | * Status: |
* Proposed Version: PHP 8.2 | * Proposed Version: PHP 8.2 | ||
* Implementation: | * Implementation: | ||
Line 8: | Line 8: | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | This RFC proposes to allow use of '' | + | This RFC proposes to allow the use of '' |
<PHP> | <PHP> | ||
Line 20: | Line 20: | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | This RFC proposes to allow the use of '' | + | This RFC proposes to allow the use of '' |
- | + | ||
- | * Enums (for the '' | + | |
- | * '' | + | |
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:: | + | case Foo = ' |
+ | } | ||
- | // Where E::C is a backed enum | + | const C = E::Foo-> |
- | function | + | |
- | static $b = E::C->value; | + | function |
+ | static $v = E::Foo->value; | ||
} | } | ||
- | // Where B is a class with a property $c | + | #[Attr(E::Foo->name)] |
- | #[A((new B)->c)] | + | class C {} |
- | class D {} | + | |
- | // Where C::$d is an object with a property $e | + | function |
- | function | + | $p = E::Foo->value, |
- | $b = (new C)->d->e | + | |
) {} | ) {} | ||
+ | |||
+ | class C { | ||
+ | public string $p = E:: | ||
+ | } | ||
// The rhs of -> allows other constant expressions | // The rhs of -> allows other constant expressions | ||
- | const A = 'foo'; | + | const VALUE = 'value'; |
- | const B = (new C)->{A}; | + | class C { |
+ | const C = E::Foo->{VALUE}; | ||
+ | } | ||
</ | </ | ||
- | |||
- | As mentioned, the primary motivation for this feature are enums. However, the implementation for supporting '' | ||
For the sake of completeness, | For the sake of completeness, | ||
Line 56: | Line 57: | ||
===== Semantics ===== | ===== Semantics ===== | ||
- | The semantics of '' | + | The semantics of '' |
<PHP> | <PHP> | ||
+ | enum E { | ||
+ | case Foo; | ||
+ | } | ||
+ | |||
class A {} | class A {} | ||
- | // Warning: Undefined property: | + | // Warning: Undefined property: |
- | const B = (new A)->c; // NULL | + | const C = E::Foo->c; // NULL |
// Note that this will change to an error in PHP 9 https:// | // Note that this will change to an error in PHP 9 https:// | ||
// Warning: Attempt to read property " | // Warning: Attempt to read property " | ||
- | const D = (null)-> | + | const C = (null)-> |
// Warning: Attempt to read property "" | // Warning: Attempt to read property "" | ||
// 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 F = (null)-> | + | const C = (null)-> |
+ | |||
+ | // Error: Fetching properties on non-enums in constant expressions is not allowed | ||
+ | const C = (new A)->foo; | ||
</ | </ | ||
Line 76: | Line 84: | ||
<PHP> | <PHP> | ||
- | const A = (null)? | + | const C = (null)? |
- | const C = (null)? | + | const C = (null)? |
- | const F = (null)? | + | const C = (null)? |
- | const G = (null)? | + | const C = (null)? |
+ | </ | ||
+ | |||
+ | ===== 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 '' | ||
+ | |||
+ | ==== Order of evaluation ==== | ||
+ | |||
+ | This problem was previously described in [[https:// | ||
+ | |||
+ | < | ||
+ | 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, | ||
+ | |||
+ | 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, | ||
+ | |||
+ | As such support in these contexts is delayed until such a time as a consensus on the preferred behavior can be reached. | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | |||
+ | < | ||
+ | class Foo { | ||
+ | public function __get(string $name) { | ||
+ | echo "Side effect!\n"; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | const FOO = new Foo(); | ||
+ | |||
+ | class C { | ||
+ | const C = FOO-> | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== 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 '' | ||
+ | |||
+ | < | ||
+ | class Foo { | ||
+ | public function __get(string $name) { | ||
+ | return rand(0, 100); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function test($bar = (new Foo)-> | ||
+ | var_dump($bar); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | < | ||
+ | class Foo { | ||
+ | public readonly string $bar; | ||
+ | |||
+ | public function init() { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | const FOO = new Foo(); | ||
+ | function test($bar = FOO->bar ?? ' | ||
+ | |||
+ | test(); | ||
+ | FOO-> | ||
+ | test(); | ||
</ | </ | ||
Line 96: | Line 188: | ||
===== Vote ===== | ===== Vote ===== | ||
- | Voting opened on xxx and closes on xxx. | + | Voting opened on 2022-07-01 |
- | <doodle title=" | + | <doodle title=" |
* Yes | * Yes | ||
* No | * No | ||
</ | </ | ||
rfc/fetch_property_in_const_expressions.txt · Last modified: 2022/07/18 21:57 by ilutov