From fa2cb45df563e9f5181aa5dc3c17d33cd61345c3 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 12 May 2026 15:43:00 +0800 Subject: [PATCH] =?UTF-8?q?test(aot):=20=E6=B7=BB=E5=8A=A0=E6=A0=87?= =?UTF-8?q?=E5=87=86=E5=AE=B9=E5=99=A8=E5=A4=8D=E5=88=B6=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 std map 相同类型复制测试用例 - 新增 std unordered_map 相同类型复制测试用例 - 新增 std array 嵌套相同类型复制测试用例 - 新增 std vector 相同类型复制测试用例 - 实现标准容器复制赋值语法解析功能 - 添加嵌套数组访问层级检查机制 - 完善类型匹配验证逻辑 --- src/Php/CompilerBase.php | 21 ++++ src/Php/Parser/StdContainerParser.php | 139 +++++++++++++++++++++----- tests/aot/std-array/010.phpt | 34 +++++++ tests/aot/std-map/008.phpt | 25 +++++ tests/aot/std-unordered-map/008.phpt | 25 +++++ tests/aot/std-vector/011.phpt | 25 +++++ 6 files changed, 245 insertions(+), 24 deletions(-) create mode 100644 tests/aot/std-array/010.phpt create mode 100644 tests/aot/std-map/008.phpt create mode 100644 tests/aot/std-unordered-map/008.phpt create mode 100644 tests/aot/std-vector/011.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 644c25c3..c4aca94c 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1503,6 +1503,12 @@ class CompilerBase extends \PhpAot\Core\Translator $type = $this->detectTypeOfExpr($right); if ($this->isVarExpr($left)) { + if ($this->isStdContainer($var)) { + $copyAssign = $this->parseStdContainerCopyAssign($var, $right); + if ($copyAssign !== null) { + return $copyAssign; + } + } // 类型推断,获取对象的类名,如果不是对象则返回空字符串 $rightClass = $this->detectClassOfExpr($right); // 右值是一个对象,已获得类的名称,左值必须与右值的类一致 @@ -1610,6 +1616,21 @@ class CompilerBase extends \PhpAot\Core\Translator return $var . ' = ' . $this->convertExprType($rightExpr, $this->detectTypeOfExpr($left), $this->detectTypeOfExpr($right)); } + protected function parseStdContainerCopyAssign(string $leftVar, Expr $right): ?string + { + $rightInfo = $this->getStdContainerExprInfo($right); + if ($rightInfo === null) { + return null; + } + + $leftInfo = $this->getStdContainerVarInfo($leftVar); + if (!$this->isSameStdContainerInfo($leftInfo, $rightInfo)) { + $this->fatalError($right, 'Cannot copy std container with different type'); + } + + return $leftVar . ' = ' . $this->parseStdContainerCopyExpr($right); + } + protected function parseAssignRightExpr(Expr $right): string { $rightExpr = $this->parseExpr($right); diff --git a/src/Php/Parser/StdContainerParser.php b/src/Php/Parser/StdContainerParser.php index d354ad76..e2aa8008 100644 --- a/src/Php/Parser/StdContainerParser.php +++ b/src/Php/Parser/StdContainerParser.php @@ -72,6 +72,83 @@ trait StdContainerParser return $this->context->stdContainers[$var]; } + protected function getStdArrayDecl(string $type, array $sizes): string + { + $decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($sizes)); + $decl .= $type; + for ($i = count($sizes) - 1; $i >= 0; $i--) { + $decl .= ', ' . $sizes[$i] . '>'; + } + return $decl; + } + + protected function getStdValueTypeBytes(string $type): int + { + return match ($type) { + self::TYPE_BOOL => 1, + self::TYPE_INT, self::TYPE_FLOAT => 8, + default => 16, + }; + } + + protected function getNestedStdArrayInfo(array $info, int $accessLevel): ?array + { + $sizes = array_reverse($info['sizes']); + if ($accessLevel >= count($sizes)) { + return null; + } + + $nestedSizes = array_slice($sizes, $accessLevel); + return [ + 'kind' => 'array', + 'decl' => $this->getStdArrayDecl($info['type'], $nestedSizes), + 'type' => $info['type'], + 'class' => $info['class'], + 'sizes' => array_reverse($nestedSizes), + 'bytes' => array_product($nestedSizes) * $this->getStdValueTypeBytes($info['type']), + ]; + } + + protected function getStdArrayDimFetchContainerInfo(Expr\ArrayDimFetch $expr): ?array + { + if (!$expr->hasAttribute('stdArrayDimFetch')) { + $this->parseStdArrayDimFetch($expr); + } + $attr = $expr->getAttribute('stdArrayDimFetch'); + return $this->getNestedStdArrayInfo($this->context->stdArrays[$attr['var']], $attr['accessLevel']); + } + + protected function isSameStdContainerInfo(array $leftInfo, array $rightInfo): bool + { + return $this->getStdTypeKey($leftInfo) === $this->getStdTypeKey($rightInfo); + } + + protected function getStdContainerExprInfo(NodeAbstract $expr): ?array + { + if ($this->isVarExpr($expr)) { + $var = $this->parseVariable($expr); + if ($this->isStdContainer($var)) { + return $this->getStdContainerVarInfo($var); + } + return null; + } + if ($this->isArrayDimFetch($expr) and $this->isStdArrayExpr($expr)) { + return $this->getStdArrayDimFetchContainerInfo($expr); + } + return null; + } + + protected function parseStdContainerCopyExpr(NodeAbstract $expr): string + { + if ($this->isVarExpr($expr)) { + return $this->parseVariable($expr); + } + if ($this->isArrayDimFetch($expr) and $this->isStdArrayExpr($expr)) { + return $this->parseStdArrayDimFetch($expr); + } + return $this->parseExpr($expr); + } + protected function isStdArrayExpr(Expr\ArrayDimFetch $expr): bool { $info = $this->getStdArrayInfo($expr); @@ -130,6 +207,15 @@ trait StdContainerParser { $info = $this->getStdArrayInfo($left); $arrayDimFetch = $this->parseStdArrayDimFetch($left); + $attr = $left->getAttribute('stdArrayDimFetch'); + if ($attr['accessLevel'] < $attr['totalLevel']) { + $leftInfo = $this->getNestedStdArrayInfo($info, $attr['accessLevel']); + $rightInfo = $this->getStdContainerExprInfo($right); + if ($rightInfo !== null and $this->isSameStdContainerInfo($leftInfo, $rightInfo)) { + return $arrayDimFetch . ' = ' . $this->parseStdContainerCopyExpr($right); + } + $this->fatalError($right, 'Cannot assign non-matching value to nested std::array'); + } return $arrayDimFetch . ' = ' . $this->convertStdValueExpr($info, $right); } @@ -163,6 +249,10 @@ trait StdContainerParser $info = $this->getStdArrayInfo($expr->var); $arrayDimFetch = $this->parseStdArrayDimFetch($expr->var); + $attr = $expr->var->getAttribute('stdArrayDimFetch'); + if ($attr['accessLevel'] < $attr['totalLevel']) { + $this->fatalError($expr, 'Cannot use assign operator on nested std::array'); + } return $arrayDimFetch . ' ' . $binaryOp . '= ' . $this->convertExprFromType($info['type'], $this->parseExpr($expr->expr)); } @@ -185,8 +275,7 @@ trait StdContainerParser protected function parseStdArrayDimFetch(Expr\ArrayDimFetch $expr): string { $tmp = $expr; - $nesting = []; - $level = 0; + $dims = []; $info = $this->getStdArrayInfo($expr); while (true) { @@ -194,24 +283,34 @@ trait StdContainerParser if ($tmp->dim === null) { $this->fatalError($tmp, 'std::array expects an index'); } - $size = $info['sizes'][$level]; - if ($this->isScalarInt($tmp->dim)) { - if ($tmp->dim->value < 0 || $tmp->dim->value >= $size) { - $this->fatalError($tmp, "std::array index out of bounds: index {$tmp->dim->value}, size {$size}"); - } - } - $index = $this->parseExpr($tmp->dim); - $nesting[] = '[' . Symbol::safeIndex($this->convertIntExpr($index), $info['sizes'][$level]) . ']'; + $dims[] = $tmp->dim; $tmp = $tmp->var; - $level++; } else { - $nesting[] = $this->parseVariable($tmp); break; } } + if (!$this->isVarExpr($tmp)) { + $this->fatalError($expr, 'std::array expects a variable'); + } + + $dims = array_reverse($dims); + $sizes = array_reverse($info['sizes']); + if (count($dims) > count($sizes)) { + $this->fatalError($expr, 'std::array access level exceeds array dimensions'); + } - $nesting = array_reverse($nesting); - $expr->setAttribute('stdArrayDimFetch', ['var' => $nesting[0], 'accessLevel' => $level, 'totalLevel' => count($info['sizes'])]); + $nesting = [$this->parseVariable($tmp)]; + foreach ($dims as $level => $dim) { + $size = $sizes[$level]; + if ($this->isScalarInt($dim)) { + if ($dim->value < 0 || $dim->value >= $size) { + $this->fatalError($dim, "std::array index out of bounds: index {$dim->value}, size {$size}"); + } + } + $index = $this->parseExpr($dim); + $nesting[] = '[' . Symbol::safeIndex($this->convertIntExpr($index), $size) . ']'; + } + $expr->setAttribute('stdArrayDimFetch', ['var' => $nesting[0], 'accessLevel' => count($dims), 'totalLevel' => count($sizes)]); return implode('', $nesting); } @@ -460,11 +559,7 @@ trait StdContainerParser if ($this->isClassConstFetch($typeExpr)) { $typeInfo = $this->parseStdValueTypeInfo($typeExpr, 'std::array'); $type = $typeInfo['type']; - $byte = match ($type) { - self::TYPE_BOOL => 1, - self::TYPE_INT, self::TYPE_FLOAT => 8, - default => 16, - }; + $byte = $this->getStdValueTypeBytes($type); break; } if ($this->isStaticCall($typeExpr)) { @@ -478,11 +573,7 @@ trait StdContainerParser } $totalBytes = array_product($nesting) * $byte; - $decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($nesting)); - $decl .= $type; - for ($i = count($nesting) - 1; $i >= 0; $i--) { - $decl .= ', ' . $nesting[$i] . '>'; - } + $decl = $this->getStdArrayDecl($type, $nesting); $this->context->stdArrays[$var] = $this->addStdTypeId([ 'kind' => 'array', 'decl' => $decl, diff --git a/tests/aot/std-array/010.phpt b/tests/aot/std-array/010.phpt new file mode 100644 index 00000000..4a5a10c1 --- /dev/null +++ b/tests/aot/std-array/010.phpt @@ -0,0 +1,34 @@ +--TEST-- +std array: nested same type copy +--FILE-- + +--EXPECT-- +int(10) +int(20) +int(30) +int(10) +int(99) +int(20) +int(30) diff --git a/tests/aot/std-map/008.phpt b/tests/aot/std-map/008.phpt new file mode 100644 index 00000000..67d72b03 --- /dev/null +++ b/tests/aot/std-map/008.phpt @@ -0,0 +1,25 @@ +--TEST-- +std map: same type copy +--FILE-- + +--EXPECT-- +int(2) +int(100) +int(200) +int(100) diff --git a/tests/aot/std-unordered-map/008.phpt b/tests/aot/std-unordered-map/008.phpt new file mode 100644 index 00000000..e3b36e4e --- /dev/null +++ b/tests/aot/std-unordered-map/008.phpt @@ -0,0 +1,25 @@ +--TEST-- +std unordered map: same type copy +--FILE-- + +--EXPECT-- +int(2) +int(100) +int(200) +int(100) diff --git a/tests/aot/std-vector/011.phpt b/tests/aot/std-vector/011.phpt new file mode 100644 index 00000000..fcc59fac --- /dev/null +++ b/tests/aot/std-vector/011.phpt @@ -0,0 +1,25 @@ +--TEST-- +std vector: same type copy +--FILE-- + +--EXPECT-- +int(2) +int(10) +int(20) +int(10)