From cff27c7c795b366a9785a4753515621ae2ad6c04 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 13:18:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor(parser):=20=E9=87=8D=E6=9E=84=E4=BA=8C?= =?UTF-8?q?=E5=85=83=E6=93=8D=E4=BD=9C=E7=AC=A6=E5=92=8C=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E6=B5=81=E7=9A=84=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现逻辑运算符短路求值功能,支持 && 和 || 操作符的惰性求值 - 添加 parseShortCircuitLogicalOp 方法处理逻辑运算符的特殊解析逻辑 - 新增 parseExprWithCapturedStmts 方法捕获表达式解析过程中的语句 - 实现 appendCapturedStmtLines 和 genConditionWithCapturedStmts 辅助方法 - 重构 for 循环条件和循环体表达式的解析逻辑 - 修改三元运算符解析以支持条件表达式的语句捕获 - 重构 if 语句解析使用递归函数处理 if-elseif-else 链 - 更新 while 和 do-while 循环处理条件表达式中的副作用 - 重构 switch 语句解析逻辑,改进 case 条件的匹配机制 - 添加多个测试用例验证条件表达式副作用的执行时机 --- src/Php/CompilerBase.php | 237 ++++++++++++++---- src/Php/Parser/BinaryOpTrait.php | 48 +++- .../control_flow/condition-side-effects.phpt | 62 +++++ .../do-while-condition-side-effects.phpt | 29 +++ .../for-condition-side-effects.phpt | 56 +++++ .../operator/logical-short-circuit-lazy.phpt | 41 +++ tests/aot/operator/ternary-lazy-eval.phpt | 12 + tests/aot/switch/switch-side-effects.phpt | 55 ++++ 8 files changed, 483 insertions(+), 57 deletions(-) create mode 100644 tests/aot/control_flow/condition-side-effects.phpt create mode 100644 tests/aot/control_flow/do-while-condition-side-effects.phpt create mode 100644 tests/aot/control_flow/for-condition-side-effects.phpt create mode 100644 tests/aot/operator/logical-short-circuit-lazy.phpt create mode 100644 tests/aot/switch/switch-side-effects.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 2f7eabac..9beeb199 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1350,6 +1350,40 @@ class CompilerBase extends \PhpAot\Core\Translator return ''; } + protected function parseExprWithCapturedStmts(NodeAbstract $expr): array + { + $beforeStmtCount = count($this->context->beforeStmtLines); + $afterStmtCount = count($this->context->afterStmtLines); + $value = $this->parseExpr($expr); + $beforeStmts = array_slice($this->context->beforeStmtLines, $beforeStmtCount); + $afterStmts = array_slice($this->context->afterStmtLines, $afterStmtCount); + $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $beforeStmtCount); + $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $afterStmtCount); + return [$value, $beforeStmts, $afterStmts]; + } + + protected function appendCapturedStmtLines(string &$code, array $stmts): void + { + if ($stmts) { + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL; + } + } + + protected function genConditionWithCapturedStmts(NodeAbstract $cond, string $openPrefix): string + { + [$condExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($cond); + $code = ''; + $this->appendCapturedStmtLines($code, $beforeStmts); + if ($afterStmts) { + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $code .= $this->getIndent() . $tmpVar . ' = ' . $condExpr . ';' . PHP_EOL; + $this->appendCapturedStmtLines($code, $afterStmts); + $condExpr = $tmpVar; + } + $code .= $openPrefix . '(' . $condExpr . ') {' . PHP_EOL; + return $code; + } + protected function parseBlockStmts(array $stmts): string { $this->indentLevel++; @@ -2592,24 +2626,65 @@ class CompilerBase extends \PhpAot\Core\Translator $list_expr = []; foreach ($init as $expr) { - $list_expr[] = $this->parseExpr($expr); + [$initExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr); + $this->appendCapturedStmtLines($code, $beforeStmts); + $list_expr[] = $initExpr; + if ($afterStmts) { + $list_expr[] = implode(";\n" . $this->getIndent(), $afterStmts); + } } $list_expr[] = ''; $code .= implode(";\n" . $this->getIndent(), $list_expr); $list_cond = []; + $hasCondStmts = false; foreach ($cond as $expr) { - $list_cond[] = $this->parseExpr($expr); + [$condExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr); + $hasCondStmts = $hasCondStmts || $beforeStmts || $afterStmts; + $list_cond[] = [$condExpr, $beforeStmts, $afterStmts]; } $code .= $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'for (;'; - $code .= implode(', ', $list_cond); + if ($hasCondStmts) { + $condCode = '[&]() -> bool {'; + if (empty($list_cond)) { + $condCode .= $this->getIndent() . 'return true;'; + } else { + $condResult = $this->genTmpVarName(); + $condCode .= $this->getIndent() . 'bool ' . $condResult . ' = true;' . PHP_EOL; + foreach ($list_cond as [$condExpr, $beforeStmts, $afterStmts]) { + $this->appendCapturedStmtLines($condCode, $beforeStmts); + if ($afterStmts) { + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $condCode .= $this->getIndent() . $tmpVar . ' = ' . $condExpr . ';' . PHP_EOL; + $this->appendCapturedStmtLines($condCode, $afterStmts); + $condExpr = $tmpVar; + } + $condCode .= $this->getIndent() . $condResult . ' = ' . $this->convertBoolExpr($condExpr) . ';' . PHP_EOL; + } + $condCode .= $this->getIndent() . 'return ' . $condResult . ';'; + } + $condCode .= $this->getIndent() . '}()'; + $code .= $condCode; + } else { + $code .= implode(', ', array_map(static fn($item) => $item[0], $list_cond)); + } $code .= '; '; $list_loop = []; foreach ($loop as $expr) { - $list_loop[] = $this->parseExpr($expr); + [$loopExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr); + if ($beforeStmts || $afterStmts) { + $loopCode = '[&]() {'; + $this->appendCapturedStmtLines($loopCode, $beforeStmts); + $loopCode .= $this->getIndent() . $loopExpr . ';' . PHP_EOL; + $this->appendCapturedStmtLines($loopCode, $afterStmts); + $loopCode .= $this->getIndent() . '}()'; + $list_loop[] = $loopCode; + } else { + $list_loop[] = $loopExpr; + } } $code .= implode(', ', $list_loop); $code .= ') {' . PHP_EOL; @@ -3531,7 +3606,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($expr->if === null) { return $this->parseValueSelection($expr, $expr->cond, $expr->else, self::OP_NOT_EMPTY); } - $cond = $this->parseExpr($expr->cond); + [$cond, $condBeforeStmts, $condAfterStmts] = $this->parseExprWithCapturedStmts($expr->cond); $ifBeforeStmtCount = count($this->context->beforeStmtLines); $ifAfterStmtCount = count($this->context->afterStmtLines); $if = $this->parseExpr($expr->if); @@ -3548,7 +3623,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $elseBeforeStmtCount); $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $elseAfterStmtCount); - $hasBranchStmts = $ifBeforeStmts || $ifAfterStmts || $elseBeforeStmts || $elseAfterStmts; + $hasBranchStmts = $condBeforeStmts || $condAfterStmts || $ifBeforeStmts || $ifAfterStmts || $elseBeforeStmts || $elseAfterStmts; $typeChanged = $this->detectTypeOfExpr($expr->if) !== $this->detectTypeOfExpr($expr->else); if (!$hasBranchStmts && $typeChanged) { $if = 'php::Var(' . $if . ')'; @@ -3557,7 +3632,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($hasBranchStmts) { $appendStmtLines = function (string &$code, array $stmts): void { if ($stmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts); + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL; } }; $appendReturn = function (string &$code, string $value, array $beforeStmts, array $afterStmts) use ($appendStmtLines): void { @@ -3572,6 +3647,13 @@ class CompilerBase extends \PhpAot\Core\Translator } }; $code = '[&]() -> ' . self::TYPE_VAR . '{'; + $appendStmtLines($code, $condBeforeStmts); + if ($condAfterStmts) { + $condTmpVar = $this->addTmpVar(self::TYPE_VAR); + $code .= $this->getIndent() . "{$condTmpVar} = {$cond};"; + $appendStmtLines($code, $condAfterStmts); + $cond = $condTmpVar; + } $code .= $this->getIndent() . 'if (' . $cond . ') {'; $appendReturn($code, $if, $ifBeforeStmts, $ifAfterStmts); $code .= $this->getIndent() . '} else {'; @@ -3609,7 +3691,7 @@ class CompilerBase extends \PhpAot\Core\Translator $appendStmtLines = function (string &$code, array $stmts): void { if ($stmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts); + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL; } }; @@ -3700,29 +3782,34 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseIf(Node\Stmt\If_ $v): string { - $cond = $this->parseExpr($v->cond); + $arms = [[$v->cond, $v->stmts]]; + foreach ($v->elseifs as $elseif) { + $arms[] = [$elseif->cond, $elseif->stmts]; + } - $code = $this->parseBeforeStmtLines() . PHP_EOL; - $code .= 'if (' . $cond . ') {' . PHP_EOL; - $code .= $this->parseBlockStmts($v->stmts); - $code .= $this->getIndent() . '}'; + $emitIfChain = function (int $index) use (&$emitIfChain, $arms, $v): string { + if (!isset($arms[$index])) { + if (!$v->else) { + return ''; + } + return $this->parseBlockStmts($v->else->stmts); + } - if ($v->elseifs) { - foreach ($v->elseifs as $elseif) { - $elseifCond = $this->parseExpr($elseif->cond); - $code .= ' else if (' . $elseifCond . ') {' . PHP_EOL; - $code .= $this->parseBlockStmts($elseif->stmts); + [$cond, $stmts] = $arms[$index]; + $code = $this->genConditionWithCapturedStmts($cond, 'if '); + $code .= $this->parseBlockStmts($stmts); + $tail = $emitIfChain($index + 1); + if ($tail !== '') { + $code .= $this->getIndent() . '} else {' . PHP_EOL; + $code .= $tail; + $code .= $this->getIndent() . '}'; + } else { $code .= $this->getIndent() . '}'; } - } - - if ($v->else) { - $code .= ' else {' . PHP_EOL; - $code .= $this->parseBlockStmts($v->else->stmts); - $code .= $this->getIndent() . '}'; - } + return $code; + }; - return $code . PHP_EOL; + return $this->parseBeforeStmtLines() . PHP_EOL . $emitIfChain(0) . PHP_EOL; } /** @@ -3735,11 +3822,23 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseWhile(Node\Stmt\While_ $v): string { - $cond = $this->parseExpr($v->cond); $stmts = $v->stmts; + [$cond, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($v->cond); $code = $this->parseBeforeStmtLines() . PHP_EOL; - $code .= 'while (' . $cond . ') {' . PHP_EOL; + if ($beforeStmts || $afterStmts) { + $code .= 'while (true) {' . PHP_EOL; + $this->appendCapturedStmtLines($code, $beforeStmts); + if ($afterStmts) { + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $code .= $this->getIndent() . $tmpVar . ' = ' . $cond . ';' . PHP_EOL; + $this->appendCapturedStmtLines($code, $afterStmts); + $cond = $tmpVar; + } + $code .= $this->getIndent() . 'if (!(' . $cond . ')) { break; }' . PHP_EOL; + } else { + $code .= 'while (' . $cond . ') {' . PHP_EOL; + } $code .= $this->parseBlockStmts($stmts); $code .= $this->genLoopEndFlagCheck(); $code .= $this->getIndent() . '}' . PHP_EOL; @@ -3755,7 +3854,20 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseDo(Node\Stmt\Do_ $v): string { $stmts = $v->stmts; - $cond = $this->parseExpr($v->cond); + [$cond, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($v->cond); + if ($beforeStmts || $afterStmts) { + $condCode = '[&]() -> bool {'; + $this->appendCapturedStmtLines($condCode, $beforeStmts); + if ($afterStmts) { + $tmpVar = $this->addTmpVar(self::TYPE_VAR); + $condCode .= $this->getIndent() . $tmpVar . ' = ' . $cond . ';' . PHP_EOL; + $this->appendCapturedStmtLines($condCode, $afterStmts); + $cond = $tmpVar; + } + $condCode .= $this->getIndent() . 'return ' . $this->convertBoolExpr($cond) . ';'; + $condCode .= $this->getIndent() . '}()'; + $cond = $condCode; + } $code = $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'do {' . PHP_EOL; $code .= $this->parseBlockStmts($stmts); @@ -4597,7 +4709,11 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isVarExpr($cond)) { $this->requireVar($v, $this->parseIdentifier($cond)); } - $var_def = $type . ' ' . $tmp_var . ' = ' . $this->parseExpr($cond) . ';' . PHP_EOL; + [$condExpr, $condBeforeStmts, $condAfterStmts] = $this->parseExprWithCapturedStmts($cond); + $var_def = ''; + $this->appendCapturedStmtLines($var_def, $condBeforeStmts); + $var_def .= $type . ' ' . $tmp_var . ' = ' . $condExpr . ';' . PHP_EOL; + $this->appendCapturedStmtLines($var_def, $condAfterStmts); // 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置 $localVars = $this->context->localVars; @@ -4634,33 +4750,23 @@ class CompilerBase extends \PhpAot\Core\Translator _fail: - if (count($v->cases) == 1) { - if (!empty($v->cases[0]->cond)) { - $code = 'if (' . $tmp_var . '==' . $this->parseIdentifier($v->cases[0]->cond) . ') {' . PHP_EOL; - } else { - $code = 'if (1) {' . PHP_EOL; - } - $code .= $this->parseStmts($v->cases[0]->stmts); - $code .= $this->getIndent() . '}'; - return $var_def . $code; - } - $code = 'do {' . PHP_EOL; $this->indentLevel++; - $condList = []; - $defaultCase = false; - $defaultOnly = true; + $switchMatched = $this->genTmpVarName(); + $code .= $this->getIndent() . 'bool ' . $switchMatched . ' = false;' . PHP_EOL; + $caseConds = []; foreach ($v->cases as $case) { if (empty($case->cond)) { - $defaultCase = true; + $isDefault = true; } else { - $condList[] = $tmp_var . '==' . $this->parseIdentifier($case->cond); + $isDefault = false; + $caseConds[] = $case->cond; } - $this->indentLevel++; $stmts = $case->stmts; if (empty($stmts)) { continue; } + $this->indentLevel++; if (count($stmts) === 1 and $stmts[0] instanceof Node\Stmt\Block) { $stmts = $stmts[0]->stmts; } @@ -4673,13 +4779,34 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($case, 'switch case must end with return/break/exit/throw, ' . $lastExpr->getType() . ' given'); } - if ($defaultCase) { - $else = $defaultOnly ? '' : 'else'; - $code .= $this->getIndent() . $else . ' {' . PHP_EOL; + if ($isDefault) { + $code .= $this->getIndent() . 'if (!' . $switchMatched . ') {' . PHP_EOL; } else { - $code .= $this->getIndent() . 'if (' . implode(' || ', $condList) . ') {' . PHP_EOL; - $defaultOnly = false; - $condList = []; + $groupMatched = $this->genTmpVarName(); + $code .= $this->getIndent() . 'bool ' . $groupMatched . ' = false;' . PHP_EOL; + foreach ($caseConds as $caseCond) { + $caseBeforeStmtCount = count($this->context->beforeStmtLines); + $caseAfterStmtCount = count($this->context->afterStmtLines); + $caseCondExpr = $this->parseIdentifier($caseCond); + $caseBeforeStmts = array_slice($this->context->beforeStmtLines, $caseBeforeStmtCount); + $caseAfterStmts = array_slice($this->context->afterStmtLines, $caseAfterStmtCount); + $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $caseBeforeStmtCount); + $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $caseAfterStmtCount); + + $code .= $this->getIndent() . 'if (!' . $switchMatched . ' && !' . $groupMatched . ') {' . PHP_EOL; + $this->appendCapturedStmtLines($code, $caseBeforeStmts); + if ($caseAfterStmts) { + $caseTmpVar = $this->addTmpVar(self::TYPE_VAR); + $code .= $this->getIndent() . $caseTmpVar . ' = ' . $caseCondExpr . ';' . PHP_EOL; + $this->appendCapturedStmtLines($code, $caseAfterStmts); + $caseCondExpr = $caseTmpVar; + } + $code .= $this->getIndent() . $groupMatched . ' = php::equals(' . $tmp_var . ', ' . $caseCondExpr . ');' . PHP_EOL; + $code .= $this->getIndent() . '}' . PHP_EOL; + } + $code .= $this->getIndent() . 'if (' . $groupMatched . ') {' . PHP_EOL; + $code .= $this->getIndent() . $switchMatched . ' = true;' . PHP_EOL; + $caseConds = []; } $code .= $this->parseStmts($stmts); @@ -6342,11 +6469,11 @@ class CompilerBase extends \PhpAot\Core\Translator $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $beforeStmtCount); $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $afterStmtCount); if ($argBeforeStmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argBeforeStmts); + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argBeforeStmts) . PHP_EOL; } $code .= $this->getIndent() . "{$tmpVar} = {$object}.call({$item[1]}, {$args});"; if ($argAfterStmts) { - $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argAfterStmts); + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argAfterStmts) . PHP_EOL; } } $object = $tmpVar; diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index f752e65d..ceef608c 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -283,12 +283,56 @@ trait BinaryOpTrait protected function parseBinaryOpLogicalAnd(Expr\BinaryOp\LogicalAnd|Expr\BinaryOp\BooleanAnd $expr): string { - return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '&&')); + return $this->parseShortCircuitLogicalOp($expr->left, $expr->right, '&&'); } protected function parseBinaryOpLogicalOr(Expr\BinaryOp\LogicalOr|Expr\BinaryOp\BooleanOr $expr): string { - return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '||')); + return $this->parseShortCircuitLogicalOp($expr->left, $expr->right, '||'); + } + + protected function parseShortCircuitLogicalOp(NodeAbstract $left, NodeAbstract $right, string $op): string + { + $leftExpr = $this->parseNumericIdentifier($left); + $this->checkVarMustExist($left, $leftExpr); + + $rightBeforeStmtCount = count($this->context->beforeStmtLines); + $rightAfterStmtCount = count($this->context->afterStmtLines); + $rightExpr = $this->parseNumericIdentifier($right); + $rightBeforeStmts = array_slice($this->context->beforeStmtLines, $rightBeforeStmtCount); + $rightAfterStmts = array_slice($this->context->afterStmtLines, $rightAfterStmtCount); + $this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $rightBeforeStmtCount); + $this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $rightAfterStmtCount); + $this->checkVarMustExist($right, $rightExpr); + + $leftBool = $this->convertBoolExpr((string) $leftExpr); + if (!$rightBeforeStmts && !$rightAfterStmts) { + return '(' . $leftBool . ' ' . $op . ' ' . $this->convertBoolExpr((string) $rightExpr) . ')'; + } + + $appendStmtLines = function (string &$code, array $stmts): void { + if ($stmts) { + $code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL; + } + }; + $shortCircuitValue = $op === '&&' ? 'false' : 'true'; + $rightCondition = $op === '&&' ? $leftBool : '!(' . $leftBool . ')'; + + $code = '[&]() -> bool {'; + $code .= $this->getIndent() . 'if (' . $rightCondition . ') {'; + $appendStmtLines($code, $rightBeforeStmts); + if ($rightAfterStmts) { + $rightTmpVar = $this->addTmpVar(self::TYPE_VAR); + $code .= $this->getIndent() . $rightTmpVar . ' = ' . $rightExpr . ';'; + $appendStmtLines($code, $rightAfterStmts); + $rightExpr = $rightTmpVar; + } + $code .= $this->getIndent() . 'return ' . $this->convertBoolExpr((string) $rightExpr) . ';'; + $code .= $this->getIndent() . '}'; + $code .= $this->getIndent() . 'return ' . $shortCircuitValue . ';'; + $code .= $this->getIndent() . '}()'; + + return $code; } protected function parseBinaryOpLogicalXor(Expr\BinaryOp\LogicalXor $expr): string diff --git a/tests/aot/control_flow/condition-side-effects.phpt b/tests/aot/control_flow/condition-side-effects.phpt new file mode 100644 index 00000000..84f6e0bb --- /dev/null +++ b/tests/aot/control_flow/condition-side-effects.phpt @@ -0,0 +1,62 @@ +--TEST-- +If and while conditions evaluate side effects at the correct time +--FILE-- + +--EXPECT-- +args:elseif-false +value:elseif-false +probe:elseif-false:elseif-false +args:elseif-true +value:elseif-true +probe:elseif-true:elseif-true +elseif true +loop:0 +body:0 +loop:1 +body:1 +loop:2 +body:2 +loop:3 diff --git a/tests/aot/control_flow/do-while-condition-side-effects.phpt b/tests/aot/control_flow/do-while-condition-side-effects.phpt new file mode 100644 index 00000000..d81a6ff7 --- /dev/null +++ b/tests/aot/control_flow/do-while-condition-side-effects.phpt @@ -0,0 +1,29 @@ +--TEST-- +Do-while condition evaluates side effects after continue +--FILE-- + +--EXPECT-- +body:0 +cond:1 +body:1 +cond:2 +body:2 +cond:3 diff --git a/tests/aot/control_flow/for-condition-side-effects.phpt b/tests/aot/control_flow/for-condition-side-effects.phpt new file mode 100644 index 00000000..f4e839cf --- /dev/null +++ b/tests/aot/control_flow/for-condition-side-effects.phpt @@ -0,0 +1,56 @@ +--TEST-- +For condition and loop expressions evaluate side effects at the correct time +--FILE-- + +--EXPECT-- +args:cond-0 +cond:cond-0:0 +body:0 +args:step-0 +step:step-0:0 +args:cond-1 +cond:cond-1:1 +body:1 +args:step-1 +step:step-1:1 +args:cond-2 +cond:cond-2:2 +body:2 +args:step-2 +step:step-2:2 +args:cond-3 +cond:cond-3:3 diff --git a/tests/aot/operator/logical-short-circuit-lazy.phpt b/tests/aot/operator/logical-short-circuit-lazy.phpt new file mode 100644 index 00000000..fef51c0a --- /dev/null +++ b/tests/aot/operator/logical-short-circuit-lazy.phpt @@ -0,0 +1,41 @@ +--TEST-- +Logical && and || evaluate right side lazily +--FILE-- + +--EXPECT-- +bool(false) +args:and-run +value:and-run +check:and-run:and-run +bool(true) +bool(true) +args:or-run +value:or-run +check:or-run:or-run +bool(true) diff --git a/tests/aot/operator/ternary-lazy-eval.phpt b/tests/aot/operator/ternary-lazy-eval.phpt index 1d7f288e..1cb575e5 100644 --- a/tests/aot/operator/ternary-lazy-eval.phpt +++ b/tests/aot/operator/ternary-lazy-eval.phpt @@ -20,6 +20,12 @@ function build(string $id, string $value): string return $id . ':' . $value; } +function condition(string $id, string $value): bool +{ + echo "cond:$id:$value\n"; + return true; +} + function choose(bool $flag): string { return $flag @@ -29,11 +35,17 @@ function choose(bool $flag): string function main(): void { + var_dump(condition(...makeArgs('C'), value: makeValue('C')) ? build('T', 'T') : build('F', 'F')); var_dump(choose(true)); var_dump(choose(false)); } ?> --EXPECT-- +args:C +value:C +cond:C:C +body:T:T +string(3) "T:T" args:T value:T body:T:T diff --git a/tests/aot/switch/switch-side-effects.phpt b/tests/aot/switch/switch-side-effects.phpt new file mode 100644 index 00000000..c57e54d1 --- /dev/null +++ b/tests/aot/switch/switch-side-effects.phpt @@ -0,0 +1,55 @@ +--TEST-- +Switch condition and case expressions evaluate side effects in order +--FILE-- + +--EXPECT-- +args:subject +value:b +subject:subject:b +args:a +value:a +case:a:a +args:b +value:b +case:b:b +B