test(aot): 添加标准容器复制赋值功能测试

- 新增 std map 相同类型复制测试用例
- 新增 std unordered_map 相同类型复制测试用例
- 新增 std array 嵌套相同类型复制测试用例
- 新增 std vector 相同类型复制测试用例
- 实现标准容器复制赋值语法解析功能
- 添加嵌套数组访问层级检查机制
- 完善类型匹配验证逻辑
pull/1/head
韩天峰 4 months ago
parent 4411216354
commit fa2cb45df5
  1. 21
      src/Php/CompilerBase.php
  2. 139
      src/Php/Parser/StdContainerParser.php
  3. 34
      tests/aot/std-array/010.phpt
  4. 25
      tests/aot/std-map/008.phpt
  5. 25
      tests/aot/std-unordered-map/008.phpt
  6. 25
      tests/aot/std-vector/011.phpt

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

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

@ -0,0 +1,34 @@
--TEST--
std array: nested same type copy
--FILE--
<?php
function main() {
$a = std::array(native_types::type_int, 3);
$b = std::array(std::array(native_types::type_int, 3), 2);
$b[1][0] = 10;
$b[1][1] = 20;
$b[1][2] = 30;
$a = $b[1];
var_dump($a[0]);
var_dump($a[1]);
var_dump($a[2]);
$a[0] = 99;
var_dump($b[1][0]);
$b[0] = $a;
var_dump($b[0][0]);
var_dump($b[0][1]);
var_dump($b[0][2]);
}
?>
--EXPECT--
int(10)
int(20)
int(30)
int(10)
int(99)
int(20)
int(30)

@ -0,0 +1,25 @@
--TEST--
std map: same type copy
--FILE--
<?php
function main() {
$a = std::map(native_types::type_int, native_types::type_int);
$b = std::map(native_types::type_int, native_types::type_int);
$b[10] = 100;
$b[20] = 200;
$a = $b;
var_dump(count($a));
var_dump($a[10]);
var_dump($a[20]);
$a[10] = 999;
var_dump($b[10]);
}
?>
--EXPECT--
int(2)
int(100)
int(200)
int(100)

@ -0,0 +1,25 @@
--TEST--
std unordered map: same type copy
--FILE--
<?php
function main() {
$a = std::unordered_map(native_types::type_int, native_types::type_int);
$b = std::unordered_map(native_types::type_int, native_types::type_int);
$b[10] = 100;
$b[20] = 200;
$a = $b;
var_dump(count($a));
var_dump($a[10]);
var_dump($a[20]);
$a[10] = 999;
var_dump($b[10]);
}
?>
--EXPECT--
int(2)
int(100)
int(200)
int(100)

@ -0,0 +1,25 @@
--TEST--
std vector: same type copy
--FILE--
<?php
function main() {
$a = std::vector(native_types::type_int);
$b = std::vector(native_types::type_int);
$b[] = 10;
$b[] = 20;
$a = $b;
var_dump(count($a));
var_dump($a[0]);
var_dump($a[1]);
$a[0] = 99;
var_dump($b[0]);
}
?>
--EXPECT--
int(2)
int(10)
int(20)
int(10)
Loading…
Cancel
Save