From 6b3f4250b54d62f226eb8912ca656b76ec2784c5 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 23 May 2026 18:39:41 +0200 Subject: [PATCH 1/5] Refactor function scopes: Capturing and returning --- composer.json | 2 +- src/main/php/lang/ast/emit/PHP.class.php | 21 ++++++++++--- .../emit/ControlStructuresTest.class.php | 6 ++-- .../unittest/emit/InvocationTest.class.php | 31 +++++++++++++------ .../ast/unittest/emit/LambdasTest.class.php | 6 ++-- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index e0c519b4..4e0da782 100755 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require" : { "xp-framework/core": "^12.0 | ^11.6 | ^10.16", "xp-framework/reflection": "^3.2 | ^2.15", - "xp-framework/ast": "^12.1", + "xp-framework/ast": "dev-refactor/function-scope as 12.2.0", "php" : ">=7.4.0" }, "require-dev" : { diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index 572a82ba..2d426aba 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -350,6 +350,18 @@ protected function emitSignature($result, $signature, $use= null) { } } + protected function emitFunctionScope($result, $scope) { + if ($scope instanceof Block) { + $this->emitAll($result, $scope->statements); + } else if ($scope instanceof Node) { + $result->out->write('return '); + $this->emitOne($result, $scope); + $result->out->write(';'); + } else { + $this->emitAll($result, $scope); // BC + } + } + protected function emitFunction($result, $function) { $locals= $result->locals; $result->locals= []; @@ -358,7 +370,7 @@ protected function emitFunction($result, $function) { $this->emitSignature($result, $function->signature); $result->out->write('{'); - $this->emitAll($result, $function->body); + $this->emitFunctionScope($result, $function->body); $result->out->write('}'); $result->locals= $locals; @@ -372,7 +384,7 @@ protected function emitClosure($result, $closure) { $this->emitSignature($result, $closure->signature, $closure->use); $result->out->write('{'); - $this->emitAll($result, $closure->body); + $this->emitFunctionScope($result, $closure->body); $result->out->write('}'); $result->locals= $locals; @@ -384,6 +396,7 @@ protected function emitLambda($result, $lambda) { $lambda->static ? $result->out->write('static fn') : $result->out->write('fn'); $this->emitSignature($result, $lambda->signature); $result->out->write('=>'); + $this->emitOne($result, $lambda->body); $result->locals= $locals; @@ -719,7 +732,7 @@ protected function emitMethod($result, $method) { if (null === $method->body) { $result->out->write(';'); } else { - $result->out->write(' {'); + $result->out->write('{'); // Emit non-constant parameter defaults foreach ($init as $param) { @@ -739,7 +752,7 @@ protected function emitMethod($result, $method) { $result->codegen->scope[0]->init= []; } - $this->emitAll($result, $method->body); + $this->emitFunctionScope($result, $method->body); $result->out->write('}'); } diff --git a/src/test/php/lang/ast/unittest/emit/ControlStructuresTest.class.php b/src/test/php/lang/ast/unittest/emit/ControlStructuresTest.class.php index 299374a0..8d26a7ac 100755 --- a/src/test/php/lang/ast/unittest/emit/ControlStructuresTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/ControlStructuresTest.class.php @@ -121,11 +121,11 @@ public function run($type) { $value= "Test"; return match ($type) { "PING" => "+PONG", - "MSG" => { + "MSG" { $reply= "Re: ".$value; return "+OK $reply"; }, - default => { + default { return "-ERR Unknown ".$type; } }; @@ -243,7 +243,7 @@ public function run() { $test= "Original"; (function() use(&$test) { match (true) { - true => { + true { $test= "Changed"; return true; } diff --git a/src/test/php/lang/ast/unittest/emit/InvocationTest.class.php b/src/test/php/lang/ast/unittest/emit/InvocationTest.class.php index 15ae41bf..ae2864c4 100755 --- a/src/test/php/lang/ast/unittest/emit/InvocationTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/InvocationTest.class.php @@ -78,32 +78,45 @@ public function run() { )); } - #[Test] - public function closure() { + #[Test, Values(['function() { return "closure"; }', 'function() => "closure"'])] + public function closure($expr) { Assert::equals('closure', $this->run( 'class %T { public function run() { - $f= function() { return "closure"; }; + $f= '.$expr.'; return $f(); } }' )); } - #[Test] - public function global_function() { - Assert::equals('function', $this->run( - 'function fixture() { return "function"; } - class %T { + #[Test, Values(['fn() { return "lambda"; }', 'fn() => "lambda"'])] + public function lambda($expr) { + Assert::equals('lambda', $this->run( + 'class %T { public function run() { - return fixture(); + $f= '.$expr.'; + return $f(); } }' )); } + #[Test, Values(['function t%1$s() { return "function"; }', 'function t%1$s() => "function";'])] + public function global_function($decl) { + Assert::equals('function', $this->run(sprintf( + $decl.' class %%T { + + public function run() { + return t%1$s(); + } + }', + uniqid() + ))); + } + #[Test] public function function_self_reference() { Assert::equals(13, $this->run( diff --git a/src/test/php/lang/ast/unittest/emit/LambdasTest.class.php b/src/test/php/lang/ast/unittest/emit/LambdasTest.class.php index 26ad8fd9..1de05063 100755 --- a/src/test/php/lang/ast/unittest/emit/LambdasTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/LambdasTest.class.php @@ -193,7 +193,7 @@ public function run() { public function with_block() { $r= $this->run('class %T { public function run() { - return fn() => { + return fn() { $a= 1; return $a + 1; }; @@ -208,7 +208,7 @@ public function capturing_with_block() { $r= $this->run('class %T { public function run() { $a= 1; - return fn() => { + return fn() { return $a + 1; }; } @@ -231,7 +231,7 @@ public function issue_176() { $r= $this->run('class %T { public function run(iterable $records) { $nonNull= fn($record) => null !== $record; - $process= fn($records, $filter) => { + $process= fn($records, $filter) { foreach ($records as $record) { if ($filter($record)) yield $record; } From 87b8a49bd2ca1c5a03573efb608a9b07f3faee18 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 23 May 2026 19:34:22 +0200 Subject: [PATCH 2/5] Simpilify emitFunctionScope() --- src/main/php/lang/ast/emit/PHP.class.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index 2d426aba..6ee9afcb 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -353,12 +353,10 @@ protected function emitSignature($result, $signature, $use= null) { protected function emitFunctionScope($result, $scope) { if ($scope instanceof Block) { $this->emitAll($result, $scope->statements); - } else if ($scope instanceof Node) { + } else { $result->out->write('return '); $this->emitOne($result, $scope); $result->out->write(';'); - } else { - $this->emitAll($result, $scope); // BC } } From a69d463b617b81b818cf440ad0dec1ef13de7408 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 23 May 2026 19:37:58 +0200 Subject: [PATCH 3/5] Inline simplified emitFunctionScope() --- src/main/php/lang/ast/emit/PHP.class.php | 34 +++++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index 6ee9afcb..e0b6ecc9 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -350,16 +350,6 @@ protected function emitSignature($result, $signature, $use= null) { } } - protected function emitFunctionScope($result, $scope) { - if ($scope instanceof Block) { - $this->emitAll($result, $scope->statements); - } else { - $result->out->write('return '); - $this->emitOne($result, $scope); - $result->out->write(';'); - } - } - protected function emitFunction($result, $function) { $locals= $result->locals; $result->locals= []; @@ -368,7 +358,13 @@ protected function emitFunction($result, $function) { $this->emitSignature($result, $function->signature); $result->out->write('{'); - $this->emitFunctionScope($result, $function->body); + if ($function->body instanceof Block) { + $this->emitAll($result, $function->body->statements); + } else { + $result->out->write('return '); + $this->emitOne($result, $function->body); + $result->out->write(';'); + } $result->out->write('}'); $result->locals= $locals; @@ -382,7 +378,13 @@ protected function emitClosure($result, $closure) { $this->emitSignature($result, $closure->signature, $closure->use); $result->out->write('{'); - $this->emitFunctionScope($result, $closure->body); + if ($closure->body instanceof Block) { + $this->emitAll($result, $closure->body->statements); + } else { + $result->out->write('return '); + $this->emitOne($result, $closure->body); + $result->out->write(';'); + } $result->out->write('}'); $result->locals= $locals; @@ -750,7 +752,13 @@ protected function emitMethod($result, $method) { $result->codegen->scope[0]->init= []; } - $this->emitFunctionScope($result, $method->body); + if ($method->body instanceof Block) { + $this->emitAll($result, $method->body->statements); + } else { + $result->out->write('return '); + $this->emitOne($result, $method->body); + $result->out->write(';'); + } $result->out->write('}'); } From 7a6e9cdc11680fc968f5ebd9deb8b3402aca0a22 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 23 May 2026 19:43:37 +0200 Subject: [PATCH 4/5] Reduce writes --- src/main/php/lang/ast/emit/PHP.class.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php index e0b6ecc9..163ff27b 100755 --- a/src/main/php/lang/ast/emit/PHP.class.php +++ b/src/main/php/lang/ast/emit/PHP.class.php @@ -357,15 +357,15 @@ protected function emitFunction($result, $function) { $result->out->write('function '.($function->signature->byref ? '&' : '').$function->name); $this->emitSignature($result, $function->signature); - $result->out->write('{'); if ($function->body instanceof Block) { + $result->out->write('{'); $this->emitAll($result, $function->body->statements); + $result->out->write('}'); } else { - $result->out->write('return '); + $result->out->write('{ return '); $this->emitOne($result, $function->body); - $result->out->write(';'); + $result->out->write('; }'); } - $result->out->write('}'); $result->locals= $locals; } @@ -377,15 +377,15 @@ protected function emitClosure($result, $closure) { $closure->static ? $result->out->write('static function') : $result->out->write('function'); $this->emitSignature($result, $closure->signature, $closure->use); - $result->out->write('{'); if ($closure->body instanceof Block) { + $result->out->write('{'); $this->emitAll($result, $closure->body->statements); + $result->out->write('}'); } else { - $result->out->write('return '); + $result->out->write('{ return '); $this->emitOne($result, $closure->body); - $result->out->write(';'); + $result->out->write('; }'); } - $result->out->write('}'); $result->locals= $locals; } @@ -754,12 +754,12 @@ protected function emitMethod($result, $method) { if ($method->body instanceof Block) { $this->emitAll($result, $method->body->statements); + $result->out->write('}'); } else { $result->out->write('return '); $this->emitOne($result, $method->body); - $result->out->write(';'); + $result->out->write('; }'); } - $result->out->write('}'); } foreach ($promoted as $param) { From f5ed515560e052f961611629566dd3f5658df3e7 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 23 May 2026 20:42:50 +0200 Subject: [PATCH 5/5] Use AST release version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4e0da782..13a0c980 100755 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "require" : { "xp-framework/core": "^12.0 | ^11.6 | ^10.16", "xp-framework/reflection": "^3.2 | ^2.15", - "xp-framework/ast": "dev-refactor/function-scope as 12.2.0", + "xp-framework/ast": "^12.2", "php" : ">=7.4.0" }, "require-dev" : {