fix: 修复变参参数缺失命名参数及闭包副作用等问题

pull/13/head
韩天峰 2 months ago
parent c740a24501
commit aab412084f
  1. 24
      src/Php/CompilerBase.php
  2. 1
      src/Php/Generator/ClosureGenerator.php
  3. 1
      src/Php/Translator.php
  4. 14
      tests/aot/basic/exit-string.phpt
  5. 35
      tests/aot/closure/closure-use-ref-composed.phpt
  6. 29
      tests/aot/closure/closure-variadic-unpack-use-ref.phpt
  7. 42
      tests/aot/control_flow/expression-side-effects-composed.phpt
  8. 19
      tests/aot/exception/finally-exit.phpt
  9. 31
      tests/aot/functions/unpack-followed-by-named-dynamic.phpt
  10. 40
      tests/aot/object_property/nested-property-array-write.phpt
  11. 33
      tests/aot/object_property/null-unset-false.phpt
  12. 57
      tests/aot/object_property/property-array-write-composed-expr.phpt

@ -754,6 +754,30 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return 'tmp_var_' . $this->context->tmpVarIndex++;
}
protected function genExtraNamedVariadicArgs(string $var): string
{
$keyVar = $var . '_named_key';
$valueVar = $var . '_named_value';
$code = $this->getIndent() . 'if (zend_array *named_args = php::getCallExtraNamedArgs()) {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'zend_string *' . $keyVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'zval *' . $valueVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(named_args, ' . $keyVar . ', ' . $valueVar . ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'if (' . $keyVar . ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . $var . '.set(' . $keyVar . ', php::Variant(' . $valueVar . ', php::Ctor::CopyRef));' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '} ZEND_HASH_FOREACH_END();' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
}
public function writeFile(string $file, string $content): void
{
$dir = dirname($file);

@ -85,6 +85,7 @@ trait ClosureGenerator
$code .= $this->getIndent() . $var . '.append(php::getCallArg(i));' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
$code .= $this->genExtraNamedVariadicArgs($var);
$this->addArgument($var, self::TYPE_ARRAY);
$code .= $this->genClosureParamTypeCheck($param, $var, $phpName, $i, true);
continue;

@ -2898,6 +2898,7 @@ CODE;
$cppCode .= $this->getIndent() . $var . '.append(php::getCallArg(i));' . PHP_EOL;
$this->indentLevel--;
$cppCode .= '}' . PHP_EOL;
$cppCode .= $this->genExtraNamedVariadicArgs($var);
} else {
if ($argInfo->default) {
$defaultExpr = $this->genDefaultArgumentExpr($functionDef, $argInfo);

@ -0,0 +1,14 @@
--TEST--
die with string should print message and terminate without fatal error
--ENV--
USE_ZEND_ALLOC=0
--FILE--
<?php
function main() {
die("done\n");
echo "unreachable\n";
}
?>
--EXPECT--
done

@ -0,0 +1,35 @@
--TEST--
closure use by reference should work with nested control flow
--FILE--
<?php
function main() {
$log = [];
$counter = 0;
$push = function (string $label, int $value) use (&$log, &$counter): int {
$log[] = $label . ':' . $counter;
$counter++;
return match ($value) {
1 => $value + $counter,
default => $counter,
};
};
var_dump($push('a', 1));
var_dump($push('b', 2));
var_dump($log);
var_dump($counter);
}
?>
--EXPECT--
int(2)
int(2)
array(2) {
[0]=>
string(3) "a:0"
[1]=>
string(3) "b:1"
}
int(2)

@ -0,0 +1,29 @@
--TEST--
closure variadic parameter should work with unpacked positional arguments
--FILE--
<?php
function main() {
$log = [];
$fn = function ($a, $b = 20, ...$rest) use (&$log) {
$log[] = $a + $b + array_sum($rest);
var_dump($a, $b, $rest);
};
$fn(...[10, 200, 300, 400]);
var_dump($log);
}
?>
--EXPECT--
int(10)
int(200)
array(2) {
[0]=>
int(300)
[1]=>
int(400)
}
array(1) {
[0]=>
int(910)
}

@ -0,0 +1,42 @@
--TEST--
composed ternary and match expressions should evaluate side effects once
--FILE--
<?php
function side_effect(string $label, &$counter, $value) {
echo $label . ':' . $counter . "\n";
$counter++;
return $value;
}
function main() {
$n = 0;
$ternary = side_effect('ternary-cond', $n, true)
? side_effect('ternary-if', $n, 'yes')
: side_effect('ternary-else', $n, 'no');
var_dump($ternary);
var_dump($n);
$match = match (side_effect('match-subject', $n, 2)) {
side_effect('match-arm-1', $n, 1) => side_effect('match-body-1', $n, 'one'),
side_effect('match-arm-2', $n, 2) => side_effect('match-body-2', $n, 'two'),
default => side_effect('match-default', $n, 'default'),
};
var_dump($match);
var_dump($n);
}
?>
--EXPECT--
ternary-cond:0
ternary-if:1
string(3) "yes"
int(2)
match-subject:2
match-arm-1:3
match-arm-2:4
match-body-2:5
string(3) "two"
int(6)

@ -0,0 +1,19 @@
--TEST--
exit inside finally should terminate without graceful-exit fatal
--ENV--
USE_ZEND_ALLOC=0
--FILE--
<?php
try {
echo "try\n";
} finally {
echo "finally\n";
exit(0);
}
echo "unreachable\n";
?>
--EXPECT--
try
finally

@ -0,0 +1,31 @@
--TEST--
unpack followed by named argument should use dynamic call semantics
--FILE--
<?php
function unpack_named_target($a, $b = 20, $c = 30, ...$rest) {
var_dump($a, $b, $c, $rest);
}
function main() {
unpack_named_target(...[10], c: 300, extra: 400);
$fn = 'unpack_named_target';
$fn(...[11], c: 301, extra: 401);
}
?>
--EXPECT--
int(10)
int(20)
int(300)
array(1) {
["extra"]=>
int(400)
}
int(11)
int(20)
int(301)
array(1) {
["extra"]=>
int(401)
}

@ -0,0 +1,40 @@
--TEST--
nested object property array writes use generic property path
--FILE--
<?php
class NestedPropertyArrayWriteBlock
{
public array $predecessors = [];
public array $phi = [];
}
class NestedPropertyArrayWriteGraph
{
/** @var array<int, NestedPropertyArrayWriteBlock> */
public array $blocks = [];
public function run(): void
{
$this->blocks[1] = new NestedPropertyArrayWriteBlock();
$this->blocks[1]->predecessors[] = 42;
$this->blocks[1]->phi['x'] = 7;
var_dump($this->blocks[1]->predecessors);
var_dump($this->blocks[1]->phi);
}
}
function main() {
(new NestedPropertyArrayWriteGraph())->run();
}
?>
--EXPECT--
array(1) {
[0]=>
int(42)
}
array(1) {
["x"]=>
int(7)
}

@ -0,0 +1,33 @@
--TEST--
object property null and unset should both behave as false
--FILE--
<?php
class NullUnsetFalseBox
{
public $value = 1;
}
function main() {
$box = new NullUnsetFalseBox();
var_dump((bool) $box->value);
$box->value = null;
var_dump((bool) $box->value);
var_dump(isset($box->value));
var_dump(empty($box->value));
$box->value = 1;
unset($box->value);
var_dump(isset($box->value));
var_dump(empty($box->value));
}
?>
--EXPECT--
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)

@ -0,0 +1,57 @@
--TEST--
object property array writes embedded in expressions should write once
--FILE--
<?php
class PropertyArrayWriteComposedNode
{
public array $items = [];
}
class PropertyArrayWriteComposedBox
{
public PropertyArrayWriteComposedNode $node;
public function __construct()
{
$this->node = new PropertyArrayWriteComposedNode();
}
}
function next_value(&$counter) {
echo 'next:' . $counter . "\n";
return ++$counter;
}
function main() {
$box = new PropertyArrayWriteComposedBox();
$counter = 0;
$a = ($box->node->items[] = next_value($counter));
$b = true ? ($box->node->items['k'] = next_value($counter)) : 99;
$c = match ($counter) {
2 => ($box->node->items[] = next_value($counter)),
default => 0,
};
var_dump($a, $b, $c);
var_dump($box->node->items);
var_dump($counter);
}
?>
--EXPECT--
next:0
next:1
next:2
int(1)
int(2)
int(3)
array(3) {
[0]=>
int(1)
["k"]=>
int(2)
[1]=>
int(3)
}
int(3)
Loading…
Cancel
Save