fix(compiler): 修复PHP编译器中的类型检查和表达式解析问题

- 移除不再使用的常量定义 EXPR_VARIABLE、EXPR_NEW、EXPR_ARRAY_DIM_FETCH、EXPR_PROPERTY_FETCH
- 为 parseExpr 方法添加返回类型声明 string
- 将 switch 语句中的常量引用改为直接字符串引用
- 在变量重新赋值时添加对象到数组的类型检查限制
- 禁止对象与数组之间的相互转换
- 修复变量类型检测中的常量引用问题
- 删除未使用的 leftExprType 变量
- 使用 isArrayDimFetch 方法替代类型比较
- 优化布尔取反表达式的解析逻辑
- 添加对 self 和 parent 关键字的支持
- 重构 unset 语句的类型判断逻辑
- 修复 foreach 循环中数组维度获取的判断方式
- 修正 Translator 类中的格式化和类型引用问题
pull/1/head
韩天峰 4 months ago
parent 8c4bfd6683
commit c94d7afdba
  1. 54
      src/Php/CompilerBase.php
  2. 76
      src/Php/Translator.php
  3. 24
      tests/aot/self-class/001.phpt

@ -78,13 +78,6 @@ class CompilerBase extends \PhpAot\Core\Translator
public const string CLASS_MAP = 'class_map';
public const string FUNC_MAP = 'func_map';
public const string PROP_MAP = 'property_map';
public const string EXPR_VARIABLE = 'Expr_Variable';
public const string EXPR_NEW = 'Expr_New';
public const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch';
public const string EXPR_PROPERTY_FETCH = 'Expr_PropertyFetch';
public const string NAMESPACE_SEPARATOR = '__';
public const string PREFIX = 'php_';
@ -321,7 +314,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->context->objects[$object] ?? 'stdClass';
}
public function parseExpr(NodeAbstract $expr)
public function parseExpr(NodeAbstract $expr): string
{
if ($expr->hasAttribute('replace')) {
return $expr->getAttribute('replace');
@ -416,9 +409,9 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->parseBinaryOpMinus($expr);
case 'Expr_Array':
return $this->parseArray($expr);
case self::EXPR_ARRAY_DIM_FETCH:
case 'Expr_ArrayDimFetch':
return $this->parseArrayDimFetch($expr, $this->context->inAssignExpr);
case self::EXPR_PROPERTY_FETCH:
case 'Expr_PropertyFetch':
return $this->parsePropertyFetch($expr, $this->context->inAssignExpr);
case 'Expr_NullsafePropertyFetch':
return $this->parseNullsafePropertyFetch($expr);
@ -477,7 +470,7 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Scalar_Int':
case 'Scalar_Float':
case 'Scalar_String':
case self::EXPR_VARIABLE:
case 'Expr_Variable':
return $this->parseIdentifier($expr);
case 'Scalar_MagicConst_File':
case 'Scalar_MagicConst_Dir':
@ -979,7 +972,7 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$type = $expr->getType();
switch ($type) {
case self::EXPR_VARIABLE:
case 'Expr_Variable':
return $this->parseVariable($expr);
case 'Name_FullyQualified':
return '\\' . $expr->name;
@ -1306,6 +1299,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`");
}
} else {
if ($this->getVarType($var) === self::TYPE_ARRAY) {
$this->fatalError($left, "Cannot re-assign `\${$var}` from object to array");
}
// TODO 右值是一个类型对象,但左值是一个 var ,许可,但无法标记对象类型
}
} else {
@ -1344,6 +1340,8 @@ class CompilerBase extends \PhpAot\Core\Translator
// 禁止字符串与数组互相转换,其他类型如对象可以使用 __toString() 协议转为字符串,整数和浮点型也可以转为字符串
or ($this->getVarType($var) === self::TYPE_STR and $type === self::TYPE_ARRAY)
or ($this->getVarType($var) === self::TYPE_ARRAY and $type === self::TYPE_STR)
// 禁止对象转为数组
or ($this->getVarType($var) === self::TYPE_ARRAY and $type === self::TYPE_OBJECT)
) {
$this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type);
}
@ -1904,7 +1902,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return self::TYPE_OBJECT;
case 'Expr_Assign':
return $this->detectVarType($expr->var);
case self::EXPR_VARIABLE:
case 'Expr_Variable':
return $this->detectVarType($expr);
case 'Expr_ConstFetch':
return $this->detectConstType($expr);
@ -2145,7 +2143,6 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$var = $this->parseIdentifier($node->var);
$expr = $this->parseIdentifier($node->expr);
$leftExprType = $node->var->getType();
if ($this->isVarExpr($node->var)) {
if (!$this->hasVar($var)) {
@ -2166,7 +2163,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $var . ' ' . $op . ' ' . $rightExprStr;
}
if ($leftExprType === self::EXPR_ARRAY_DIM_FETCH) {
if ($this->isArrayDimFetch($node->var)) {
/**
* $count[$r] -= 1;
* 需要转为下面语句:
@ -2882,9 +2879,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBooleanNot(Expr\BooleanNot $expr): string
{
$expr = $this->parseExpr($expr->expr);
return '!(' . $expr . ')';
return '!(' . $this->parseExpr($expr->expr) . ')';
}
protected function parseWhile(Node\Stmt\While_ $v): string
@ -3093,7 +3088,13 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($className === 'static') {
$cePtr = Symbol::getCalledCe();
} else {
$className = $this->getNamespacedClassName($className);
if ($className === 'self') {
$className = $this->getFullClassName();
} elseif ($className === 'parent') {
$className = $this->classDef->extends;
} else {
$className = $this->getNamespacedClassName($className);
}
if ($this->hasClass($className)) {
$classDef = $this->getClass($className);
if ($classDef->flags & Modifiers::ABSTRACT) {
@ -3370,23 +3371,22 @@ class CompilerBase extends \PhpAot\Core\Translator
$vars = $node->vars;
$lines = [];
foreach ($vars as $var) {
$type = $var->getType();
if ($type === self::EXPR_ARRAY_DIM_FETCH) {
if ($this->isArrayDimFetch($var)) {
$array = $this->parseIdentifier($var->var);
$dim = $this->parseIdentifier($var->dim);
$lines[] = $array . '.offsetUnset(' . $dim . ');';
} elseif ($type === 'Expr_PropertyFetch') {
} elseif ($this->isPropertyFetch($var)) {
$object = $this->parseIdentifier($var->var);
$propName = $this->identifierToStr($var->name, literal: true);
$lines[] = $object . '.unsetProperty(' . $propName . ');';
} elseif ($type === self::EXPR_VARIABLE) {
} elseif ($this->isVarExpr($var)) {
$name = $this->parseIdentifier($var);
if (!$this->hasVar($name)) {
$this->errorUndefinedVariable($var);
}
$lines[] = "{$name}.unset();";
} else {
$this->fatalError($var, "Unsupported unset type `{$type}`");
$this->fatalError($var, "Unsupported unset type `{$var->getType()}`");
}
}
@ -3472,7 +3472,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($node, 'Foreach by reference only supports variable as value');
}
if ($node->valueVar->getType() == self::EXPR_ARRAY_DIM_FETCH) {
if ($this->isArrayDimFetch($node->valueVar)) {
$array = $this->parseIdentifier($node->valueVar->var);
if (!$this->hasVar($array) or $node->valueVar->dim === null) {
abort($node->valueVar);
@ -4057,10 +4057,6 @@ class CompilerBase extends \PhpAot\Core\Translator
}
return $id;
}
/*
* 对 static 的支持存在问题,静态编译时无法获得实际运行时的子类名,所以只能使用 self
* self 是在编译期确定的,而 static 是运行时确定的,但使用 AOT 编译为可执行文件后,运行时类的名称是无法确定的
*/
if ($id === 'self') {
$id = $this->getNamespacedClassName($this->class);
} elseif ($id === 'static') {

@ -184,7 +184,7 @@ class Translator extends Preprocessor
$path = $realpath;
if (is_dir($path)) {
$list = $this->getFilesFromDir($path);
$list = $this->getFilesFromDir($path);
$targetName = basename($path);
$this->setTargetName($targetName);
$this->sourceDirs[] = $path;
@ -193,7 +193,7 @@ class Translator extends Preprocessor
if ($ext === 'yml') {
$list = $this->parseProjectYaml($path);
} elseif ($ext === 'php') {
$list = [$path];
$list = [$path];
$targetName = FileScanner::getFileName($path);
$this->setTargetName($targetName);
$this->sourceDirs[] = dirname($path);
@ -275,8 +275,8 @@ class Translator extends Preprocessor
for ($i = 1; $i < count($argv); $i++) {
$arg = $argv[$i];
if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) {
$option = $matches[1];
$value = $matches[2];
$option = $matches[1];
$value = $matches[2];
$processed[] = "-{$option}";
$processed[] = $value;
} elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) {
@ -368,14 +368,14 @@ class Translator extends Preprocessor
$code .= 'uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . count($this->propMap) . '];' . PHP_EOL;
$code .= <<<'CODE'
zend_class_entry *php_get_class(int class_id, const php::String &class_name) {
zend_class_entry *php_get_class(int class_id, const php::Str &class_name) {
if (UNEXPECTED(php_class_map[class_id] == nullptr)) {
php_class_map[class_id] = php::getClassEntrySafe(class_name);
}
return php_class_map[class_id];
}
zend_function *php_get_func(int func_id, const php::String &func_name) {
zend_function *php_get_func(int func_id, const php::Str &func_name) {
if (UNEXPECTED(php_func_map[func_id] == nullptr)) {
php_func_map[func_id] = php::getFunction(func_name);
}
@ -631,7 +631,7 @@ CODE;
$this->genExternGlobalVars($this->getIncludeDir() . '/php_global_var_decl.h');
// 生成扩展模块的源文件
$sourceFiles[] =$this->genExtension();
$sourceFiles[] = $this->genExtension();
// embed 需要 main 函数,以及 cli 的内置函数定义
if ($this->getBuildMode() == 'bin') {
@ -751,7 +751,7 @@ CODE;
$code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '(';
$list = [];
if ($func->method) {
$list[] = 'php::Object &this_';
$list[] = self::TYPE_OBJECT . ' &this_';
}
$argInfoList = $func->argInfoList;
if ($argInfoList) {
@ -804,7 +804,7 @@ CODE;
public function genIncludeHeaderFiles(): string
{
$headers = array_merge($this->globalHeaders, $this->localHeaders);
$lines = [];
$lines = [];
foreach ($headers as $header) {
$lines[] = '#include <' . $header . '>';
}
@ -905,7 +905,7 @@ CODE;
protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array
{
$list = [];
$list = [];
$parentCe = $this->getParentClassCe($classDef);
if ($parentCe !== '') {
$list = [$parentCe];
@ -961,7 +961,7 @@ CODE;
protected function parseProjectYaml(string $path): array
{
$cfg = Yaml::parseFile($path);
$cfg = Yaml::parseFile($path);
$projectDir = dirname($path);
if (!empty($cfg['sources'])) {
@ -969,7 +969,7 @@ CODE;
if (!is_array($sources)) {
$this->error('`sources` must be array');
}
$list = [];
$list = [];
foreach ($sources as $src) {
$realPath = $this->getAbsolutePath($src, $projectDir);
if (!$realPath) {
@ -1049,7 +1049,7 @@ CODE;
{
$this->climate->info('convert: ' . $this->getRelativePath($this->file));
$ast = $this->parser->parse($phpCode);
$ast = $this->parser->parse($phpCode);
$traverser = new NodeTraverser();
$traverser->addVisitor(new Visitor());
@ -1126,8 +1126,8 @@ CODE;
foreach ($this->interfaces as $interfaceDef) {
$parent = $interfaceDef->extends;
$ce = $this->getClassCe($interfaceDef);
$deps = [];
$ce = $this->getClassCe($interfaceDef);
$deps = [];
if ($parent) {
// 不存在的接口,说明可能是内置接口
@ -1139,17 +1139,17 @@ CODE;
}
$this->classCeInfo[$ce] = [
'deps' => $deps,
'func' => $this->getRegisterClassFunction($interfaceDef->getNamespacedName()),
'args' => $this->getRegisterClassFunctionArgs($interfaceDef),
'deps' => $deps,
'func' => $this->getRegisterClassFunction($interfaceDef->getNamespacedName()),
'args' => $this->getRegisterClassFunctionArgs($interfaceDef),
'argDef' => $this->getRegisterClassFunctionArgDef($interfaceDef),
];
$sorter->add($ce, $deps);
}
foreach ($this->classes as $classDef) {
$ce = $this->getClassCe($classDef);
$deps = [];
$ce = $this->getClassCe($classDef);
$deps = [];
$parent = $classDef->extends;
if ($parent) {
// 不存在的父类,说明可能是内置类
@ -1173,9 +1173,9 @@ CODE;
$this->classCeInfo[$ce] = [
'classDef' => $classDef,
'deps' => $deps,
'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()),
'args' => $this->getRegisterClassFunctionArgs($classDef),
'deps' => $deps,
'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()),
'args' => $this->getRegisterClassFunctionArgs($classDef),
'argDef' => $this->getRegisterClassFunctionArgDef($classDef),
];
$sorter->add($ce, $deps);
@ -1191,7 +1191,7 @@ CODE;
protected function parseNamespace(Node\Stmt\Namespace_ $node): string
{
$ns = $node->name ? $this->parseIdentifier($node->name) : '';
$ns = $node->name ? $this->parseIdentifier($node->name) : '';
$code = '';
$this->resetNamespace();
@ -1200,7 +1200,7 @@ CODE;
$this->resetFunction();
$this->namespace = $ns;
$ns_end = '';
$ns_end = '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
@ -1422,7 +1422,7 @@ CODE;
protected function genNativeMethod(array $methodCodes): string
{
$code = '';
$code = '';
$classDef = $this->classDef;
foreach ($classDef->methods as $method) {
$code .= $methodCodes[$method->name] . PHP_EOL;
@ -1434,7 +1434,7 @@ CODE;
protected function genWrapperFunctionArgs(string $fn, FunctionDef $functionDef): string
{
$cppCode = '';
$cppCode = '';
$callParams = '';
foreach ($functionDef->argInfoList as $k => $argInfo) {
$var = 'arg_' . $argInfo->name;
@ -1499,9 +1499,9 @@ CODE;
protected function getClassRegisterCeFunc(ClassDef|InterfaceDef $classDef): string
{
$cppCode = '';
$name = $classDef->getNamespacedName();
$name = $classDef->getNamespacedName();
$argsDef = $this->getRegisterClassFunctionArgDef($classDef);
$param = $this->getRegisterClassFunctionArgs($classDef);
$param = $this->getRegisterClassFunctionArgs($classDef);
$cppCode .= 'zend_class_entry *' . $this->getRegisterClassFunction($name) . '(' . $argsDef . ') {' . PHP_EOL;
$cppCode .= $this->getIndent() . 'return register_class_' . $name . '(' . $param . ');' . PHP_EOL;
$cppCode .= '}' . PHP_EOL . PHP_EOL;
@ -1694,7 +1694,7 @@ CODE;
protected function parseForeachObject(Foreach_ $node): string
{
$obj = $this->parseIdentifier($node->expr);
$obj = $this->parseIdentifier($node->expr);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_OBJECT);
@ -1773,15 +1773,15 @@ CODE;
{
$code = 'class ' . $this->class . ' { ';
$publicMethods = [];
$protectedMethods = [];
$privateMethods = [];
$publicConstants = [];
$protectedConstants = [];
$privateConstants = [];
$publicProperties = [];
$publicMethods = [];
$protectedMethods = [];
$privateMethods = [];
$publicConstants = [];
$protectedConstants = [];
$privateConstants = [];
$publicProperties = [];
$protectedProperties = [];
$privateProperties = [];
$privateProperties = [];
foreach ($this->classDef->constants as $const) {
if ($const->flags & Modifiers::PUBLIC) {

@ -0,0 +1,24 @@
--TEST--
self class
--FILE--
<?php
class EvaluatedValue
{
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
public static function null(): EvaluatedValue
{
return new self('null');
}
}
function main() {
$obj = EvaluatedValue::null();
var_dump($obj->value);
}
?>
--EXPECT--
string(4) "null"
Loading…
Cancel
Save