feat(php): 添加对foreach列表解构语法的支持

- 在AssignOpTrait.php中导入ArrayItem类并更新类型检查
- 在CompilerBase.php中添加parseForeachItemAsList方法处理列表解构
- 在CompilerBase.php中修改parseForeachArray方法支持List_节点
- 在Translator.php中扩展foreach循环翻译逻辑以处理列表解构
- 添加foreach-list.phpt和iterator-list.phpt测试用例验证功能
pull/1/head
韩天峰 3 months ago
parent dce5190422
commit 6a1fb828f8
  1. 34
      src/Php/CompilerBase.php
  2. 3
      src/Php/Parser/AssignOpTrait.php
  3. 28
      src/Php/Translator.php
  4. 19
      tests/aot/array/foreach-list.phpt
  5. 18
      tests/aot/loop/iterator-list.phpt

@ -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);

@ -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);

@ -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;

@ -0,0 +1,19 @@
--TEST--
foreach with list/array destructuring syntax
--FILE--
<?php
function main() {
$array = [];
$array[] = ['foo', 'elem1'];
$array[] = ['bar', 'elem2'];
foreach (array_reverse($array) as [$v1, $v2]) {
var_dump($v1, $v2);
}
}
?>
--EXPECT--
string(3) "bar"
string(5) "elem2"
string(3) "foo"
string(5) "elem1"

@ -0,0 +1,18 @@
--TEST--
foreach with list destructuring on Traversable object
--FILE--
<?php
function main() {
$array = new ArrayObject();
$array[] = ['foo', 'elem1'];
$array[] = ['bar', 'elem2'];
foreach ($array as [$v1, $v2]) {
var_dump($v1, $v2);
}
}
?>
--EXPECT--
string(3) "foo"
string(5) "elem1"
string(3) "bar"
string(5) "elem2"
Loading…
Cancel
Save