feat(compiler): 添加对标准容器类型的完整支持

- 实现了标准数组维度获取的类型检测和解析功能
- 扩展了类型转换系统以支持字符串、数组和对象类型
- 在polyfills.php中添加了新的类型常量和容器函数定义
- 重构了标准容器解析器以支持复杂的值类型验证
- 实现了标准容器赋值操作中的类型安全检查
- 添加了类值类型的支持和验证机制
- 完善了标准容器的类型信息解析和存储功能
pull/1/head
韩天峰 4 months ago
parent fb2301ab5e
commit 73bf28667d
  1. 18
      examples/stdmap.php
  2. 26
      src/Php/CompilerBase.php
  3. 139
      src/Php/Parser/StdContainerParser.php
  4. 19
      src/polyfills.php

@ -0,0 +1,18 @@
<?php
class Foo {
public string $a;
public int $b;
public function __construct(string $a, int $b) {
$this->a = $a;
$this->b = $b;
}
}
function main()
{
$map = std::map(complex_types::type_str, Foo::class);
$map["a"] = new Foo("a", 1);
$map["b"] = new Foo("b", 2);
var_dump($map);
}

@ -1767,6 +1767,23 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
}
if ($this->isArrayDimFetch($expr) and $this->isStdContainerExpr($expr)) {
if ($this->isStdArrayExpr($expr)) {
if (!$expr->hasAttribute('stdArrayDimFetch')) {
$this->parseStdArrayDimFetch($expr);
}
$attr = $expr->getAttribute('stdArrayDimFetch');
if ($attr['accessLevel'] === $attr['totalLevel']) {
return $this->context->stdArrays[$attr['var']]['class'] ?? '';
}
return '';
}
if (!$expr->hasAttribute('stdContainerDimFetch')) {
$this->parseStdContainerDimFetch($expr);
}
$attr = $expr->getAttribute('stdContainerDimFetch');
return $this->context->stdContainers[$attr['var']]['class'] ?? '';
}
if ($this->isMethodCall($expr) and $this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) {
$object = $this->parseVariable($expr->var);
try {
@ -4534,6 +4551,15 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($type === self::TYPE_BOOL) {
return $this->convertBoolExpr($expr);
}
if ($type === self::TYPE_STR) {
return $this->convertStringExpr($expr);
}
if ($type === self::TYPE_ARRAY) {
return $this->convertArrayExpr($expr);
}
if ($type === self::TYPE_OBJECT) {
return $this->convertObjectExpr($expr);
}
return $expr;
}

@ -61,9 +61,8 @@ trait StdContainerParser
$this->fatalError($expr, 'fill() only support std::array');
}
$array = $this->parseIdentifier($expr->args[0]->value);
$valueExpr = $this->parseExpr($expr->args[1]->value);
$type = $this->context->stdArrays[$array]['type'];
$value = $this->convertExprFromType($type, $valueExpr);
$info = $this->context->stdArrays[$array];
$value = $this->convertStdValueExpr($info, $expr->args[1]->value);
return "{$array}.fill({$value})";
}
@ -103,7 +102,7 @@ trait StdContainerParser
{
$info = $this->getStdArrayInfo($left);
$arrayDimFetch = $this->parseStdArrayDimFetch($left);
return $arrayDimFetch . ' = ' . $this->convertExprFromType($info['type'], $this->parseExpr($right));
return $arrayDimFetch . ' = ' . $this->convertStdValueExpr($info, $right);
}
protected function parseStdContainerAssign(Expr\ArrayDimFetch $left, NodeAbstract $right): string
@ -118,13 +117,13 @@ trait StdContainerParser
$this->fatalError($left, 'std::vector append only supports a vector variable');
}
$vector = $this->parseVariable($left->var);
return $vector . '.push_back(' . $this->convertExprFromType($info['type'], $this->parseExpr($right)) . ')';
return $vector . '.push_back(' . $this->convertStdValueExpr($info, $right) . ')';
}
if ($left->dim === null) {
$this->fatalError($left, 'std map expects a key');
}
return $this->parseStdContainerOffsetSet($left, $this->convertExprFromType($info['type'], $this->parseExpr($right)));
return $this->parseStdContainerOffsetSet($left, $this->convertStdValueExpr($info, $right));
}
protected function parseStdArrayAssignOp(Expr\AssignOp $expr, string $op): string
@ -265,6 +264,90 @@ trait StdContainerParser
};
}
protected function parseStdValueTypeInfo(NodeAbstract $expr, string $owner): array
{
if (!$this->isClassConstFetch($expr)) {
$this->fatalError($expr, "{$owner} expects a native_types or complex_types class constant");
}
if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {
$this->fatalError($expr, "An incorrect `{$owner}` definition");
}
if ($expr->class->toString() === 'native_types') {
return ['type' => $this->parseStdNativeType($expr, $owner), 'class' => null];
}
if ($expr->class->toString() === 'complex_types') {
return [
'type' => match ($expr->name->name) {
'type_str', 'type_string' => self::TYPE_STR,
'type_array' => self::TYPE_ARRAY,
'type_object' => self::TYPE_OBJECT,
'type_any', 'type_var', 'type_variant' => self::TYPE_VAR,
default => $this->fatalError($expr, "An incorrect `{$owner}` definition"),
},
'class' => null,
];
}
if ($expr->name->name !== 'class') {
$this->fatalError($expr, "{$owner} class value only supports ClassName::class");
}
$class = $this->parseStdClassValueType($expr, $owner);
return ['type' => self::TYPE_OBJECT, 'class' => $class];
}
protected function parseStdValueType(NodeAbstract $expr, string $owner): string
{
return $this->parseStdValueTypeInfo($expr, $owner)['type'];
}
protected function parseStdClassValueType(Expr\ClassConstFetch $expr, string $owner): string
{
$class = $this->parseIdentifier($expr->class);
if ($class === 'static') {
$this->fatalError($expr, "{$owner} class value does not support static::class");
}
if ($class === 'self' || $class === 'this_') {
if (!$this->classDef) {
$this->fatalError($expr, "{$owner} class value cannot use self::class outside class scope");
}
$class = $this->class;
} elseif ($class === 'parent') {
if (!$this->classDef || !$this->classDef->extends) {
$this->fatalError($expr, "{$owner} class value cannot use parent::class because current class does not extend any class");
}
$class = $this->classDef->extends;
} else {
$class = $this->getNamespacedClassName($class);
}
if ($this->hasInterface($class)) {
$this->fatalError($expr, "{$owner} class value cannot use interface `{$class}`");
}
if ($this->isAbstractClass($class)) {
$this->fatalError($expr, "{$owner} class value cannot use abstract class `{$class}`");
}
return $class;
}
protected function convertStdValueExpr(array $info, NodeAbstract $expr): string
{
$valueExpr = $this->parseExpr($expr);
$class = $info['class'] ?? null;
if ($class === null) {
return $this->convertExprFromType($info['type'], $valueExpr);
}
$rightClass = $this->detectClassOfExpr($expr);
if ($rightClass !== '') {
if ($rightClass !== $class) {
$this->fatalError($expr, "Cannot assign object of class `{$rightClass}` to std container value of class `{$class}`");
}
return $this->convertObjectExpr($valueExpr);
}
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$this->context->beforeStmtLines[] = "{$tmpVar} = {$valueExpr};";
$this->context->beforeStmtLines[] = 'if (!' . $tmpVar . '.isObject() || ' . $tmpVar . '.ce() != ' . $this->getClassEntryPtr($class) . ') { zend_throw_error(NULL, "std container value expects exact object of class `%s`", "' . $this->escapeString($class) . '"); php::setDebugInfo(); if (php::throw_impl) { php::throw_impl(EG(exception)); } }';
return self::TYPE_OBJECT . '(' . $tmpVar . ')';
}
protected function parseStdMapKeyType(NodeAbstract $expr, string $owner): string
{
if (!$this->isClassConstFetch($expr) || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {
@ -306,29 +389,15 @@ trait StdContainerParser
$nesting[] = $size;
$typeExpr = $tmp->args[0]->value;
if ($this->isClassConstFetch($typeExpr)) {
if (!$this->isNameExpr($typeExpr->class) || !$this->isIdExpr($typeExpr->name) || $typeExpr->class->toString() !== 'native_types') {
$this->fatalError($tmp, 'An incorrect `std::array` definition');
}
switch ($typeExpr->name->name) {
case 'type_int':
$type = self::TYPE_INT;
$byte = 8;
break;
case 'type_float':
$type = self::TYPE_FLOAT;
$byte = 8;
break;
case 'type_bool':
$type = self::TYPE_BOOL;
$byte = 1;
break;
default:
$this->fatalError($tmp, 'An incorrect `std::array` definition');
break;
}
$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,
};
break;
}
$totalBytes += $size * $byte;
if ($this->isStaticCall($typeExpr)) {
$tmp = $typeExpr;
if (!$this->isNameExpr($tmp->class) || !$this->isIdExpr($tmp->name) || $tmp->class->toString() !== 'std' || $tmp->name->toString() !== 'array') {
@ -338,6 +407,7 @@ trait StdContainerParser
$this->fatalError($tmp, 'std::array() expects first argument to be a class constant');
}
}
$totalBytes = array_product($nesting) * $byte;
$decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($nesting));
$decl .= $type;
@ -347,6 +417,7 @@ trait StdContainerParser
$this->context->stdArrays[$var] = [
'decl' => $decl,
'type' => $type,
'class' => $typeInfo['class'],
'sizes' => array_reverse($nesting),
'bytes' => $totalBytes,
];
@ -358,7 +429,8 @@ trait StdContainerParser
if (count($expr->args) < 1 || count($expr->args) > 2) {
$this->fatalError($expr, 'std::vector() expects one or two arguments');
}
$type = $this->parseStdNativeType($expr->args[0]->value, 'std::vector');
$typeInfo = $this->parseStdValueTypeInfo($expr->args[0]->value, 'std::vector');
$type = $typeInfo['type'];
$size = null;
if (count($expr->args) === 2) {
if (!$this->isScalarInt($expr->args[1]->value)) {
@ -371,6 +443,7 @@ trait StdContainerParser
'kind' => 'vector',
'decl' => $decl,
'type' => $type,
'class' => $typeInfo['class'],
'size' => $size,
];
return '// ' . $decl;
@ -382,12 +455,14 @@ trait StdContainerParser
$this->fatalError($expr, 'std::map() expects two arguments');
}
$keyType = $this->parseStdMapKeyType($expr->args[0]->value, 'std::map');
$valueType = $this->parseStdNativeType($expr->args[1]->value, 'std::map');
$valueTypeInfo = $this->parseStdValueTypeInfo($expr->args[1]->value, 'std::map');
$valueType = $valueTypeInfo['type'];
$decl = $this->getStdMapDecl(self::TYPE_STD_MAP, $keyType, $valueType);
$this->context->stdContainers[$var] = [
'kind' => 'map',
'decl' => $decl,
'type' => $valueType,
'class' => $valueTypeInfo['class'],
'keyType' => $keyType,
];
return '// ' . $decl;
@ -399,14 +474,16 @@ trait StdContainerParser
$this->fatalError($expr, 'std::unordered_map() expects two arguments');
}
$keyType = $this->parseStdMapKeyType($expr->args[0]->value, 'std::unordered_map');
$valueType = $this->parseStdNativeType($expr->args[1]->value, 'std::unordered_map');
$valueTypeInfo = $this->parseStdValueTypeInfo($expr->args[1]->value, 'std::unordered_map');
$valueType = $valueTypeInfo['type'];
$decl = $this->getStdMapDecl(self::TYPE_STD_UNORDERED_MAP, $keyType, $valueType);
$this->context->stdContainers[$var] = [
'kind' => 'unordered_map',
'decl' => $decl,
'type' => $valueType,
'class' => $valueTypeInfo['class'],
'keyType' => $keyType,
];
return '// ' . $decl;
}
}
}

@ -15,6 +15,8 @@ class native_types
class complex_types {
public const type_any = 'any';
public const type_var = 'any';
public const type_variant = 'any';
public const type_str = 'string';
public const type_string = 'string';
public const type_array = 'array';
@ -43,6 +45,21 @@ class std
return [];
}
public static function map(mixed $key_type, mixed $value_type): array
{
return [];
}
public static function unordered_map(mixed $key_type, mixed $value_type): array
{
return [];
}
public static function vector(mixed $value_type): array
{
return [];
}
public static function fill(array $array, mixed $value): void
{
for ($i = 0; $i < count($array); $i++) {
@ -67,4 +84,4 @@ function &refval(&$var)
function any(mixed $var): mixed
{
return $var;
}
}

Loading…
Cancel
Save