From b6d0488ea64e0054363a4e0a7b9c912c2b2030e3 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 4 Jul 2026 14:50:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(compiler):=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E5=A4=84=E8=BF=90=E7=AE=97=E7=AC=A6=E5=92=8C=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E7=BC=96=E8=AF=91=E5=89=AF=E4=BD=9C=E7=94=A8=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Php/CompilerBase.php | 40 +++-------- src/Php/Parser/AssignOpTrait.php | 10 +++ .../aot/array/foreach-list-write-targets.phpt | 33 +++++++++ tests/aot/arrow_fn/static-arrow-capture.phpt | 16 +++++ .../basic/clone-expression-side-effects.phpt | 32 +++++++++ tests/aot/basic/error-suppress-restore.phpt | 23 +++++++ tests/aot/basic/globals-assign-ref.phpt | 24 +++++++ tests/aot/basic/globals-ref-arg.phpt | 21 ++++++ .../include-expression-side-effects.phpt | 26 +++++++ tests/aot/basic/test_include_return.inc | 3 + .../class/instanceof-dynamic-expression.phpt | 33 +++++++++ .../assign-coalesce-complex-targets.phpt | 37 ++++++++++ .../nullsafe-chain-middle-null-lazy.phpt | 68 +++++++++++++++++++ .../static/static-vars-dynamic-init-once.phpt | 36 ++++++++++ 14 files changed, 373 insertions(+), 29 deletions(-) create mode 100644 tests/aot/array/foreach-list-write-targets.phpt create mode 100644 tests/aot/arrow_fn/static-arrow-capture.phpt create mode 100644 tests/aot/basic/clone-expression-side-effects.phpt create mode 100644 tests/aot/basic/error-suppress-restore.phpt create mode 100644 tests/aot/basic/globals-assign-ref.phpt create mode 100644 tests/aot/basic/globals-ref-arg.phpt create mode 100644 tests/aot/basic/include-expression-side-effects.phpt create mode 100644 tests/aot/basic/test_include_return.inc create mode 100644 tests/aot/class/instanceof-dynamic-expression.phpt create mode 100644 tests/aot/coalesce/assign-coalesce-complex-targets.phpt create mode 100644 tests/aot/nullsafe/nullsafe-chain-middle-null-lazy.phpt create mode 100644 tests/aot/static/static-vars-dynamic-init-once.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index f4088dea..0fe8d29c 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -756,26 +756,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont 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; + return $this->getIndent() . 'php::appendCallExtraNamedArgs(' . $var . ');' . PHP_EOL; } public function writeFile(string $file, string $content): void @@ -4466,13 +4447,18 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont protected function parseInstanceof(Expr\Instanceof_ $expr): string { $this->assertExprCanBeUsedAsValue($expr->expr, 'instanceof operand'); - $value = $this->parseExprAsValue($expr->expr); if ($this->isNameExpr($expr->class)) { + $value = $this->parseExprAsValue($expr->expr); $className = $this->getNamespacedClassName($this->parseIdentifier($expr->class)); $className = $this->getClassEntryPtr($className); return 'php::instanceOf(' . $value . ', ' . $className . ')'; } else { - return 'php::instanceOf(' . $value . ', ' . $this->identifierToStr($expr->class) . ')'; + [$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr->expr); + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $this->appendCapturedStmtLinesToContext($beforeStmts); + $this->context->beforeStmtLines[] = $tmpVar . ' = ' . $value . ';'; + $this->appendCapturedStmtLinesToContext($afterStmts); + return 'php::instanceOf(' . $tmpVar . ', ' . $this->identifierToStr($expr->class) . ')'; } } @@ -4768,12 +4754,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont protected function parseExit(Expr\Exit_ $node): string { if (!$node->expr) { - return 'std::exit(0)'; + return 'php::aotExit()'; } $status = $this->parseExprAsValue($node->expr); - return '([&]() -> php::Var { php::Var exit_status = ' . $status . '; ' - . 'if (exit_status.isInt()) { std::exit(exit_status.toInt()); } ' - . 'php::echo(php::toString(exit_status)); std::exit(0); return php::null; })()'; + return 'php::aotExit(' . $status . ')'; } protected function getFixedObjectPropDefaultValue(PropertyDef $def): ?string @@ -6453,9 +6437,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont if ($type != self::TYPE_VAR) { $this->fatalError($expr, 'Can only throw objects'); } - $tmp = $this->genTmpVarName(); - $this->addLocalVar($tmp, self::TYPE_VAR); - return '([&]() -> php::Var { ' . $tmp . ' = ' . $ex . '; if (!' . $tmp . '.isObject()) { php::throwError("Can only throw objects"); } return php::throwException(php::Object(' . $tmp . ')); })()'; + return 'php::throwValue(' . $ex . ')'; } protected function parseTryCatch(mixed $v): string diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index 4464e9a2..8f2a5e96 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -22,6 +22,13 @@ trait AssignOpTrait if ($this->isPropertyFetch($left)) { return $this->parseAssignPropertyArrayDim($left, $right); } + if ($this->isVarExpr($left->var) && $left->var->name === 'GLOBALS') { + $target = $this->parseGlobalsArrayDimFetch($left); + $value = $this->parseExprAsValue($right); + $tmp = $this->genTmpVarName(); + $this->addLocalVar($tmp, self::TYPE_VAR); + return '((' . $tmp . ' = ' . $value . ', ' . $target . ' = ' . $tmp . '), ' . $tmp . ')'; + } $oriInAssignExpr = $this->context->inAssignExpr; $this->context->inAssignExpr = true; $array = $this->parseIdentifier($left->var); @@ -394,6 +401,9 @@ trait AssignOpTrait $this->convertExprType($expr, $type, $rightType) . ';'; } + if ($this->isVarExpr($node->var->var) && $node->var->var->name === 'GLOBALS') { + return $var . ' = ' . $tmpVar; + } return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar); } diff --git a/tests/aot/array/foreach-list-write-targets.phpt b/tests/aot/array/foreach-list-write-targets.phpt new file mode 100644 index 00000000..0adeddba --- /dev/null +++ b/tests/aot/array/foreach-list-write-targets.phpt @@ -0,0 +1,33 @@ +--TEST-- +foreach list destructuring can write into object properties and array dimensions +--FILE-- +name, $out['value']]) { + $box->value += $out['value']; + var_dump($box->name, $out['value'], $box->value); + } +} +?> +--EXPECT-- +string(5) "first" +int(10) +int(10) +string(6) "second" +int(20) +int(30) diff --git a/tests/aot/arrow_fn/static-arrow-capture.phpt b/tests/aot/arrow_fn/static-arrow-capture.phpt new file mode 100644 index 00000000..983f9bbf --- /dev/null +++ b/tests/aot/arrow_fn/static-arrow-capture.phpt @@ -0,0 +1,16 @@ +--TEST-- +static arrow function captures local variables by value +--FILE-- + $value * $factor; + $factor = 10; + + var_dump($map(4)); +} +?> +--EXPECT-- +int(12) diff --git a/tests/aot/basic/clone-expression-side-effects.phpt b/tests/aot/basic/clone-expression-side-effects.phpt new file mode 100644 index 00000000..e61174a9 --- /dev/null +++ b/tests/aot/basic/clone-expression-side-effects.phpt @@ -0,0 +1,32 @@ +--TEST-- +clone operand expression is evaluated once and __clone runs +--FILE-- +value++; + } +} + +function make_clone_source(): CloneSideEffect +{ + echo "make\n"; + return new CloneSideEffect(); +} + +function main(): void +{ + $copy = clone make_clone_source(); + var_dump($copy->value); +} +?> +--EXPECT-- +make +__clone +int(2) diff --git a/tests/aot/basic/error-suppress-restore.phpt b/tests/aot/basic/error-suppress-restore.phpt new file mode 100644 index 00000000..cfb146d1 --- /dev/null +++ b/tests/aot/basic/error-suppress-restore.phpt @@ -0,0 +1,23 @@ +--TEST-- +Error suppression should restore error_reporting after expression evaluation +--FILE-- + +--EXPECT-- +bool(false) +bool(true) diff --git a/tests/aot/basic/globals-assign-ref.phpt b/tests/aot/basic/globals-assign-ref.phpt new file mode 100644 index 00000000..0bf0f13b --- /dev/null +++ b/tests/aot/basic/globals-assign-ref.phpt @@ -0,0 +1,24 @@ +--TEST-- +$GLOBALS array assignment and reference parameter update global slot +--FILE-- + +--EXPECT-- +int(42) +int(42) diff --git a/tests/aot/basic/globals-ref-arg.phpt b/tests/aot/basic/globals-ref-arg.phpt new file mode 100644 index 00000000..2af99654 --- /dev/null +++ b/tests/aot/basic/globals-ref-arg.phpt @@ -0,0 +1,21 @@ +--TEST-- +$GLOBALS array element can be passed to reference parameter +--FILE-- + +--EXPECT-- +int(42) diff --git a/tests/aot/basic/include-expression-side-effects.phpt b/tests/aot/basic/include-expression-side-effects.phpt new file mode 100644 index 00000000..3d3dd537 --- /dev/null +++ b/tests/aot/basic/include-expression-side-effects.phpt @@ -0,0 +1,26 @@ +--TEST-- +include path expression side effects and return value +--FILE-- + +--EXPECT-- +target:test_include_return.inc +included:test_include_return.inc +int(123) +target:test_include_return.inc +bool(true) diff --git a/tests/aot/basic/test_include_return.inc b/tests/aot/basic/test_include_return.inc new file mode 100644 index 00000000..184f049c --- /dev/null +++ b/tests/aot/basic/test_include_return.inc @@ -0,0 +1,3 @@ + +--EXPECT-- +object +class:InstanceofMarker +bool(true) +object +class:stdClass +bool(false) diff --git a/tests/aot/coalesce/assign-coalesce-complex-targets.phpt b/tests/aot/coalesce/assign-coalesce-complex-targets.phpt new file mode 100644 index 00000000..9c562de5 --- /dev/null +++ b/tests/aot/coalesce/assign-coalesce-complex-targets.phpt @@ -0,0 +1,37 @@ +--TEST-- +null coalescing assignment with object property and array dim targets +--FILE-- +value ??= make_default('prop'); + $box->value ??= make_default('prop-skip'); + var_dump($box->value); + + $box->items['name'] ??= make_default('array'); + $box->items['name'] ??= make_default('array-skip'); + var_dump($box->items); +} +?> +--EXPECT-- +default:prop +string(4) "prop" +default:array +array(1) { + ["name"]=> + string(5) "array" +} diff --git a/tests/aot/nullsafe/nullsafe-chain-middle-null-lazy.phpt b/tests/aot/nullsafe/nullsafe-chain-middle-null-lazy.phpt new file mode 100644 index 00000000..8945f582 --- /dev/null +++ b/tests/aot/nullsafe/nullsafe-chain-middle-null-lazy.phpt @@ -0,0 +1,68 @@ +--TEST-- +Nullsafe chain should stop at middle null and skip later arguments +--FILE-- +leaf; + } +} + +class NullsafeChainRoot +{ + public function __construct(private ?NullsafeChainMiddle $middle) + { + } + + public function middle(string $arg): ?NullsafeChainMiddle + { + echo "root:$arg\n"; + return $this->middle; + } +} + +function make_arg(string $name): string +{ + echo "arg:$name\n"; + return $name; +} + +function main(): void +{ + $root = new NullsafeChainRoot(new NullsafeChainMiddle(null)); + var_dump($root?->middle(make_arg('root'))?->leaf(make_arg('middle'))?->value(make_arg('leaf'))); + + $root = new NullsafeChainRoot(new NullsafeChainMiddle(new NullsafeChainLeaf())); + var_dump($root?->middle(make_arg('root2'))?->leaf(make_arg('middle2'))?->value(make_arg('leaf2'))); +} +?> +--EXPECT-- +arg:root +root:root +arg:middle +middle:middle +NULL +arg:root2 +root:root2 +arg:middle2 +middle:middle2 +arg:leaf2 +leaf:leaf2 +string(5) "leaf2" diff --git a/tests/aot/static/static-vars-dynamic-init-once.phpt b/tests/aot/static/static-vars-dynamic-init-once.phpt new file mode 100644 index 00000000..6c6512ed --- /dev/null +++ b/tests/aot/static/static-vars-dynamic-init-once.phpt @@ -0,0 +1,36 @@ +--TEST-- +static variable dynamic initializer runs only once +--FILE-- + +--EXPECT-- +init:10 +array(1) { + [0]=> + int(10) +} +array(2) { + [0]=> + int(10) + [1]=> + int(10) +}