From 78d513d40e89c988ad6eff5f4be93d91ba89fb84 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 5 Oct 2021 17:23:12 +0200 Subject: [PATCH 1/2] Support `new T(...)` as a shorthand for `fn(...$args) => new T(...$args)` See https://wiki.php.net/rfc/first_class_callable_syntax#object_creation --- src/main/php/lang/ast/syntax/PHP.class.php | 25 +++++++++++++++++++ .../ast/unittest/parse/InvokeTest.class.php | 20 ++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index dcb7238..e3d2477 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -321,6 +321,31 @@ public function __construct() { } $parse->expecting('(', 'new arguments'); + + // Resolve ambiguity by looking ahead: `new T(...)` which is a first-class + // callable reference vs. `new T(...$it)` - a call with an unpacked argument + if ('...' === $parse->token->value) { + $dots= $parse->token; + $parse->forward(); + if (')' === $parse->token->value) { + $parse->forward(); + + $arguments= [new UnpackExpression(new Variable('__args'), $token->line)]; + if (null === $type) { + $class= $this->clazz($parse, null); + $class->annotations= $annotations; + $new= new NewClassExpression($class, $arguments, $token->line); + } else { + $new= new NewExpression($type, $arguments, $token->line); + } + + return new CallableExpression($new, $token->line); + } + + $parse->queue[]= $parse->token; + $parse->token= $dots; + } + $arguments= $this->arguments($parse); $parse->expecting(')', 'new arguments'); diff --git a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php index c69256f..6048129 100755 --- a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php @@ -1,6 +1,15 @@ assertParsed( + [new CallableExpression(new NewExpression('\\T', $args, self::LINE), self::LINE)], + 'new T(...);' + ); + } } \ No newline at end of file From 6df762b48114ec6209c5e7a479b5a901ac80e4bb Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Tue, 5 Oct 2021 21:46:32 +0200 Subject: [PATCH 2/2] Create dedicated node for `new T(...)` syntax --- .../ast/nodes/CallableNewExpression.class.php | 16 ++++++++++++++++ src/main/php/lang/ast/syntax/PHP.class.php | 8 ++++---- .../lang/ast/unittest/parse/InvokeTest.class.php | 4 ++-- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100755 src/main/php/lang/ast/nodes/CallableNewExpression.class.php diff --git a/src/main/php/lang/ast/nodes/CallableNewExpression.class.php b/src/main/php/lang/ast/nodes/CallableNewExpression.class.php new file mode 100755 index 0000000..d883a47 --- /dev/null +++ b/src/main/php/lang/ast/nodes/CallableNewExpression.class.php @@ -0,0 +1,16 @@ +type= $type; + $this->line= $line; + } + + /** @return iterable */ + public function children() { return [$this->type]; } +} \ No newline at end of file diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index e3d2477..ba357ff 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -6,6 +6,7 @@ Braced, BreakStatement, CallableExpression, + CallableNewExpression, CaseLabel, CastExpression, CatchStatement, @@ -330,16 +331,15 @@ public function __construct() { if (')' === $parse->token->value) { $parse->forward(); - $arguments= [new UnpackExpression(new Variable('__args'), $token->line)]; if (null === $type) { $class= $this->clazz($parse, null); $class->annotations= $annotations; - $new= new NewClassExpression($class, $arguments, $token->line); + $new= new NewClassExpression($class, null, $token->line); } else { - $new= new NewExpression($type, $arguments, $token->line); + $new= new NewExpression($type, null, $token->line); } - return new CallableExpression($new, $token->line); + return new CallableNewExpression($new, $token->line); } $parse->queue[]= $parse->token; diff --git a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php index 6048129..13c02c1 100755 --- a/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/InvokeTest.class.php @@ -2,6 +2,7 @@ use lang\ast\nodes\{ CallableExpression, + CallableNewExpression, NewExpression, InstanceExpression, InvokeExpression, @@ -99,9 +100,8 @@ public function first_class_callable_static() { #[Test] public function first_class_callable_object_creation() { - $args= [new UnpackExpression(new Variable('__args'), self::LINE)]; $this->assertParsed( - [new CallableExpression(new NewExpression('\\T', $args, self::LINE), self::LINE)], + [new CallableNewExpression(new NewExpression('\\T', null, self::LINE), self::LINE)], 'new T(...);' ); }