diff --git a/src/Parser/ForeachTrait.php b/src/Parser/ForeachTrait.php index b352eb91..1e0ce341 100644 --- a/src/Parser/ForeachTrait.php +++ b/src/Parser/ForeachTrait.php @@ -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); diff --git a/tests/compiler/loop/foreach-reference-property.phpt b/tests/compiler/loop/foreach-reference-property.phpt new file mode 100644 index 00000000..70ba77f8 --- /dev/null +++ b/tests/compiler/loop/foreach-reference-property.phpt @@ -0,0 +1,136 @@ +--TEST-- +foreach by reference supports live mutation of object array properties +--FILE-- +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)