diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 45b356a4..1b3b953a 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -43,6 +43,7 @@ use PhpAot\Php\Platform\PlatformFactory; use PhpAot\Php\Platform\Windows; use PhpParser\Modifiers; use PhpParser\Node; +use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Expr\Variable; @@ -3550,6 +3551,29 @@ class CompilerBase extends \PhpAot\Core\Translator } } + protected function parseForeachItemAsList(string $listTmpVar, array $listItems): string + { + $code = ''; + foreach ($listItems as $k => $item) { + if (!$item) { + continue; + } + if ($item instanceof ArrayItem) { + $oriInAssignExpr = $this->context->inAssignExpr; + $this->context->inAssignExpr = true; + $var = $this->parseIdentifier($item->value); + $this->context->inAssignExpr = $oriInAssignExpr; + if ($this->isVarExpr($item->value) and !$this->hasVar($var)) { + $this->addLocalVar($var, self::TYPE_VAR); + } + $code .= $this->getIndent() . ' ' . $var . ' = ' . $listTmpVar . '.item(' . $k . ');' . PHP_EOL; + } else { + abort($item); + } + } + return $code; + } + protected function parseForeachArray(Foreach_ $node, string $iteratorVar): string { $tmpVar = $this->genTmpVarName(); @@ -3565,7 +3589,15 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($node, 'Foreach by reference only supports variable as value'); } - if ($this->isArrayDimFetch($node->valueVar)) { + if ($node->valueVar instanceof Expr\List_) { + if ($node->byRef) { + $this->fatalError($node, 'Foreach by reference cannot use list destructuring'); + } + $listTmpVar = $this->genTmpVarName(); + $this->addLocalVar($listTmpVar, self::TYPE_VAR); + $code .= $this->getIndent() . ' ' . $listTmpVar . ' = ' . $tmpVar . '.value();' . PHP_EOL; + $code .= $this->parseForeachItemAsList($listTmpVar, $node->valueVar->items); + } elseif ($this->isArrayDimFetch($node->valueVar)) { $array = $this->parseIdentifier($node->valueVar->var); if (!$this->hasVar($array) or $node->valueVar->dim === null) { abort($node->valueVar); diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index 3594bf74..38c5e0da 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -10,6 +10,7 @@ namespace PhpAot\Php\Parser; use PhpAot\Php\Symbol; use PhpParser\Node; +use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Variable; use PhpParser\NodeAbstract; @@ -107,7 +108,7 @@ trait AssignOpTrait if (!$item) { continue; } - if ($item instanceof Expr\ArrayItem) { + if ($item instanceof ArrayItem) { $oriInAssignExpr = $this->context->inAssignExpr; $this->context->inAssignExpr = true; $var = $this->parseIdentifier($item->value); diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 8024918c..3460b29b 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -27,6 +27,8 @@ use PhpAot\Php\Platform\PlatformFactory; use PhpAot\Php\Platform\Windows; use PhpParser\Modifiers; use PhpParser\Node; +use PhpParser\Node\ArrayItem; +use PhpParser\Node\Expr\List_; use PhpParser\Node\Stmt\Foreach_; use PhpParser\NodeAbstract; use PhpParser\NodeTraverser; @@ -2614,14 +2616,26 @@ CODE; $code .= $this->getIndent() . 'for (;' . $tmpVar . '.call("valid"); ' . $tmpVar . '.call("next")) {' . PHP_EOL; $this->indentLevel++; - $valueVar = $this->parseIdentifier($node->valueVar); - $this->checkVar($node, $valueVar); + if ($node->valueVar instanceof List_) { + $listTmpVar = $this->genTmpVarName(); + $this->addLocalVar($listTmpVar, self::TYPE_VAR); + $code .= $this->getIndent() . ' ' . $listTmpVar . ' = ' . $tmpVar . '.call("current");' . PHP_EOL; + if ($node->keyVar) { + $keyVar = $this->parseIdentifier($node->keyVar); + $this->checkVar($node, $keyVar); + $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call("key");' . PHP_EOL; + } + $code .= $this->parseForeachItemAsList($listTmpVar, $node->valueVar->items); + } else { + $valueVar = $this->parseIdentifier($node->valueVar); + $this->checkVar($node, $valueVar); - $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.call("current");' . PHP_EOL; - if ($node->keyVar) { - $keyVar = $this->parseIdentifier($node->keyVar); - $this->checkVar($node, $keyVar); - $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call("key");' . PHP_EOL; + $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.call("current");' . PHP_EOL; + if ($node->keyVar) { + $keyVar = $this->parseIdentifier($node->keyVar); + $this->checkVar($node, $keyVar); + $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call("key");' . PHP_EOL; + } } $code .= $this->parseStmts($node->stmts); $code .= '}' . PHP_EOL; diff --git a/tests/aot/array/foreach-list.phpt b/tests/aot/array/foreach-list.phpt new file mode 100644 index 00000000..be8123c0 --- /dev/null +++ b/tests/aot/array/foreach-list.phpt @@ -0,0 +1,19 @@ +--TEST-- +foreach with list/array destructuring syntax +--FILE-- + +--EXPECT-- +string(3) "bar" +string(5) "elem2" +string(3) "foo" +string(5) "elem1" diff --git a/tests/aot/loop/iterator-list.phpt b/tests/aot/loop/iterator-list.phpt new file mode 100644 index 00000000..f194c0be --- /dev/null +++ b/tests/aot/loop/iterator-list.phpt @@ -0,0 +1,18 @@ +--TEST-- +foreach with list destructuring on Traversable object +--FILE-- + +--EXPECT-- +string(3) "foo" +string(5) "elem1" +string(3) "bar" +string(5) "elem2"