feat(compiler): 添加对 bigint、bigfloat 和 decimal 类型的支持

- 在 zendTypeMap 中添加了 bigint、bigfloat 和 decimal 类型映射
- 实现了类型名称的大小写不敏感匹配逻辑
- 添加了对 std 类中 bigInt、bigFloat 和 decimal 方法的类型推断
- 支持 native_types、decimal_types 和 bigint_types 的大小写不敏感解析
- 在 polyfills.php 中定义了新的类型常量
- 添加了相关测试用例验证大数运算功能
- 优化了变量类型检测和赋值时的类型处理逻辑
pull/1/head
韩天峰 3 months ago
parent c604fc09ab
commit 4c3d7ca4c3
  1. 56
      src/Php/CompilerBase.php
  2. 17
      src/Php/Parser/StdContainerParser.php
  3. 4
      src/polyfills.php
  4. 13
      tests/aot/basic/pow-int-overflow-native-type.phpt
  5. 12
      tests/aot/basic/pow-int-overflow.phpt
  6. 14
      tests/aot/bigint/pow-02.phpt
  7. 4
      tests/aot/decimal/round-02.phpt

@ -169,6 +169,9 @@ class CompilerBase extends \PhpAot\Core\Translator
// iterable 类型,可以是数组或者对象 // iterable 类型,可以是数组或者对象
'iterable' => self::TYPE_VAR, 'iterable' => self::TYPE_VAR,
'stream' => self::TYPE_STREAM, 'stream' => self::TYPE_STREAM,
'bigint' => self::TYPE_BIGINT,
'bigfloat' => self::TYPE_BIGFLOAT,
'decimal' => self::TYPE_DECIMAL,
]; ];
protected array $globalHeaders = [ protected array $globalHeaders = [
'phpx.h', 'phpx.h',
@ -1058,11 +1061,12 @@ class CompilerBase extends \PhpAot\Core\Translator
return self::TYPE_VAR; return self::TYPE_VAR;
} else { } else {
$typeName = $this->parseIdentifier($type); $typeName = $this->parseIdentifier($type);
$typeNameLower = strtolower($typeName);
// 属性和类常量的类型不能声明为 void/never ,只有返回值可以 // 属性和类常量的类型不能声明为 void/never ,只有返回值可以
if ($what !== self::DECL_TYPE_OF_RETURN and ($typeName === 'void' or $typeName === 'never')) { if ($what !== self::DECL_TYPE_OF_RETURN and ($typeNameLower === 'void' or $typeNameLower === 'never')) {
$this->fatalError($type, 'The type `void`/`never` is allowed only for return type'); $this->fatalError($type, 'The type `void`/`never` is allowed only for return type');
} elseif (isset($this->zendTypeMap[$typeName])) { } elseif (isset($this->zendTypeMap[$typeNameLower])) {
return $this->getTypeFromZendType($typeName); return $this->getTypeFromZendType($typeNameLower);
} else { } else {
if ($typeName === 'self') { if ($typeName === 'self') {
$class = $this->getFullClassName(); $class = $this->getFullClassName();
@ -1636,7 +1640,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($var === 'this_') { if ($var === 'this_') {
$this->fatalError($left, 'Cannot re-assign $this'); $this->fatalError($left, 'Cannot re-assign $this');
} }
$type = $this->detectTypeOfExpr($right); $finalVarType = $type = $this->detectTypeOfExpr($right);
if ($this->isVarExpr($left)) { if ($this->isVarExpr($left)) {
if ($this->isStdContainer($var)) { if ($this->isStdContainer($var)) {
@ -1669,6 +1673,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$type = self::TYPE_VAR; $type = self::TYPE_VAR;
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type); $this->addLocalVar($var, $type);
$finalVarType = $type;
} }
return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); return $var . ' = ' . $this->parseIdentifier($right->args[0]->value);
} else { } else {
@ -1709,7 +1714,8 @@ class CompilerBase extends \PhpAot\Core\Translator
} else { } else {
$valueExpr = $this->parseStdCall($right); $valueExpr = $this->parseStdCall($right);
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {
$this->addLocalVar($var, $right->getAttribute('nativeType')); $finalVarType = $right->getAttribute('nativeType');
$this->addLocalVar($var, $finalVarType);
} }
return $var . ' = ' . $valueExpr; return $var . ' = ' . $valueExpr;
} }
@ -1725,7 +1731,8 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
// 变量第一次被赋值,确定其类型,由于 PHP 的变量作用域是 function 级的,在 for/while 块中声明的变量,可以在块外使用 // 变量第一次被赋值,确定其类型,由于 PHP 的变量作用域是 function 级的,在 for/while 块中声明的变量,可以在块外使用
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {
$this->addLocalVar($var, $this->isNativeType($type) ? $this->getNativeType($type) : $type); $finalVarType = $this->isNativeType($type) ? $this->getNativeType($type) : $type;
$this->addLocalVar($var, $finalVarType);
} else { } else {
$this->checkVarAssignExpr($left, $this->getVarType($var), $type); $this->checkVarAssignExpr($left, $this->getVarType($var), $type);
} }
@ -1744,7 +1751,13 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$rightExpr = $this->parseAssignRightExpr($right); $rightExpr = $this->parseAssignRightExpr($right);
return $var . ' = ' . $this->convertExprType($rightExpr, $this->detectTypeOfExpr($left), $this->detectTypeOfExpr($right)); $leftExprType = $this->detectTypeOfExpr($left);
$rightExprType = $this->detectTypeOfExpr($right);
if ($finalVarType === self::TYPE_VAR) {
return $var . ' = ' . $rightExpr;
} else {
return $var . ' = ' . $this->convertExprType($rightExpr, $leftExprType, $rightExprType);
}
} }
protected function parseStdContainerCopyAssign(string $leftVar, Expr $right): ?string protected function parseStdContainerCopyAssign(string $leftVar, Expr $right): ?string
@ -2455,14 +2468,6 @@ class CompilerBase extends \PhpAot\Core\Translator
return self::TYPE_FLOAT; return self::TYPE_FLOAT;
} }
if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) { if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) {
// 除法存在特殊性,若未能整除,会返回浮点数,其他则一律视为整数
if ($exprType === 'Expr_BinaryOp_Div') {
if ($leftType === self::TYPE_INT && $rightType === self::TYPE_INT) {
return self::TYPE_INT;
} else {
return self::TYPE_VAR;
}
}
return self::TYPE_INT; return self::TYPE_INT;
} }
break; break;
@ -2540,12 +2545,12 @@ class CompilerBase extends \PhpAot\Core\Translator
return self::TYPE_OBJECT; return self::TYPE_OBJECT;
} }
$className = $this->parseIdentifier($expr->class); $className = $this->parseIdentifier($expr->class);
if ($className === 'std') { if (strtolower($className) === 'std') {
$method = $this->parseIdentifier($expr->name); $method = strtolower($this->parseIdentifier($expr->name));
return match ($method) { return match ($method) {
'bigInt' => self::TYPE_BIGINT, 'bigint' => self::TYPE_BIGINT,
'decimal' => self::TYPE_DECIMAL, 'decimal' => self::TYPE_DECIMAL,
'bigFloat' => self::TYPE_BIGFLOAT, 'bigfloat' => self::TYPE_BIGFLOAT,
default => self::TYPE_VAR, default => self::TYPE_VAR,
}; };
} }
@ -5990,11 +5995,12 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->useConstants[$cn] = $fullName; $this->useConstants[$cn] = $fullName;
} }
} else { } else {
if ($id === 'native_types') { $idLower = strtolower($id);
if ($idLower === 'native_types') {
$this->nativeTypes = true; $this->nativeTypes = true;
} elseif ($id === 'decimal_types') { } elseif ($idLower === 'decimal_types') {
$this->decimalTypes = true; $this->decimalTypes = true;
} elseif ($id === 'bigint_types') { } elseif ($idLower === 'bigint_types') {
$this->bigintTypes = true; $this->bigintTypes = true;
} else { } else {
$this->useNamespaces[] = $id; $this->useNamespaces[] = $id;
@ -6184,14 +6190,14 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseStdCall(Expr\StaticCall $expr): string protected function parseStdCall(Expr\StaticCall $expr): string
{ {
$func = $this->parseIdentifier($expr->name); $func = strtolower($this->parseIdentifier($expr->name));
$type = match ($func) { $type = match ($func) {
'int' => self::TYPE_INT, 'int' => self::TYPE_INT,
'float' => self::TYPE_FLOAT, 'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL, 'bool' => self::TYPE_BOOL,
'bigInt' => self::TYPE_BIGINT, 'bigint' => self::TYPE_BIGINT,
'decimal' => self::TYPE_DECIMAL, 'decimal' => self::TYPE_DECIMAL,
'bigFloat' => self::TYPE_BIGFLOAT, 'bigfloat' => self::TYPE_BIGFLOAT,
default => '', default => '',
}; };
if ($type) { if ($type) {

@ -467,10 +467,10 @@ trait StdContainerParser
if (!$this->isClassConstFetch($expr)) { if (!$this->isClassConstFetch($expr)) {
$this->fatalError($expr, "{$owner} expects a native_types class constant"); $this->fatalError($expr, "{$owner} expects a native_types class constant");
} }
if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name) || $expr->class->toString() !== 'native_types') { if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name) || strtolower($expr->class->toString()) !== 'native_types') {
$this->fatalError($expr, "An incorrect `{$owner}` definition"); $this->fatalError($expr, "An incorrect `{$owner}` definition");
} }
return match ($expr->name->name) { return match (strtolower($expr->name->name)) {
'type_int' => self::TYPE_INT, 'type_int' => self::TYPE_INT,
'type_float' => self::TYPE_FLOAT, 'type_float' => self::TYPE_FLOAT,
'type_bool' => self::TYPE_BOOL, 'type_bool' => self::TYPE_BOOL,
@ -489,12 +489,13 @@ trait StdContainerParser
if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) { if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {
$this->fatalError($expr, "An incorrect `{$owner}` definition"); $this->fatalError($expr, "An incorrect `{$owner}` definition");
} }
if ($expr->class->toString() === 'native_types') { $className = strtolower($expr->class->toString());
if ($className === 'native_types') {
return ['type' => $this->parseStdNativeType($expr, $owner), 'class' => null]; return ['type' => $this->parseStdNativeType($expr, $owner), 'class' => null];
} }
if ($expr->class->toString() === 'complex_types') { if ($className === 'complex_types') {
return [ return [
'type' => match ($expr->name->name) { 'type' => match (strtolower($expr->name->name)) {
'type_str', 'type_string' => self::TYPE_STR, 'type_str', 'type_string' => self::TYPE_STR,
'type_array' => self::TYPE_ARRAY, 'type_array' => self::TYPE_ARRAY,
'type_object' => self::TYPE_OBJECT, 'type_object' => self::TYPE_OBJECT,
@ -634,10 +635,12 @@ trait StdContainerParser
if (!$this->isClassConstFetch($expr) || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) { if (!$this->isClassConstFetch($expr) || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {
$this->fatalError($expr, "{$owner} expects a native_types or complex_types class constant"); $this->fatalError($expr, "{$owner} expects a native_types or complex_types class constant");
} }
if ($expr->class->toString() === 'native_types' && $expr->name->name === 'type_int') { $className = strtolower($expr->class->toString());
$constName = strtolower($expr->name->name);
if ($className === 'native_types' && $constName === 'type_int') {
return self::TYPE_INT; return self::TYPE_INT;
} }
if ($expr->class->toString() === 'complex_types' && in_array($expr->name->name, ['type_string', 'type_str'], true)) { if ($className === 'complex_types' && in_array($constName, ['type_string', 'type_str'], true)) {
return self::TYPE_STR; return self::TYPE_STR;
} }
$this->fatalError($expr, "{$owner} key only supports native_types::type_int, complex_types::type_string or complex_types::type_str"); $this->fatalError($expr, "{$owner} key only supports native_types::type_int, complex_types::type_string or complex_types::type_str");

@ -11,6 +11,9 @@ class native_types
public const type_int = 'int'; public const type_int = 'int';
public const type_float = 'float'; public const type_float = 'float';
public const type_bool = 'bool'; public const type_bool = 'bool';
public const type_bigint = 'bigint';
public const type_bigfloat = 'bigfloat';
public const type_decimal = 'decimal';
} }
class complex_types { class complex_types {
@ -21,6 +24,7 @@ class complex_types {
public const type_string = 'string'; public const type_string = 'string';
public const type_array = 'array'; public const type_array = 'array';
public const type_object = 'object'; public const type_object = 'object';
public const type_stream = 'stream';
} }
class std class std

@ -0,0 +1,13 @@
--TEST--
pow int overflow
--FILE--
<?php
use native_types;
function main()
{
$a = 2 ** 80;
echo $a, PHP_EOL;
}
?>
--EXPECT--
0

@ -0,0 +1,12 @@
--TEST--
pow int overflow
--FILE--
<?php
function main()
{
$a = 2 ** 80;
echo $a, PHP_EOL;
}
?>
--EXPECT--
1.2089258196146E+24

@ -0,0 +1,14 @@
--TEST--
BigInt: use
--FILE--
<?php
use bigint_types;
function main()
{
$a = 2 ** 80;
echo $a, PHP_EOL;
}
?>
--EXPECT--
1208925819614629174706176

@ -15,11 +15,11 @@ function main(): void {
$total = $subtotal + $tax; $total = $subtotal + $tax;
echo "小计: " . round($subtotal, 2) . "\n"; // 89.97 echo "小计: " . round($subtotal, 2) . "\n"; // 89.97
echo "税额: " . round($tax, 4) . "\n"; // 7.1976 echo "税额: " . round($tax, 20) . "\n"; // 7.19760000000000000000
echo "总计: " . round($total, 2) . "\n"; // 97.17 echo "总计: " . round($total, 2) . "\n"; // 97.17
} }
?> ?>
--EXPECT-- --EXPECT--
小计: 89.97 小计: 89.97
税额: 7.1976 税额: 7.19760000000000000000
总计: 97.17 总计: 97.17

Loading…
Cancel
Save