feat(compiler): 添加标准容器foreach循环支持和删除元素保护

- 实现std::map和std::unordered_map的foreach循环功能
- 实现std::array和std::vector的foreach循环功能
- 添加在foreach循环中删除容器元素的保护机制
- 添加标准容器类型的变量检查和类型验证
- 更新标准容器声明语法以支持键值类型参数
- 添加标准容器键值类型推断功能
pull/1/head
韩天峰 4 months ago
parent 958a1aa2cb
commit ea462010d1
  1. 16
      src/Php/CompilerBase.php
  2. 96
      src/Php/Parser/StdContainerParser.php
  3. 15
      tests/aot/std-array/012.phpt
  4. 32
      tests/aot/std-map/010.phpt
  5. 32
      tests/aot/std-unordered-map/010.phpt
  6. 23
      tests/aot/std-vector/013.phpt

@ -4041,6 +4041,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($var, 'Cannot use [] for array unset');
}
$array = $this->parseIdentifier($var->var);
if (($this->isStdMap($array) or $this->isStdUnorderedMap($array))
and !empty($this->context->stdContainers[$array]['locking'])) {
$this->fatalError($var, 'Cannot delete element in std container in foreach loop');
}
$dim = $this->parseIdentifier($var->dim);
$lines[] = $array . '.offsetUnset(' . $dim . ');';
} elseif ($this->isPropertyFetch($var)) {
@ -4229,6 +4233,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($node, 'Cannot use & with foreach');
}
return $this->parseForeachObject($node);
} elseif ($this->isStdContainerType($type)) {
return $this->parseForeachStdContainer($node);
}
}
}
@ -5313,14 +5319,18 @@ class CompilerBase extends \PhpAot\Core\Translator
return $code;
}
protected function checkVar(NodeAbstract $node, string $name): void
protected function checkVar(NodeAbstract $node, string $name, string $defaultType = self::TYPE_VAR): void
{
if (!$this->hasVar($name)) {
$this->addLocalVar($name, self::TYPE_VAR);
} else {
$this->addLocalVar($name, $defaultType);
} elseif ($defaultType === self::TYPE_VAR) {
if ($this->getVarType($name) !== self::TYPE_VAR) {
$this->fatalError($node, 'Cannot assign value to variable $' . $name . ' of type ' . $this->getVarType($name));
}
} else {
if ($this->getVarType($name) !== $defaultType) {
$this->fatalError($node, 'Cannot assign value to variable $' . $name . ' of type ' . $this->getVarType($name) . ' with type ' . $defaultType);
}
}
}

