From 2e85940bffdbf364e525cb8db6468b5d17c45a81 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 7 Jul 2026 10:45:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(foreach):=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E9=94=AE=E5=80=BC=E8=B5=8B=E5=80=BC=E4=B8=8E=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E4=BD=93=E8=A7=A3=E6=9E=90=E4=B8=BA=E7=8B=AC=E7=AB=8B=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Php/CompilerBase.php | 78 ++++++++++++------- src/Php/Parser/StdContainerTrait.php | 3 +- src/Php/Translator.php | 27 +------ .../attribute/return-type-will-change.phpt | 57 ++++++++++++++ 4 files changed, 113 insertions(+), 52 deletions(-) create mode 100644 tests/aot/attribute/return-type-will-change.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 2f74c585..9e5b9069 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -5164,15 +5164,26 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont return $code; } - protected function parseForeachArray(Foreach_ $node, string $iteratorVar): string + protected function parseForeachBody(Foreach_ $node): string { - $tmpVar = $this->genTmpVarName(); - $code = "for (auto $tmpVar = $iteratorVar.begin(); $tmpVar != $iteratorVar.end(); ++$tmpVar) {" . PHP_EOL; - $this->indentLevel++; - if ($node->keyVar) { - $keyVar = $this->parseIdentifier($node->keyVar); - $this->checkVar($node, $keyVar); - $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.key();' . PHP_EOL; + return $this->parseStmts($node->stmts) . $this->genLoopEndFlagCheck(); + } + + protected function parseForeachKeyAssignment(Foreach_ $node, string $keyExpr, string $defaultType = self::TYPE_VAR): string + { + if (!$node->keyVar) { + return ''; + } + + $keyVar = $this->parseIdentifier($node->keyVar); + $this->checkVar($node, $keyVar, $defaultType); + return $this->getIndent() . ' ' . $keyVar . ' = ' . $keyExpr . ';' . PHP_EOL; + } + + protected function parseForeachValueAssignment(Foreach_ $node, string $valueExpr, ?string $valueRefExpr = null): string + { + if ($node->byRef && $valueRefExpr === null) { + $this->fatalError($node, 'Cannot use & with foreach'); } if ($node->byRef and !$this->isVarExpr($node->valueVar)) { @@ -5185,32 +5196,47 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont } $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)) { + return $this->getIndent() . ' ' . $listTmpVar . ' = ' . $valueExpr . ';' . PHP_EOL + . $this->parseForeachItemAsList($listTmpVar, $node->valueVar->items); + } + + if ($this->isArrayDimFetch($node->valueVar)) { + if ($node->byRef) { + $this->fatalError($node, 'Foreach by reference only supports variable as value'); + } $array = $this->parseIdentifier($node->valueVar->var); if (!$this->hasVar($array) or $node->valueVar->dim === null) { abort($node->valueVar); } $dim = $this->parseIdentifier($node->valueVar->dim); - $code .= $this->getIndent() . "{$array}.offsetSet({$dim}, {$tmpVar}.value());"; - } else { - $valueVar = $this->parseIdentifier($node->valueVar); - if ($node->byRef) { - if (!$this->hasVar($valueVar)) { - $this->addLocalVar($valueVar, self::TYPE_REF); - } elseif ($this->getVarType($valueVar) !== self::TYPE_REF) { - $this->fatalError($node, 'Cannot assign value to reference of type'); - } - $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.valueRef();' . PHP_EOL; - } else { - $this->checkVar($node, $valueVar); - $code .= $this->getIndent() . ' ' . $valueVar . ' = ' . $tmpVar . '.value();' . PHP_EOL; + return $this->getIndent() . "{$array}.offsetSet({$dim}, {$valueExpr});"; + } + + $valueVar = $this->parseIdentifier($node->valueVar); + if ($node->byRef) { + if (!$this->hasVar($valueVar)) { + $this->addLocalVar($valueVar, self::TYPE_REF); + } elseif ($this->getVarType($valueVar) !== self::TYPE_REF) { + $this->fatalError($node, 'Cannot assign value to reference of type'); } + return $this->getIndent() . ' ' . $valueVar . ' = ' . $valueRefExpr . ';' . PHP_EOL; + } + + if ($this->isVarExpr($node->valueVar)) { + $this->checkVar($node, $valueVar); } + return $this->getIndent() . ' ' . $valueVar . ' = ' . $valueExpr . ';' . PHP_EOL; + } + + protected function parseForeachArray(Foreach_ $node, string $iteratorVar): string + { + $tmpVar = $this->genTmpVarName(); + $code = "for (auto $tmpVar = $iteratorVar.begin(); $tmpVar != $iteratorVar.end(); ++$tmpVar) {" . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseForeachKeyAssignment($node, $tmpVar . '.key()'); + $code .= $this->parseForeachValueAssignment($node, $tmpVar . '.value()', $tmpVar . '.valueRef()'); - $body = $this->parseStmts($node->stmts); - $body .= $this->genLoopEndFlagCheck(); + $body = $this->parseForeachBody($node); $this->indentLevel--; $code .= $this->parseBeforeStmtLines() . PHP_EOL; diff --git a/src/Php/Parser/StdContainerTrait.php b/src/Php/Parser/StdContainerTrait.php index c724c8aa..828cb642 100644 --- a/src/Php/Parser/StdContainerTrait.php +++ b/src/Php/Parser/StdContainerTrait.php @@ -383,8 +383,7 @@ trait StdContainerTrait $code .= $this->getIndent() . "$valueVar = {$iterator}->second;" . PHP_EOL; } - $body = $this->parseStmts($node->stmts); - $body .= $this->genLoopEndFlagCheck(); + $body = $this->parseForeachBody($node); $this->indentLevel--; $code .= $this->parseBeforeStmtLines() . PHP_EOL; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 37e8189a..23386942 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -29,7 +29,6 @@ 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; @@ -3742,29 +3741,9 @@ CODE; $code .= $this->getIndent() . 'for (;' . $tmpVar . '.call(' . $validStr . '); ' . $tmpVar . '.call(' . $nextStr . ')) {' . PHP_EOL; $this->indentLevel++; - if ($node->valueVar instanceof List_) { - $listTmpVar = $this->genTmpVarName(); - $this->addLocalVar($listTmpVar, self::TYPE_VAR); - $code .= $this->getIndent() . ' ' . $listTmpVar . ' = ' . $tmpVar . '.call(' . $currentStr . ');' . PHP_EOL; - if ($node->keyVar) { - $keyVar = $this->parseIdentifier($node->keyVar); - $this->checkVar($node, $keyVar); - $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call(' . $keyStr . ');' . 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(' . $currentStr . ');' . PHP_EOL; - if ($node->keyVar) { - $keyVar = $this->parseIdentifier($node->keyVar); - $this->checkVar($node, $keyVar); - $code .= $this->getIndent() . ' ' . $keyVar . ' = ' . $tmpVar . '.call(' . $keyStr . ');' . PHP_EOL; - } - } - $code .= $this->parseStmts($node->stmts); - $code .= $this->genLoopEndFlagCheck(); + $code .= $this->parseForeachKeyAssignment($node, $tmpVar . '.call(' . $keyStr . ')'); + $code .= $this->parseForeachValueAssignment($node, $tmpVar . '.call(' . $currentStr . ')'); + $code .= $this->parseForeachBody($node); $code .= '}' . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '} else {' . PHP_EOL; diff --git a/tests/aot/attribute/return-type-will-change.phpt b/tests/aot/attribute/return-type-will-change.phpt new file mode 100644 index 00000000..7bb96dd0 --- /dev/null +++ b/tests/aot/attribute/return-type-will-change.phpt @@ -0,0 +1,57 @@ +--TEST-- +ReturnTypeWillChange attribute on internal interface methods +--FILE-- +items[$offset]); + } + + #[\ReturnTypeWillChange] + public function offsetGet(mixed $offset) + { + return $this->items[$offset] ?? null; + } + + #[\ReturnTypeWillChange] + public function offsetSet(mixed $offset, mixed $value) + { + if ($offset === null) { + $this->items[] = $value; + return; + } + $this->items[$offset] = $value; + } + + #[\ReturnTypeWillChange] + public function offsetUnset(mixed $offset) + { + unset($this->items[$offset]); + } +} + +function main(): void +{ + $box = new ReturnTypeWillChangeBox(); + $box['name'] = 'aot'; + var_dump(isset($box['name'])); + var_dump($box['name']); + unset($box['name']); + var_dump(isset($box['name'])); + + $method = new ReflectionMethod(ReturnTypeWillChangeBox::class, 'offsetGet'); + $attrs = $method->getAttributes(ReturnTypeWillChange::class); + var_dump($attrs[0]->getName()); +} +?> +--EXPECT-- +bool(true) +string(3) "aot" +bool(false) +string(20) "ReturnTypeWillChange"