From 6593503ef921d55b7034cbfeb665316e615d2377 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 15 Apr 2023 18:33:25 +0200 Subject: [PATCH 1/4] Implement first class callable syntax for instance method references --- src/main/php/lang/ast/emit/PHP.class.php | 13 +++++++++++++ .../ast/unittest/emit/CallableSyntaxTest.class.php | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index 6842b785..27e5bb9c 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -974,6 +974,19 @@ protected function emitNewClass($result, $new) { protected function emitCallable($result, $callable) { + // T->func(...) is an instance method, create a trampoline + // See https://externals.io/message/120011 + if ( + $callable->expression instanceof InstanceExpression && + $callable->expression->expression instanceof Literal + ) { + $type= $callable->expression->expression->expression; + $result->out->write('static function('.$type.' $_) { return $_->'); + $this->emitOne($result, $callable->expression->member); + $result->out->write('(); }'); + return; + } + // Disambiguate the following: // // - `T::{$func}`, a dynamic class constant diff --git a/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php b/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php index 63eba40a..15d148c2 100755 --- a/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php @@ -182,4 +182,15 @@ public function __construct($value) { $this->value= $value; } }'); Assert::equals($this, $f($this)->value); } + + #[Test] + public function instance_method_reference() { + $r= $this->run('use lang\ast\unittest\emit\Handle; class { + public function run() { + $handles= [new Handle(0), new Handle(1), new Handle(2)]; + return array_map(Handle->hashCode(...), $handles); + } + }'); + Assert::equals(['#0', '#1', '#2'], $r); + } } \ No newline at end of file From fafe5292d3762115754131fb287ef4aaa4cb9a7c Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 15 Apr 2023 18:48:39 +0200 Subject: [PATCH 2/4] Fix PHP 8.0, PHP 7.* --- .../php/lang/ast/emit/CallablesAsClosures.class.php | 9 +++++++++ src/main/php/lang/ast/emit/PHP.class.php | 13 ------------- src/main/php/lang/ast/emit/PHP70.class.php | 9 +++++++++ src/main/php/lang/ast/emit/PHP81.class.php | 2 +- src/main/php/lang/ast/emit/PHP82.class.php | 2 +- src/main/php/lang/ast/emit/PHP83.class.php | 2 +- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/php/lang/ast/emit/CallablesAsClosures.class.php b/src/main/php/lang/ast/emit/CallablesAsClosures.class.php index f2c2ce3f..0935fc36 100755 --- a/src/main/php/lang/ast/emit/CallablesAsClosures.class.php +++ b/src/main/php/lang/ast/emit/CallablesAsClosures.class.php @@ -10,6 +10,7 @@ * @see https://wiki.php.net/rfc/first_class_callable_syntax */ trait CallablesAsClosures { + use CallableInstanceMethodReferences { emitCallable as callableInstanceMethodReferences; } private function emitQuoted($result, $node) { if ($node instanceof Literal) { @@ -48,6 +49,14 @@ private function emitQuoted($result, $node) { } protected function emitCallable($result, $callable) { + if ( + $callable->expression instanceof InstanceExpression && + $callable->expression->expression instanceof Literal + ) { + $this->callableInstanceMethodReferences($result, $callable); + return; + } + $result->out->write('\Closure::fromCallable('); $this->emitQuoted($result, $callable->expression); $result->out->write(')'); diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index 27e5bb9c..6842b785 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -974,19 +974,6 @@ protected function emitNewClass($result, $new) { protected function emitCallable($result, $callable) { - // T->func(...) is an instance method, create a trampoline - // See https://externals.io/message/120011 - if ( - $callable->expression instanceof InstanceExpression && - $callable->expression->expression instanceof Literal - ) { - $type= $callable->expression->expression->expression; - $result->out->write('static function('.$type.' $_) { return $_->'); - $this->emitOne($result, $callable->expression->member); - $result->out->write('(); }'); - return; - } - // Disambiguate the following: // // - `T::{$func}`, a dynamic class constant diff --git a/src/main/php/lang/ast/emit/PHP70.class.php b/src/main/php/lang/ast/emit/PHP70.class.php index e2425630..3c3f8a83 100755 --- a/src/main/php/lang/ast/emit/PHP70.class.php +++ b/src/main/php/lang/ast/emit/PHP70.class.php @@ -10,6 +10,7 @@ * @see https://wiki.php.net/rfc#php_70 */ class PHP70 extends PHP { + use CallableInstanceMethodReferences { emitCallable as callableInstanceMethodReferences; } use ArbitrayNewExpressions, ArrayUnpackUsingMerge, @@ -61,6 +62,14 @@ public function __construct() { } protected function emitCallable($result, $callable) { + if ( + $callable->expression instanceof InstanceExpression && + $callable->expression->expression instanceof Literal + ) { + $this->callableInstanceMethodReferences($result, $callable); + return; + } + $t= $result->temp(); $result->out->write('(is_callable('.$t.'='); if ($callable->expression instanceof Literal) { diff --git a/src/main/php/lang/ast/emit/PHP81.class.php b/src/main/php/lang/ast/emit/PHP81.class.php index d0b3fe59..bc3b04f7 100755 --- a/src/main/php/lang/ast/emit/PHP81.class.php +++ b/src/main/php/lang/ast/emit/PHP81.class.php @@ -19,7 +19,7 @@ * @see https://wiki.php.net/rfc#php_81 */ class PHP81 extends PHP { - use RewriteBlockLambdaExpressions, RewriteDynamicClassConstants, ReadonlyClasses; + use RewriteBlockLambdaExpressions, RewriteDynamicClassConstants, ReadonlyClasses, CallableInstanceMethodReferences; /** Sets up type => literal mappings */ public function __construct() { diff --git a/src/main/php/lang/ast/emit/PHP82.class.php b/src/main/php/lang/ast/emit/PHP82.class.php index c99e4de9..bc689f2b 100755 --- a/src/main/php/lang/ast/emit/PHP82.class.php +++ b/src/main/php/lang/ast/emit/PHP82.class.php @@ -19,7 +19,7 @@ * @see https://wiki.php.net/rfc#php_82 */ class PHP82 extends PHP { - use RewriteBlockLambdaExpressions, RewriteDynamicClassConstants, ReadonlyClasses; + use RewriteBlockLambdaExpressions, RewriteDynamicClassConstants, ReadonlyClasses, CallableInstanceMethodReferences; /** Sets up type => literal mappings */ public function __construct() { diff --git a/src/main/php/lang/ast/emit/PHP83.class.php b/src/main/php/lang/ast/emit/PHP83.class.php index 3be2fb23..c42d41a8 100755 --- a/src/main/php/lang/ast/emit/PHP83.class.php +++ b/src/main/php/lang/ast/emit/PHP83.class.php @@ -18,7 +18,7 @@ * @see https://wiki.php.net/rfc#php_83 */ class PHP83 extends PHP { - use RewriteBlockLambdaExpressions, ReadonlyClasses; + use RewriteBlockLambdaExpressions, ReadonlyClasses, CallableInstanceMethodReferences; /** Sets up type => literal mappings */ public function __construct() { From dd2ae47bb7ae398adc725820f2bff1d509507834 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 15 Apr 2023 18:49:17 +0200 Subject: [PATCH 3/4] Extract instance method reference rewriting to trait --- ...CallableInstanceMethodReferences.class.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php diff --git a/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php b/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php new file mode 100755 index 00000000..2691d8f5 --- /dev/null +++ b/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php @@ -0,0 +1,26 @@ +func(...)` to `fn(T $t) => $t->func()`. + * + * @see https://externals.io/message/120011 + */ +trait CallableInstanceMethodReferences { + + protected function emitCallable($result, $callable) { + if ( + $callable->expression instanceof InstanceExpression && + $callable->expression->expression instanceof Literal + ) { + $type= $callable->expression->expression->expression; + $result->out->write('static function('.$type.' $_) { return $_->'); + $this->emitOne($result, $callable->expression->member); + $result->out->write('(); }'); + } else { + return parent::emitCallable($result, $callable); + } + } +} + From acdc2982b81634274bfa062559d7345d83cfc415 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 15 Apr 2023 19:04:08 +0200 Subject: [PATCH 4/4] Make it work for arbitrary number of parameters --- .../CallableInstanceMethodReferences.class.php | 4 ++-- .../ast/unittest/emit/CallableSyntaxTest.class.php | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php b/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php index 2691d8f5..c3f7f15c 100755 --- a/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php +++ b/src/main/php/lang/ast/emit/CallableInstanceMethodReferences.class.php @@ -15,9 +15,9 @@ protected function emitCallable($result, $callable) { $callable->expression->expression instanceof Literal ) { $type= $callable->expression->expression->expression; - $result->out->write('static function('.$type.' $_) { return $_->'); + $result->out->write('static function('.$type.' $self, ... $args) { return $self->'); $this->emitOne($result, $callable->expression->member); - $result->out->write('(); }'); + $result->out->write('(...$args); }'); } else { return parent::emitCallable($result, $callable); } diff --git a/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php b/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php index 15d148c2..4ec853d5 100755 --- a/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/CallableSyntaxTest.class.php @@ -184,7 +184,7 @@ public function __construct($value) { $this->value= $value; } } #[Test] - public function instance_method_reference() { + public function instance_method_reference_map() { $r= $this->run('use lang\ast\unittest\emit\Handle; class { public function run() { $handles= [new Handle(0), new Handle(1), new Handle(2)]; @@ -193,4 +193,16 @@ public function run() { }'); Assert::equals(['#0', '#1', '#2'], $r); } + + #[Test] + public function instance_method_reference_sort() { + $r= $this->run('use util\Date; class { + public function run() { + $dates= [new Date(43200), new Date(86400), new Date(0)]; + usort($dates, Date->compareTo(...)); + return array_map(Date->getTime(...), $dates); + } + }'); + Assert::equals([86400, 43200, 0], $r); + } } \ No newline at end of file