rfc:new_without_parentheses

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:new_without_parentheses [2023/12/29 15:09] – Improved some sentences vudaltsovrfc:new_without_parentheses [2024/05/28 18:04] (current) – Added Target Version vudaltsov
Line 2: Line 2:
  
   * Date: 2023-12-29   * Date: 2023-12-29
-  * Author: Valentin Udaltsov <vudaltsov@php.net> +  * Author: Valentin Udaltsov (udaltsov.valentin@gmail.com) 
-  * Status: Draft+  * Status: Implemented 
 +  * Target Version: PHP 8.4 
 +  * Discussion: https://externals.io/message/123031
   * First Published at: http://wiki.php.net/rfc/new_without_parentheses   * First Published at: http://wiki.php.net/rfc/new_without_parentheses
   * Implementation: https://github.com/php/php-src/pull/13029   * Implementation: https://github.com/php/php-src/pull/13029
Line 9: Line 11:
 ===== Introduction ===== ===== Introduction =====
  
-The "Class member access on instantiation" feature+The "class member access on instantiation" feature
 [[https://php-legacy-docs.zend.com/manual/php5/en/migration54.new-features#:~:text=Class%20member%20access%20on%20instantiation%20has%20been%20added%2C%20e.g.%20(new%20Foo)%2D%3Ebar().|was introduced in PHP 5.4.0]]. [[https://php-legacy-docs.zend.com/manual/php5/en/migration54.new-features#:~:text=Class%20member%20access%20on%20instantiation%20has%20been%20added%2C%20e.g.%20(new%20Foo)%2D%3Ebar().|was introduced in PHP 5.4.0]].
-Since then methods, properties and constants can be accessed on a newly created instance without an intermediate variable, +Since then constants, properties and methods can be accessed on a newly created instance without an intermediate 
-but **only** if the new expression is wrapped in parentheses:+variable, but **only** if the ''new'' expression is wrapped in parentheses:
  
 <code php> <code php>
-class MyClass+class Request implements Psr\Http\Message\RequestInterface
 { {
-    public function method(): void +    // ...
-    { +
-        echo 'Hello, World!'; +
-    }+
 } }
  
-(new MyClass())->method()// Hello, World!+// OK 
 +$request = (new Request())->withMethod('GET')->withUri('/hello-world');
  
-new MyClass()->method(); // PHP Parse error: syntax error, unexpected token "->"+// PHP Parse error: syntax error, unexpected token "->" 
 +$request = new Request()->withMethod('GET')->withUri('/hello-world');
 </code> </code>
  
-The goal of this RFC is to enable the second sytax without parentheses to: +The goal of this RFC is to enable the second syntax to: 
-  * make coding with PHP more convenient, +  * make coding with PHP more convenient and satisfy many users' requests for this feature (see [[https://externals.io/message/66197|externals#66197]][[https://bugs.php.net/bug.php?id=70549|bugs.php#70549]][[https://externals.io/message/101811|externals#101811]], [[https://externals.io/message/113953|externals#113953]]) 
-  * lower the learning curve+  * decrease the visual debt in all sorts of builders and configurators ([[https://github.com/search?q=path%3A*.php+language%3APHP+%2F%5C%28%5Cs*new%5Cs%2B%5B%5E%5C%28%5D%2B%5Cs*%5C%28%5B%5E%5C%29%5D*%5C%29%5Cs*%5C%29%5Cs*-%3E%2F&type=code&ref=advsearch|more than half a million lines of open source PHP code could be simplified]]) 
-  * decrease the visual debt, +  * ease switching between other C-like languages that don't require parentheses ([[https://onecompiler.com/java/3zxuee3bm|Java]], [[https://onecompiler.com/csharp/3zxudhw4s|C#]], [[https://onecompiler.com/typescript/3zxudfpyy|TypeScript]]).
-  * ease transition from other C-like languages that don't require parentheses ([[https://onecompiler.com/java/3zxuee3bm|Java]], [[https://onecompiler.com/csharp/3zxudhw4s|C#]], [[https://onecompiler.com/typescript/3zxudfpyy|TypeScript]]).+
  
-===== Ambiguity? =====+===== Proposal =====
  
-At first glance ''new MyClass()->method()'' expression might seem ambiguous. But according to the same logic +This RFC allows to omit parentheses around the ''new'' expression **with** constructor arguments' parenthesesGiven class
-''new MyClass()'' is also ambiguous: is it ''new (MyClass())'' or ''new MyClass''? However, such ambiguity +
-does not exist: ''new MyClass()'' is interpreted as an instantiation of class ''MyClass'' with zero +
-constructor arguments, not as instantiation of ''MyClass()'' function call result.+
  
-Here's how it is solved at the grammar level. The formula for the ''new'' expression is +<code php> 
-''T_NEW class_name|new_variable|(expr) ctor_arguments'', where ''new_variable'' is a variable expression without calls. +class MyClass extends ArrayObject 
-Thus PHP offers 3 ways to provide a class:+
 +    const CONSTANT = 'constant'; 
 +    public static $staticProperty = 'staticProperty'; 
 +    public static function staticMethod(): string { return 'staticMethod';
 +    public $property = 'property'; 
 +    public function method(): string { return 'method';
 +    public function __invoke(): string { return '__invoke';
 +
 +</code> 
 + 
 +one will be able to write 
 + 
 +<code php> 
 +var_dump( 
 +    new MyClass()::CONSTANT,        // string(8)  "constant" 
 +    new MyClass()::$staticProperty, // string(14) "staticProperty" 
 +    new MyClass()::staticMethod(),  // string(12) "staticMethod" 
 +    new MyClass()->property,        // string(8)  "property" 
 +    new MyClass()->method(),        // string(6)  "method" 
 +    new MyClass()(),                // string(8)  "__invoke" 
 +    new MyClass(['value'])[0],      // string(5)  "value" 
 +); 
 + 
 +$myClass = MyClass::class; 
 +var_dump( 
 +    new $myClass()::CONSTANT,        // string(8)  "constant" 
 +    new $myClass()::$staticProperty, // string(14) "staticProperty" 
 +    new $myClass()::staticMethod(),  // string(12) "staticMethod" 
 +    new $myClass()->property,        // string(8)  "property" 
 +    new $myClass()->method(),        // string(6)  "method" 
 +    new $myClass()(),                // string(8)  "__invoke" 
 +    new $myClass(['value'])[0],      // string(5)  "value" 
 +); 
 + 
 +var_dump( 
 +    new (trim(' MyClass '))()::CONSTANT,        // string(8)  "constant" 
 +    new (trim(' MyClass '))()::$staticProperty, // string(14) "staticProperty" 
 +    new (trim(' MyClass '))()::staticMethod(),  // string(12) "staticMethod" 
 +    new (trim(' MyClass '))()->property,        // string(8)  "property" 
 +    new (trim(' MyClass '))()->method(),        // string(6)  "method" 
 +    new (trim(' MyClass '))()(),                // string(8)  "__invoke" 
 +    new (trim(' MyClass '))(['value'])[0],      // string(5)  "value" 
 +); 
 +</code> 
 + 
 +This RFC does not change behavior of ''new'' expressions **without** constructor arguments' parentheses: 
 + 
 +<code php> 
 +new MyClass::CONSTANT; // will continue to throw a syntax error 
 +new $myClass::CONSTANT; // will continue to throw a syntax error 
 +new MyClass::$staticProperty; // will continue to work as `new (MyClass::$staticProperty)` 
 +new $myClass::$staticProperty; // will continue to work as `new ($myClass::$staticProperty)` 
 +new $myObject->property; // will continue to work as `new ($myObject->property)` 
 +new MyArrayConst['class']; // will continue to throw a syntax error 
 +new $myArray['class']; // will continue to work as `new ($myArray['class'])` 
 +</code> 
 + 
 +This RFC allows to omit parentheses around the ''new'' anonymous class expression regardless of whether constructor arguments' 
 +parentheses are present or not: 
 + 
 +<code php> 
 +var_dump( 
 +    // string(8) "constant" 
 +    new class { const CONSTANT = 'constant'; }::CONSTANT, 
 +    // string(14) "staticProperty" 
 +    new class { public static $staticProperty = 'staticProperty'; }::$staticProperty, 
 +    // string(12) "staticMethod" 
 +    new class { public static function staticMethod() { return 'staticMethod'; } }::staticMethod(), 
 +    // string(8) "property" 
 +    new class { public $property = 'property'; }->property, 
 +    // string(6) "method" 
 +    new class { public function method() { return 'method'; } }->method(), 
 +    // string(8) "__invoke" 
 +    new class { public function __invoke() { return '__invoke'; } }(), 
 +    // string(5) "value" 
 +    new class (['value']) extends ArrayObject {}[0], 
 +); 
 +</code> 
 + 
 +===== Why the proposed syntax is unambiguous ===== 
 + 
 +At first glance ''new MyClass()->method()'' expression might seem ambiguous. But according to the same logic ''new MyClass()'' is also ambiguous: 
 +is it ''new (MyClass())'' or ''new MyClass''? However, ''new MyClass()'' is unambiguously interpreted as an instantiation 
 +of class ''MyClass'' with zero constructor arguments, not an instantiation of ''MyClass()'' function call result. This 
 +is because PHP interprets the first expression after ''new'' as a class name. 
 + 
 +Consider also ''MyClass::new()->method()''. It is not interpreted as ''MyClass::(new()->method())'' or ''MyClass::(new())->method()''
 +It is interpreted as ''(MyClass::new())->method()''. It's natural to assume that ''MyClass::new()'' is the first expression to evaluate. 
 +The same is true for ''new MyClass()->method()'': expression ''new MyClass()'' is also the first to evaluate. 
 + 
 +Here's how it looks at the [[https://github.com/php/php-src/blob/948b2bc2110667cfe53cec253893a3b021f22449/Zend/zend_language_parser.y#L1126|grammar level]]. 
 +The formula for the ''new'' expression with explicit constructor arguments' parentheses is ''T_NEW class_name|new_variable|(expr) ctor_arguments'', 
 +where ''new_variable'' is a variable expression without calls.
  
 <code php> <code php>
-// class_name 
 new MyClass(); new MyClass();
 +^        ^
 +|        |
 +|—T_NEW    |—ctor_arguments
 +    |
 +    |—class_name
 +
  
-// new_variable 
 new $class(); new $class();
 +^       ^
 +|       |
 +|—T_NEW   |—ctor_arguments
 +    |
 +    |—new_variable (cannot have calls!)
  
-// (expr) 
 new (trim(' MyClass '))(); new (trim(' MyClass '))();
 +^                    ^
 +|                    |
 +|—T_NEW                |—ctor_arguments
 +    |
 +    |—(expr)
 </code> </code>
  
-This guarantees that the ''new'' expression is unambiguous: once the parser encounters ''T_NEW'', it considers +Once the parser encounters ''T_NEW'', it considers whatever comes next a class name, not a part of another expression. 
-whatever comes next a class name. Hence it's safe and unambiguous to further use the ''new'' expression on its +This guarantees that the ''new'' expression with arguments' parentheses can be unambiguously used as a separate expression 
-own without parentheses. It's like refactoring ''(MyClass::new())->method()'' to ''MyClass::new()->method()''.+without any additional parentheses. It also becomes clear why ''new_variable'' cannot have calls and why argumentsparentheses 
 +are crucial for the proposed syntaxparentheses denote the end of the class name and the end of the new expression.
  
-===== Proposal =====+===== Other syntax ideas =====
  
-TODO+Some of the ideas expressed during the discussion of this RFC are listed below. They are orthogonal to this proposal and 
 +require a separate RFC. 
 + 
 +==== Allow to omit the new keyword ==== 
 + 
 +Some languages like Kotlin allow to instantiate classes via [[https://onecompiler.com/kotlin/42b54ds4u|MyClass() expression]]. 
 +Omitting the "new" keyword in PHP is currently not possible, because classes and functions can [[https://3v4l.org/mfUVp|have the same name]] 
 +(in Kotlin [[https://onecompiler.com/kotlin/42b579yhu|it is not possible]]). So, in order to achieve this in PHP, we should first 
 +deprecate declaring functions and classes with the same names. 
 + 
 +==== MyClass::new(), MyClass::create() ==== 
 + 
 +Introducing a dedicated static constructor like ''MyClass::new()'' or ''MyClass::create()'' would be a backward compatibility 
 +break for already existing static or object methods named ''new'' or ''create'' with required parameters and/or return 
 +type different from ''static''. So this idea also requires some deprecations.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
  
-None.+None. Any code that is valid before the change will be valid after and interpreted in the same way.
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
  
 PHP 8.4 PHP 8.4
 +
 +===== Proposed Voting Choices =====
 +
 +This is a simple yes-or-no vote to include this feature. 2/3 majority required to pass. 
 +
 +Voting started on 2024-05-09 and will end on 2024-05-24 00:00 GMT.
 +
 +<doodle title="Allow to omit parentheses around the new expression as described?" auth="vudaltsov" voteType="single" closed="true" closeon="2024-05-24T00:00:00Z">
 +   * Yes
 +   * No
 +</doodle>
 +
 +===== Implementation =====
 +
 +Pull request contains the final implementation and plenty of tests asserting the expected behavior and backward compatibility: https://github.com/php/php-src/pull/13029
 +
 +===== References =====
 +
 +  * [[https://externals.io/message/123031|This RFC discussion thread]]
 +
 +Old requests for this feature:
 +
 +  * [[https://externals.io/message/66197|Allow (...)->foo() expressions not only for `new` (see end of the page)]]
 +  * [[https://bugs.php.net/bug.php?id=70549|Allow `new Foo()->bar()` without parens around `(new Foo)`]]
 +  * [[https://externals.io/message/101811|Suggested change: change priority of new and ->]]
 +  * [[https://externals.io/message/113953|Raising the precedence of the new operator]]
 +
  
rfc/new_without_parentheses.1703862592.txt.gz · Last modified: 2023/12/29 15:09 by vudaltsov