fix(closure): 修复闭包生成器中的引用捕获问题

- 将闭包生成器中的引用捕获从 & 改为 =,避免悬空指针问题
- 在 PHP 中使用 = 赋值进行浅拷贝,仅增加一次引用计数
- 添加了关于 zval 封装赋值的注释说明
- 新增了可调用语法的测试用例,包括箭头函数和杂项功能
- 添加了对 PHP 8.1+ 首类可调用语法的支持测试
- 包含了枚举相关功能的测试用例验证
pull/1/head
韩天峰 5 months ago
parent b499347669
commit dd297b5f92
  1. 4
      src/Php/Generator/ClosureGenerator.php
  2. 87
      tests/aot/backed-enum.phpt
  3. 22
      tests/aot/callable/arrow-fn.phpt
  4. 110
      tests/aot/callable/misc.phpt

@ -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 . ']('

@ -0,0 +1,87 @@
--TEST--
Backed Enums (PHP 8.1+)
--FILE--
<?php
// Test basic backed enum with int values
enum Status: int {
case Pending = 0;
case Active = 1;
case Suspended = 2;
case Closed = 3;
public function label(): string {
return match($this) {
self::Pending => '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"

@ -0,0 +1,22 @@
--TEST--
First-Class Callable Syntax (PHP 8.1+)
--FILE--
<?php
// Test callable returning callable
function multiplier(int $factor): callable {
return fn(int $value) => $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)

@ -0,0 +1,110 @@
--TEST--
First-Class Callable Syntax (PHP 8.1+)
--FILE--
<?php
// Test basic first-class callable
function add(int $a, int $b): int {
return $a + $b;
}
// Test first-class callable with class methods
class Math {
public static function multiply(int $a, int $b): int {
return $a * $b;
}
public function divide(int $a, int $b): float {
return floatval($a) / $b;
}
}
// Test callable returning callable
function multiplier(int $factor): callable {
return fn(int $value) => $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)
}
Loading…
Cancel
Save