rfc:new_in_initializers

Differences

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

Link to this comparison view

Next revision
Previous revision
rfc:new_in_initializers [2021/03/03 10:56]
nikic created
rfc:new_in_initializers [2021/07/14 07:19]
nikic
Line 2: Line 2:
   * Date: 2021-03-02   * Date: 2021-03-02
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Status: Draft+  * Status: Implemented
   * Proposed Version: PHP 8.1   * Proposed Version: PHP 8.1
-  * Implementation: https://github.com/php/php-src/pull/6746+  * Implementation: https://github.com/php/php-src/pull/7153
  
 ===== Introduction ===== ===== Introduction =====
  
-This RFC proposes to allow use of ''new'' expressions inside initializer expressions, including for property and parameter default values.+This RFC proposes to allow use of ''new'' expressions inside parameter default values, attribute arguments, static variable initializers and global constant initializers.
  
 Currently, code such as the following is not permitted: Currently, code such as the following is not permitted:
Line 36: Line 36:
 This makes the actual default value less obvious (from an API contract perspective), and requires the use of a nullable argument. This makes the actual default value less obvious (from an API contract perspective), and requires the use of a nullable argument.
  
-This RFC proposes to relax this restriction and allow the use of ''new'' inside all initializer expressions.+This RFC proposes to relax this restriction and allow the use of ''new'' inside certain initializer expressions.
  
 ===== Proposal ===== ===== Proposal =====
  
-''new'' expressions are allowed as part of initializer expressions. It is possible to pass arguments to the constructor, including the use of named arguments:+''new'' expressions are allowed as part of certain initializer expressions. It is possible to pass arguments to the constructor, including the use of named arguments:
  
 <PHP> <PHP>
Line 52: Line 52:
 </PHP> </PHP>
  
-The use of a dynamic or non-string class name is not allowed. The use of argument unpacking is not allowed. The use of unsupported expressions as arguments is not allowed. +The use of a dynamic or non-string class name or an anonymous class is not allowed. The use of argument unpacking is not allowed. The use of unsupported expressions as arguments is not allowed. 
  
 <PHP> <PHP>
 // All not allowed (compile-time error): // All not allowed (compile-time error):
 function test( function test(
-    $foo = new (CLASS_NAME_CONSTANT)(), // dynamic class name +    $= new (CLASS_NAME_CONSTANT)(), // dynamic class name 
-    $bar = new A(...[]), // argument unpacking +    $b = new class {}, // anonymous class 
-    $baz = new B($abc), // unsupported constant expression+    $c = new A(...[]), // argument unpacking 
 +    $= new B($abc), // unsupported constant expression
 ) {} ) {}
 </PHP> </PHP>
  
-Affected positions are static variable intializers, constant and class constant initializers, static and non-static property intializers, parameter default values, as well as attribute arguments:+New expressions are allowed in parameter default values, attribute arguments, static variable initializers and global class constant initializers. Parameter default values also include defaults for promoted properties:
  
 <PHP> <PHP>
Line 69: Line 70:
  
 const C = new Foo; const C = new Foo;
 +
 +function test($param = new Foo) {}
  
 #[AnAttribute(new Foo)] #[AnAttribute(new Foo)]
 class Test { class Test {
-    const C = new Foo; +    public function __construct( 
-    public static $prop = new Foo; +        public $prop = new Foo, 
-    public $prop = new Foo;+    ) {}
 } }
- 
-function test($param = new Foo) {} 
 </PHP> </PHP>
  
-==== Evaluation of expressions ====+==== Unsupported positions ====
  
-Static variable initializers, constant and class constant initializers, and static property initializers are evaluated once per requestFor constants the evaluaton occurs when they are declared, for other cases evaluation is lazy and the exact time of evaluation is not specified.+New expressions continue to not be supported in (static and non-static) property initializers and class constant initializers. The reasons for this are twofold:
  
-Currentlyall initializers in a class (and its parents) are evaluated together on the first use of the class that may require evaluated initializers (e.g. object instantiationclass constant access or static property access)However, this is not guaranteed behavior, and code should not rely on a specific point of evaluation.+For non-static property initializersthe initializer expression needs to be evaluated on each object creation. There are currently two places where this could happen: As part of object creationand as part of the constructor callDoing 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.
  
-For non-static property default values, parameter default values and attribute arguments, evaluation happens as-if the expression is evaluated on each object instantiationeach function call, or each attribute instantiation respectivelyAny side-effects are observable for each evaluation.+Performing the initialization by injecting code in the constructor avoids the issuebut requires that constructor to actually be calledIn 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.
  
-If the construction of an object fails because the evaluation of a property default value resulted in an exceptionthen the destructor for the object will not be calledThis matches the behavior for exceptions thrown from the constructor.+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. 
 + 
 +==== Order of evaluation ==== 
 + 
 +Initializer expressions could always contain side-effects through autoloaders or error handlers. However, support for ''new'' and the accompanying constructor calls make side-effect more first-class citizen in initializer expressionsso it is worthwhile to specify when and in what order they are evaluated. For the contexts where ''new'' is supported under this proposal: 
 + 
 +  * Static variable initializers are evaluated when control flow reaches the static variable declaration. 
 +  * Global constant initializers are evaluated when control flow reaches the constant declaration. 
 +  * Attribute arguments are evaluated from left to right on every call of ''ReflectionAttribute::getArguments()'' or ''ReflectionAttribute::newInstance()''
 +  * Parameter default values are evaluated from left to right on every call to the function where the parameter is not explicitly passed. 
 + 
 +Additionally, initializers can be accessed through Reflection, in which case the following evaluation semantics apply: 
 + 
 +  * ''ReflectionFunctionAbstract::getStaticVariables()'': Returns the current value of the static variables and also forces evaluation of any initializers that haven't been reached yet. 
 +  * ''ReflectionParameter::getDefaultValue()'': Evaluates the default value (on each call). 
 +  * ''ReflectionParameter::isDefaultValueConstant()'' and ''ReflectionParameter::getDefaultValueConstantName()'': Do not evaluate the default value. 
 +  * ''ReflectionAttribute::getArguments()'' and ''ReflectionAttribute::newInstance()'': Evaluate attribute arguments on each call. 
 + 
 +==== Nested attributes ==== 
 + 
 +It is worth mentioning explicitly that this RFC effectively adds support for nested attributes, which were omitted from the original attributes RFCFor example, attributes of the following form are now possible: 
 + 
 +<PHP> 
 +#[Assert\All(new Assert\NotNull, new Assert\Length(max: 6))] 
 +</PHP>
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
  
-None.+This RFC does not introduce any backwards-incompatible changes, and also should not break any major assumptions. The only case where something genuinely new is possible are nested attributes.
  
 ===== Future Scope ===== ===== Future Scope =====
  
-This RFC is narrow in that it only adds support for ''new'' expressionsHowever, it also lays the technical groundwork for supporting other expressions like calls.+This RFC omits support for ''new'' in property and class constant initializersThese could be supported in the future, once we have figured out the evaluation order issues. 
 + 
 +With the precedent set by ''new'', it would probably also make sense to allow other call expressions. For example, this would allow use of static factor methods.
  
 ===== Vote  ===== ===== Vote  =====
  
-Yes/No.+Voting opened on 2021-06-30 and closes on 2021-07-14. 
 + 
 +<doodle title="Support new in initializers as proposed?" auth="nikic" voteType="single" closed="true"> 
 +   Yes 
 +   No 
 +</doodle>
  
rfc/new_in_initializers.txt · Last modified: 2021/07/14 07:19 by nikic