feat(parser): add support for foreach by reference with property fetches

- Implement parseForeachIterableRef method to handle reference iteration
- Add support for PropertyFetch, StaticPropertyFetch and ArrayDimFetch in foreach by ref
- Generate proper itemRef calls for array dimension fetches in reference context
- Update iterable type handling to distinguish between VAR and REF types
- Add test case for foreach by reference with live object property mutation
pull/44/head
韩天峰 3 weeks ago
parent 6cfab3bf9a
commit 25e5b47482
  1. 28
      src/Parser/ForeachTrait.php
  2. 136
      tests/compiler/loop/foreach-reference-property.phpt

@ -145,6 +145,28 @@ trait ForeachTrait
return $code;
}
protected function parseForeachIterableRef(Foreach_ $node): ?string
{
$expr = $node->expr;
if ($expr instanceof Expr\PropertyFetch) {
return $this->emitDynamicPropertyFetchRef($expr, $node);
}
if ($expr instanceof Expr\StaticPropertyFetch) {
return $this->emitStaticPropertyFetchRef($expr, $node);
}
if ($expr instanceof Expr\ArrayDimFetch) {
if ($expr->dim === null) {
$this->fatalError($expr, 'Cannot use [] for reading');
}
$array = $this->parseWritableIdentifier($expr->var);
return $array . '.itemRef(' . $this->parseIdentifier($expr->dim) . ')';
}
return null;
}
protected function parseForeach(Foreach_ $node): string
{
if ($this->isVarExpr($node->expr)) {
@ -162,11 +184,13 @@ trait ForeachTrait
}
$code = '';
$expr = $this->parseIdentifier($node->expr);
$expr = $node->byRef ? $this->parseForeachIterableRef($node) : null;
$iterableType = $expr === null ? Type::VAR : Type::REF;
$expr ??= $this->parseIdentifier($node->expr);
$code .= $this->parseBeforeStmtLines() . PHP_EOL;
$iterableVar = $this->genTmpVarName();
$this->addLocalVar($iterableVar, Type::VAR);
$this->addLocalVar($iterableVar, $iterableType);
$code .= $iterableVar . ' = ' . $expr . ';' . PHP_EOL;
$code .= $this->parseForeachIterable($node, $iterableVar);

@ -0,0 +1,136 @@
--TEST--
foreach by reference supports live mutation of object array properties
--FILE--
<?php
class ForeachReferenceNode
{
public array $children = [];
}
function main(): void
{
$node = new ForeachReferenceNode();
$node->children = ['a', 'b', 'c'];
$copy = $node->children;
foreach ($node->children as &$child) {
$child = 'X' . $child;
}
unset($child);
var_dump($node->children, $copy);
$node->children = [1, 2];
$copy = $node->children;
$seen = [];
foreach ($node->children as $key => &$child) {
$seen[] = [$key, $child];
if ($key === 0) {
$node->children[] = 3;
}
$child *= 10;
}
unset($child);
var_dump($seen, $node->children, $copy);
$node->children = [1, 2, 3, 4];
$seen = [];
foreach ($node->children as $key => &$child) {
$seen[] = $key;
if ($key === 0) {
unset($node->children[1]);
}
$child *= 10;
}
unset($child);
var_dump($seen, $node->children);
$root = new ForeachReferenceNode();
$original = new ForeachReferenceNode();
$root->children = [$original];
$replaceChildren = static function (ForeachReferenceNode $current): void {
foreach ($current->children as &$child) {
$child = new ForeachReferenceNode();
}
unset($child);
};
$replaceChildren($root);
var_dump($root->children[0] !== $original);
}
?>
--EXPECT--
array(3) {
[0]=>
string(2) "Xa"
[1]=>
string(2) "Xb"
[2]=>
string(2) "Xc"
}
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
}
array(3) {
[0]=>
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}
[1]=>
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
[2]=>
array(2) {
[0]=>
int(2)
[1]=>
int(3)
}
}
array(3) {
[0]=>
int(10)
[1]=>
int(20)
[2]=>
int(30)
}
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
array(3) {
[0]=>
int(0)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(10)
[2]=>
int(30)
[3]=>
int(40)
}
bool(true)
Loading…
Cancel
Save