diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 19806684..f4088dea 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -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); diff --git a/src/Php/Generator/ClosureGenerator.php b/src/Php/Generator/ClosureGenerator.php index 5381ea71..74fa6497 100644 --- a/src/Php/Generator/ClosureGenerator.php +++ b/src/Php/Generator/ClosureGenerator.php @@ -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; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 63270a84..d16793d7 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -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); diff --git a/tests/aot/basic/exit-string.phpt b/tests/aot/basic/exit-string.phpt new file mode 100644 index 00000000..ed5485e5 --- /dev/null +++ b/tests/aot/basic/exit-string.phpt @@ -0,0 +1,14 @@ +--TEST-- +die with string should print message and terminate without fatal error +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- + +--EXPECT-- +done diff --git a/tests/aot/closure/closure-use-ref-composed.phpt b/tests/aot/closure/closure-use-ref-composed.phpt new file mode 100644 index 00000000..ba7bd4dd --- /dev/null +++ b/tests/aot/closure/closure-use-ref-composed.phpt @@ -0,0 +1,35 @@ +--TEST-- +closure use by reference should work with nested control flow +--FILE-- + $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) diff --git a/tests/aot/closure/closure-variadic-unpack-use-ref.phpt b/tests/aot/closure/closure-variadic-unpack-use-ref.phpt new file mode 100644 index 00000000..6d4675d2 --- /dev/null +++ b/tests/aot/closure/closure-variadic-unpack-use-ref.phpt @@ -0,0 +1,29 @@ +--TEST-- +closure variadic parameter should work with unpacked positional arguments +--FILE-- + +--EXPECT-- +int(10) +int(200) +array(2) { + [0]=> + int(300) + [1]=> + int(400) +} +array(1) { + [0]=> + int(910) +} diff --git a/tests/aot/control_flow/expression-side-effects-composed.phpt b/tests/aot/control_flow/expression-side-effects-composed.phpt new file mode 100644 index 00000000..6073613a --- /dev/null +++ b/tests/aot/control_flow/expression-side-effects-composed.phpt @@ -0,0 +1,42 @@ +--TEST-- +composed ternary and match expressions should evaluate side effects once +--FILE-- + 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) diff --git a/tests/aot/exception/finally-exit.phpt b/tests/aot/exception/finally-exit.phpt new file mode 100644 index 00000000..f2fbd67f --- /dev/null +++ b/tests/aot/exception/finally-exit.phpt @@ -0,0 +1,19 @@ +--TEST-- +exit inside finally should terminate without graceful-exit fatal +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- + +--EXPECT-- +try +finally diff --git a/tests/aot/functions/unpack-followed-by-named-dynamic.phpt b/tests/aot/functions/unpack-followed-by-named-dynamic.phpt new file mode 100644 index 00000000..bfce3ee2 --- /dev/null +++ b/tests/aot/functions/unpack-followed-by-named-dynamic.phpt @@ -0,0 +1,31 @@ +--TEST-- +unpack followed by named argument should use dynamic call semantics +--FILE-- + +--EXPECT-- +int(10) +int(20) +int(300) +array(1) { + ["extra"]=> + int(400) +} +int(11) +int(20) +int(301) +array(1) { + ["extra"]=> + int(401) +} diff --git a/tests/aot/object_property/nested-property-array-write.phpt b/tests/aot/object_property/nested-property-array-write.phpt new file mode 100644 index 00000000..7a42bf53 --- /dev/null +++ b/tests/aot/object_property/nested-property-array-write.phpt @@ -0,0 +1,40 @@ +--TEST-- +nested object property array writes use generic property path +--FILE-- + */ + 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) +} diff --git a/tests/aot/object_property/null-unset-false.phpt b/tests/aot/object_property/null-unset-false.phpt new file mode 100644 index 00000000..5127c705 --- /dev/null +++ b/tests/aot/object_property/null-unset-false.phpt @@ -0,0 +1,33 @@ +--TEST-- +object property null and unset should both behave as false +--FILE-- +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) diff --git a/tests/aot/object_property/property-array-write-composed-expr.phpt b/tests/aot/object_property/property-array-write-composed-expr.phpt new file mode 100644 index 00000000..3f12b0b0 --- /dev/null +++ b/tests/aot/object_property/property-array-write-composed-expr.phpt @@ -0,0 +1,57 @@ +--TEST-- +object property array writes embedded in expressions should write once +--FILE-- +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)