@ -10,18 +10,24 @@ namespace PhpAot\Php\Parser;
use PhpAot\Php\Symbol;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\NodeAbstract;
trait StdContainerParser
{
protected function isStdContainer(string $var): bool
{
return $this->hasLocalVar($var) and in_array($this->getVarType($var), [
self::TYPE_STD_ARRAY,
self::TYPE_STD_VECTOR,
self::TYPE_STD_MAP,
self::TYPE_STD_UNORDERED_MAP,
], true);
return $this->hasLocalVar($var) and $this->isStdContainerType($this->getVarType($var));
}
protected function isStdContainerType(string $type): bool
{
return in_array($type, [
self::TYPE_STD_ARRAY,
self::TYPE_STD_VECTOR,
self::TYPE_STD_MAP,
self::TYPE_STD_UNORDERED_MAP,
], true);
}
protected function isStdArray(string $var): bool
@ -72,6 +78,28 @@ trait StdContainerParser
return $this->context->stdContainers[$var];
}
protected function getStdContainerKeyType(string $var): string
{
if ($this->isStdVector($var) or $this->isStdArray($var)) {
return self::TYPE_INT;
}
return $this->getStdContainerVarInfo($var)['keyType'];
}
protected function getStdContainerValueType(string $var, string $valueVar): string
{
$info = $this->getStdContainerVarInfo($var);
if ($this->isStdArray($var)) {
return count($info['sizes']) > 1 ? self::TYPE_ARRAY : $info['type'];
}
if ($info['type'] === self::TYPE_OBJECT and $info['class']) {
$this->addObject($valueVar, $info['class']);
} else {
unset($this->context->objects[$valueVar]);
}
return $info['type'];
}
protected function getStdArrayDecl(string $type, array $sizes): string
{
$decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($sizes));
@ -315,6 +343,56 @@ trait StdContainerParser
return implode('', $nesting);
}
protected function parseForeachStdContainer(Foreach_ $node): string
{
$container = $this->parseIdentifier($node->expr);
if ($this->isStdMap($container) or $this->isStdUnorderedMap($container)) {
$this->context->stdContainers[$container]['locking'] = true;
}
$iterator = $this->genTmpVarName();
$code = "for (auto $iterator = $container.begin(); $iterator != $container.end(); ++$iterator) {" . PHP_EOL;
$this->indentLevel++;
if ($node->keyVar) {
$keyVar = $this->parseIdentifier($node->keyVar);
$this->checkVar($node, $keyVar, $this->getStdContainerKeyType($container));
if ($this->isStdVector($container) or $this->isStdArray($container)) {
$code .= $this->getIndent() . "$keyVar = $iterator - $container.begin();" . PHP_EOL;
} else {
$code .= $this->getIndent() . "$keyVar = {$iterator}->first;" . PHP_EOL;
}
}
if ($node->byRef) {
$this->fatalError($node, 'Cannot use & with std container foreach');
}
if (!$this->isVarExpr($node->valueVar)) {
$this->fatalError($node, 'Cannot assign value to std container foreach');
}
$valueVar = $this->parseIdentifier($node->valueVar);
$this->checkVar($node, $valueVar, $this->getStdContainerValueType($container, $valueVar));
if ($this->isStdVector($container) or $this->isStdArray($container)) {
$code .= $this->getIndent() . "$valueVar = *$iterator;" . PHP_EOL;
} else {
$code .= $this->getIndent() . "$valueVar = {$iterator}->second;" . PHP_EOL;
}
$body = $this->parseStmts($node->stmts);
$this->indentLevel--;
$code .= $this->parseBeforeStmtLines() . PHP_EOL;
$code .= $body . PHP_EOL;
$code .= $this->getIndent() . '}';
unset($this->context->objects[$valueVar]);
if ($this->isStdMap($container) or $this->isStdUnorderedMap($container)) {
$this->context->stdContainers[$container]['locking'] = false;
}
return $code;
}
protected function parseStdContainerDimFetch(Expr\ArrayDimFetch $expr): string
{
if ($this->isStdArrayExpr($expr)) {
@ -532,11 +610,7 @@ trait StdContainerParser
protected function getStdMapDecl(string $containerType, string $keyType, string $valueType): string
{
$decl = $containerType . '<' . $valueType;
if ($keyType === self::TYPE_STR) {
$decl .= ', ' . self::TYPE_STR;
}
return $decl . '>';
return $containerType . '<' . $keyType . ', ' . $valueType . '>';
}
protected function parseStdArray(string $var, Expr\StaticCall $expr): string

@ -0,0 +1,15 @@
--TEST--
std array: foreach
--FILE--
<?php
function main() {
$a = std::array(native_types::type_int, 5);
foreach($a as $k => $item) {
$a[$k] = random_int(1, 1000);
}
$count = array_sum($a);
var_dump($count >= 5);
}
?>
--EXPECT--
bool(true)

@ -0,0 +1,32 @@
--TEST--
std map: unset
--FILE--
<?php
function main() {
$map = std::map(complex_types::type_string, native_types::type_int);
$map["alpha"] = 10;
$map["beta"] = 20;
$map["gamma"] = 30;
foreach ($map as $k => $v) {
var_dump($k, $v);
}
unset($map["beta"]);
echo "unset-------------\n";
foreach ($map as $k => $v) {
var_dump($k, $v);
}
}
?>
--EXPECT--
string(5) "alpha"
int(10)
string(4) "beta"
int(20)
string(5) "gamma"
int(30)
unset-------------
string(5) "alpha"
int(10)
string(5) "gamma"
int(30)

@ -0,0 +1,32 @@
--TEST--
std map: unset
--FILE--
<?php
function main() {
$map = std::unordered_map(complex_types::type_string, native_types::type_int);
$map["alpha"] = 10;
$map["beta"] = 20;
$map["gamma"] = 30;
foreach ($map as $k => $v) {
var_dump($k, $v);
}
unset($map["beta"]);
echo "unset-------------\n";
foreach ($map as $k => $v) {
var_dump($k, $v);
}
}
?>
--EXPECT--
string(5) "gamma"
int(30)
string(4) "beta"
int(20)
string(5) "alpha"
int(10)
unset-------------
string(5) "gamma"
int(30)
string(5) "alpha"
int(10)

@ -0,0 +1,23 @@
--TEST--
std vector: foreach
--FILE--
<?php
use native_types;
function main() {
$a = std::vector(native_types::type_int);
$n = 5;
while($n--){
$a[] = random_int(1, 1000);
}
$count = array_sum($a);
var_dump($count >= 5);
$total = 0;
foreach($a as $v){
$total += $v;
}
var_dump($total == $count);
}
?>
--EXPECT--
bool(true)
bool(true)
Loading…
Cancel
Save