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 [2024/04/07 01:19] – swapped Proposal and Ambiguity sections, improved text vudaltsovrfc:new_without_parentheses [2024/05/28 18:04] (current) – Added Target Version vudaltsov
Line 1: Line 1:
 ====== PHP RFC: new MyClass()->method() without parentheses ====== ====== PHP RFC: new MyClass()->method() without parentheses ======
  
-  * Date: 2024-04-07 +  * 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 15: Line 17:
  
 <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 syntax 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 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]]).
  
 ===== Proposal ===== ===== Proposal =====
  
-This RFC allows to omit parentheses around the ''new'' expression when constructor arguments' parentheses **are** present. Given class+This RFC allows to omit parentheses around the ''new'' expression **with** constructor arguments' parentheses. Given class
  
 <code php> <code php>
-class MyClass+class MyClass extends ArrayObject
 { {
     const CONSTANT = 'constant';     const CONSTANT = 'constant';
Line 60: Line 60:
     new MyClass()->method(),        // string(6)  "method"     new MyClass()->method(),        // string(6)  "method"
     new MyClass()(),                // string(8)  "__invoke"     new MyClass()(),                // string(8)  "__invoke"
 +    new MyClass(['value'])[0],      // string(5)  "value"
 ); );
  
Line 70: Line 71:
     new $myClass()->method(),        // string(6)  "method"     new $myClass()->method(),        // string(6)  "method"
     new $myClass()(),                // string(8)  "__invoke"     new $myClass()(),                // string(8)  "__invoke"
 +    new $myClass(['value'])[0],      // string(5)  "value"
 ); );
  
Line 79: Line 81:
     new (trim(' MyClass '))()->method(),        // string(6)  "method"     new (trim(' MyClass '))()->method(),        // string(6)  "method"
     new (trim(' MyClass '))()(),                // string(8)  "__invoke"     new (trim(' MyClass '))()(),                // string(8)  "__invoke"
 +    new (trim(' MyClass '))(['value'])[0],      // string(5)  "value"
 ); );
 </code> </code>
  
-This RFC still does not allow to omit parentheses around the ''new'' expression **without** constructor arguments' parentheses, because in some cases +This RFC does not change behavior of ''new'' expressions **without** constructor arguments' parentheses:
-this leads to an ambiguity:+
  
 <code php> <code php>
-// Access property on a new instance or instantiate the result of expression? +new MyClass::CONSTANT; // will continue to throw syntax error 
-new MyClass::$staticProperty; +new $myClass::CONSTANT; // will continue to throw a syntax error 
-new $myClass::$staticProperty; +new MyClass::$staticProperty; // will continue to work as `new (MyClass::$staticProperty)` 
-new $myClass->property; +new $myClass::$staticProperty; // will continue to work as `new ($myClass::$staticProperty)` 
- +new $myObject->property; // will continue to work as `new ($myObject->property)` 
-// Access method on new instance or instantiate the result of expression? +new MyArrayConst['class']; // will continue to throw syntax error 
-new $myClass->method();+new $myArray['class']; // will continue to work as `new ($myArray['class'])`
 </code> </code>
  
Line 112: Line 114:
     // string(8) "__invoke"     // string(8) "__invoke"
     new class { public function __invoke() { return '__invoke'; } }(),     new class { public function __invoke() { return '__invoke'; } }(),
 +    // string(5) "value"
 +    new class (['value']) extends ArrayObject {}[0],
 ); );
 </code> </code>
  
-===== Ambiguity? =====+===== Why the proposed syntax is unambiguous =====
  
-At first glance ''new MyClass()->method()'' expression might seem ambiguous. But according to the same logic +At first glance ''new MyClass()->method()'' expression might seem ambiguous. But according to the same logic ''new MyClass()'' is also ambiguous: 
-''new MyClass()'' is also ambiguous: is it ''new (MyClass())'' or ''new MyClass''? However, such ambiguity +is it ''new (MyClass())'' or ''new MyClass''? However, ''new MyClass()'' is unambiguously interpreted as an instantiation 
-does not exist: ''new MyClass()'' is interpreted as an instantiation of class ''MyClass'' with zero +of class ''MyClass'' with zero constructor arguments, not an instantiation of ''MyClass()'' function call result. This 
-constructor arguments, not as instantiation of ''MyClass()'' function call result.+is because PHP interprets the first expression after ''new'' as a class name.
  
-Here's how it is solved at the [[https://github.com/php/php-src/blob/948b2bc2110667cfe53cec253893a3b021f22449/Zend/zend_language_parser.y#L1126|grammar level]]. +Consider also ''MyClass::new()->method()''. It is not interpreted as ''MyClass::(new()->method())'' or ''MyClass::(new())->method()''
-The formula for the ''new'' expression is ''T_NEW class_name|new_variable|(expr) ctor_arguments'', where ''new_variable'' +It is interpreted as ''(MyClass::new())->method()''. It's natural to assume that ''MyClass::new()'' is the first expression to evaluate. 
-is a variable expression without calls. Thus PHP offers 3 ways to provide a class:+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 arguments' parentheses 
-nobody will ever read it as ''MyClass::(new())->method()'' or ''MyClass::(new()->method())''.+are crucial for the proposed syntaxparentheses denote the end of the class name and the end of the new expression. 
 + 
 +===== Other syntax ideas ===== 
 + 
 +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 =====
Line 149: Line 188:
  
 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.1712452777.txt.gz · Last modified: 2024/04/07 01:19 by vudaltsov