From dd297b5f92f670b4c8aa877472e46479e433997d Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 20 Mar 2026 20:58:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(closure):=20=E4=BF=AE=E5=A4=8D=E9=97=AD?= =?UTF-8?q?=E5=8C=85=E7=94=9F=E6=88=90=E5=99=A8=E4=B8=AD=E7=9A=84=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E6=8D=95=E8=8E=B7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将闭包生成器中的引用捕获从 & 改为 =,避免悬空指针问题 - 在 PHP 中使用 = 赋值进行浅拷贝,仅增加一次引用计数 - 添加了关于 zval 封装赋值的注释说明 - 新增了可调用语法的测试用例,包括箭头函数和杂项功能 - 添加了对 PHP 8.1+ 首类可调用语法的支持测试 - 包含了枚举相关功能的测试用例验证 --- src/Php/Generator/ClosureGenerator.php | 4 +- tests/aot/backed-enum.phpt | 87 +++++++++++++++++++ tests/aot/callable/arrow-fn.phpt | 22 +++++ tests/aot/callable/misc.phpt | 110 +++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 tests/aot/backed-enum.phpt create mode 100644 tests/aot/callable/arrow-fn.phpt create mode 100644 tests/aot/callable/misc.phpt diff --git a/src/Php/Generator/ClosureGenerator.php b/src/Php/Generator/ClosureGenerator.php index c3dfa378..07e29909 100644 --- a/src/Php/Generator/ClosureGenerator.php +++ b/src/Php/Generator/ClosureGenerator.php @@ -27,7 +27,9 @@ trait ClosureGenerator protected function genClosure(NodeAbstract $expr, array $params, callable $bodyGenCb, array $uses = [], bool $useCurrentScope = false): string { $tmpVar = $this->genTmpVarName(); - $capture = $useCurrentScope ? '&' : ''; + // 必须使用 = 捕获,不能使用 & ,否则可能会出现悬空指针 + // 在 PHP 中 = 赋值是浅拷贝,仅增加一次引用计数,和 zval (16 字节) 封装的赋值 + $capture = $useCurrentScope ? '=' : ''; $code = $this->getIndent() . 'php::ClosureFn ' . $tmpVar . ' = [' . $capture . '](' diff --git a/tests/aot/backed-enum.phpt b/tests/aot/backed-enum.phpt new file mode 100644 index 00000000..81c58014 --- /dev/null +++ b/tests/aot/backed-enum.phpt @@ -0,0 +1,87 @@ +--TEST-- +Backed Enums (PHP 8.1+) +--FILE-- + 'Pending', + self::Active => 'Active', + self::Suspended => 'Suspended', + self::Closed => 'Closed', + }; + } +} + +// Test backed enum with string values +enum Color: string { + case Red = 'red'; + case Green = 'green'; + case Blue = 'blue'; + + public function rgb(): array { + return match($this) { + self::Red => [255, 0, 0], + self::Green => [0, 255, 0], + self::Blue => [0, 0, 255], + }; + } +} + +// Test enum in switch +function processStatus(Status $status): string { + return match($status) { + Status::Pending => 'Task is pending', + Status::Active => 'Task is active', + Status::Suspended => 'Task is suspended', + Status::Closed => 'Task is closed', + }; +} + +function main() { + var_dump(Status::Pending->value); + var_dump(Status::Active->label()); + var_dump(Status::from(2)->name); + + var_dump(Color::Red->value); + var_dump(Color::Green->rgb()); + + var_dump(processStatus(Status::Active)); + + // Test enum comparison + var_dump(Status::Active === Status::Active); + var_dump(Status::Pending !== Status::Active); + + // Test enum array usage + $statuses = [Status::Pending, Status::Active, Status::Closed]; + foreach ($statuses as $status) { + var_dump($status->label()); + } +} +?> +--EXPECT-- +int(0) +string(6) "Active" +string(9) "Suspended" +string(3) "red" +array(3) { + [0]=> + int(0) + [1]=> + int(255) + [2]=> + int(0) +} +string(14) "Task is active" +bool(true) +bool(true) +string(7) "Pending" +string(6) "Active" +string(6) "Closed" diff --git a/tests/aot/callable/arrow-fn.phpt b/tests/aot/callable/arrow-fn.phpt new file mode 100644 index 00000000..dcb5e911 --- /dev/null +++ b/tests/aot/callable/arrow-fn.phpt @@ -0,0 +1,22 @@ +--TEST-- +First-Class Callable Syntax (PHP 8.1+) +--FILE-- + $value * $factor; +} + +function main() { + // Test callable returning callable + $double = multiplier(2); + $triple = multiplier(3); + + var_dump($double(5)); + var_dump($triple(5)); +} +?> +--EXPECT-- +int(10) +int(15) diff --git a/tests/aot/callable/misc.phpt b/tests/aot/callable/misc.phpt new file mode 100644 index 00000000..4aaa361f --- /dev/null +++ b/tests/aot/callable/misc.phpt @@ -0,0 +1,110 @@ +--TEST-- +First-Class Callable Syntax (PHP 8.1+) +--FILE-- + $value * $factor; +} + +function main() { + $callable = 'add'; + var_dump(array_map($callable, [1, 2, 3], [4, 5, 6])); + + $staticCallable = ['Math', 'multiply']; + var_dump(array_reduce([[2, 3], [4, 5]], fn($carry, $item) => $carry + call_user_func_array($staticCallable, $item), 0)); + + $math = new Math(); + $instanceCallable = [$math, 'divide']; + var_dump(call_user_func($instanceCallable, 10, 4)); + + // Test first-class callable with arrow functions + $filter = fn(array $arr): array => array_filter($arr, fn($n) => $n > 0); + var_dump($filter([1, -2, 3, -4, 5])); + + // Test callable in array operations + $numbers = range(1, 5); + $doubled = array_map(fn($n) => $n * 2, $numbers); + var_dump($doubled); + + $squared = array_map(fn($n) => $n ** 2, $numbers); + var_dump($squared); + + // Test callable with usort + $unsorted = [5, 2, 8, 1, 9]; + usort($unsorted, fn($a, $b) => $a <=> $b); + var_dump($unsorted); +} +?> +--EXPECT-- +array(3) { + [0]=> + int(5) + [1]=> + int(7) + [2]=> + int(9) +} +int(26) +float(2.5) +array(3) { + [0]=> + int(1) + [2]=> + int(3) + [4]=> + int(5) +} +array(5) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) + [3]=> + int(8) + [4]=> + int(10) +} +array(5) { + [0]=> + int(1) + [1]=> + int(4) + [2]=> + int(9) + [3]=> + int(16) + [4]=> + int(25) +} +array(5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(5) + [3]=> + int(8) + [4]=> + int(9) +}