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 22:31] – Added Proposal section 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 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 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? ===== +
- +
-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, 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 [[https://github.com/php/php-src/blob/948b2bc2110667cfe53cec253893a3b021f22449/Zend/zend_language_parser.y#L1126|grammar level]]. +
-The formula for the ''new'' expression is ''T_NEW class_name|new_variable|(expr) ctor_arguments'', where ''new_variable'' +
-is a variable expression without calls. Thus PHP offers 3 ways to provide a class: +
- +
-<code php> +
-// class_name +
-new MyClass(); +
- +
-// new_variable +
-new $class(); +
- +
-// (expr) +
-new (trim(' MyClass '))(); +
-</code> +
- +
-This guarantees that the ''new'' expression is unambiguous: once the parser encounters ''T_NEW'', it considers +
-whatever comes next a class name. Hence it's safe and unambiguous to further use the ''new'' expression on its +
-own without parentheses. It's like refactoring ''(MyClass::new())->method()'' to ''MyClass::new()->method()'', +
-nobody will ever read it as ''MyClass::(new())->method()'' or ''MyClass::(new()->method())''.+
  
 ===== Proposal ===== ===== Proposal =====
  
-This RFC allows to omit parentheses around ''new'' expressions **with** constructor arguments' parentheses:+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 76: Line 49:
 } }
 </code> </code>
 +
 +one will be able to write
  
 <code php> <code php>
Line 85: 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 95: 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 104: 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 preserves grammar for ''new'' expressions **without** constructor arguments' parentheses, because in some cases +This RFC does not change behavior of ''new'' expressions **without** constructor arguments' parentheses:
-omitting parentheses around the ''new'' expression will result in a valid expression with different meaning:+
  
 <code php> <code php>
-(new MyClass)::$staticProperty; // Access static property on MyClass instance +new MyClass::CONSTANT; // will continue to throw a syntax error 
-new MyClass::$staticProperty;   // Instantiate class MyClass::$staticProperty +new $myClass::CONSTANT; // will continue to throw a syntax error 
- +new MyClass::$staticProperty; // will continue to work as `new (MyClass::$staticProperty)` 
-(new $myClass)::$staticProperty; // Access static property on an instance of class $myClass +new $myClass::$staticProperty; // will continue to work as `new ($myClass::$staticProperty)` 
-new $myClass::$staticProperty;   // Instantiate class $myClass::$staticProperty +new $myObject->property; // will continue to work as `new ($myObject->property)` 
- +new MyArrayConst['class']; // will continue to throw a syntax error 
-(new $myClass)->property; // Access non-static property on an instance of class $myClass +new $myArray['class']; // will continue to work as `new ($myArray['class'])`
-new $myClass->property;   // Instantiate class $myClass->property +
- +
-(new $myClass)->method(); // Call method on an instance of class $myClass +
-new $myClass->method();   // Instantiate class $myClass->method+
 </code> </code>
  
-This RFC allows to omit parentheses around any ''new'' anonymous class expressions regardless of constructor arguments' +This RFC allows to omit parentheses around the ''new'' anonymous class expression regardless of whether constructor arguments' 
-parentheses:+parentheses are present or not:
  
 <code php> <code php>
Line 141: 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>
 +
 +===== 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>
 +new MyClass();
 +^        ^
 +|        |
 +|—T_NEW    |—ctor_arguments
 +    |
 +    |—class_name
 +
 +
 +new $class();
 +^       ^
 +|       |
 +|—T_NEW   |—ctor_arguments
 +    |
 +    |—new_variable (cannot have calls!)
 +
 +new (trim(' MyClass '))();
 +^                    ^
 +|                    |
 +|—T_NEW                |—ctor_arguments
 +    |
 +    |—(expr)
 +</code>
 +
 +Once the parser encounters ''T_NEW'', it considers whatever comes next a class name, not a part of another expression.
 +This guarantees that the ''new'' expression with arguments' parentheses can be unambiguously used as a separate expression
 +without any additional parentheses. It also becomes clear why ''new_variable'' cannot have calls and why arguments' parentheses
 +are crucial for the proposed syntax: parentheses 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 151: 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.1703889108.txt.gz · Last modified: 2023/12/29 22:31 by vudaltsov