From e62c953c71823bce2e6cb8b89c967db536773387 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 15 Apr 2026 20:20:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(compiler):=20=E5=AE=8C=E5=96=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=A3=B0=E6=98=8E=E5=92=8C=E5=AF=B9=E8=B1=A1=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 getNamespacedClassName 方法设为公共方法以便外部调用 - 添加对 self、parent、static 关键字的类型解析支持 - 优化 Name_FullyQualified 节点的解析逻辑 - 实现 detectClassOfExpr 方法用于检测表达式的类类型 - 增强对象赋值时的类型检查和错误提示 - 完善返回值类型的验证机制 - 重构类型检测相关方法的命名和实现 - 添加对 trait 语句的解析支持 - 优化构建模式设置功能 - 增加类型声明相关的测试用例 --- src/Php/CompilerBase.php | 200 +++++++++++++++++++++------------ src/Php/Entity/FunctionDef.php | 4 + src/Php/Preprocessor.php | 6 +- src/Php/Translator.php | 14 ++- src/gen_stub.php | 4 +- tests/aot/type_decl/006.phpt | 37 ++++++ 6 files changed, 183 insertions(+), 82 deletions(-) create mode 100644 tests/aot/type_decl/006.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 2eb46437..86eb3494 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -649,7 +649,7 @@ class CompilerBase extends \PhpAot\Core\Translator return strtolower($fullClassName . '::' . $method); } - protected function getNamespacedClassName(string $class): string + public function getNamespacedClassName(string $class): string { if ($class === '') { $this->error('Class name can not be empty'); @@ -773,6 +773,9 @@ class CompilerBase extends \PhpAot\Core\Translator return 'php_get_prop(' . $funcId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')'; } + /** + * @param string $class 一定是带有命名空间的完整类名 + */ protected function parseTypeDecl(?NodeAbstract $type, int $what, string &$class): string { // 未定义类型视为 var (mixed, any) @@ -790,7 +793,16 @@ class CompilerBase extends \PhpAot\Core\Translator } elseif (isset($this->zendTypeMap[$typeName])) { return $this->getTypeFromZendType($typeName); } else { - $class = $this->getNamespacedClassName($typeName); + if ($typeName === 'self') { + $class = $this->getFullClassName(); + } elseif ($typeName === 'parent') { + $class = $this->classDef->extends; + } elseif ($typeName === 'static') { + // static 类无法在编译期获取 + $class = ''; + } else { + $class = $this->getNamespacedClassName($typeName); + } return self::TYPE_OBJECT; } } @@ -950,8 +962,9 @@ class CompilerBase extends \PhpAot\Core\Translator switch ($type) { case self::EXPR_VARIABLE: return $this->parseVariable($expr); - case 'Name': case 'Name_FullyQualified': + return '\\' . $expr->name; + case 'Name': case 'VarLikeIdentifier': case 'Identifier': return $expr->name; @@ -1252,74 +1265,71 @@ class CompilerBase extends \PhpAot\Core\Translator } $expr = $this->parseExpr($right); - $type = $this->detectExprType($right); + $type = $this->detectTypeOfExpr($right); if ($this->isVarExpr($left)) { - // 类型推断,获取对象的类名 - if ($this->isNewExpr($right) and $this->isNameExpr($right->class)) { - $class = $this->parseIdentifier($right->class); - $fullClass = $this->getNamespacedClassName($class); - if ($this->isTypedObject($var)) { + // 类型推断,获取对象的类名,如果不是对象则返回空字符串 + $rightClass = $this->detectClassOfExpr($right); + // 右值是一个对象,已获得类的名称,左值必须与右值的类一致 + if ($rightClass) { + if (!$this->hasVar($var)) { + $this->addLocalVar($var, self::TYPE_OBJECT); + $this->addObject($var, $rightClass); + } elseif ($this->isTypedObject($var)) { $leftClass = $this->getObjectType($var); - if ($leftClass !== $fullClass) { - $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$fullClass}`"); + // 对象的类不一致,不能互相赋值 + if ($leftClass !== $rightClass) { + $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); } - } - $this->addObject($var, $fullClass); - $type = self::TYPE_OBJECT; - } elseif ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) { - $fn = $this->parseIdentifier($right->name); - if (count($right->args) === 2 and $fn === 'objval' and $this->isScalarString($right->args[1]->value)) { - $fullClass = $this->getNamespacedClassName($this->parseIdentifier($right->args[1]->value)); - if ($this->isTypedObject($var)) { - $leftClass = $this->getObjectType($var); - $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$fullClass}`"); - } - $this->addObject($var, $fullClass); - $type = self::TYPE_OBJECT; - } elseif (count($right->args) === 1 and $fn === 'any') { - $type = self::TYPE_VAR; - if (!$this->hasVar($var)) { - $this->addLocalVar($var, $type); - } - return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); } else { - $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; + $this->fatalError($left, "Cannot re-assign `\${$var}` to object of `{$rightClass}`"); } - } elseif ($this->isStaticCall($right) and $this->isIdExpr($right->name)) { - $class = $this->parseIdentifier($right->class); - if ($class === 'std') { - $func = $this->parseIdentifier($right->name); - $type = match ($func) { - 'int' => self::TYPE_INT, - 'float' => self::TYPE_FLOAT, - 'bool' => self::TYPE_BOOL, - default => '', - }; - // Native 类型 - if ($type) { + } else { + if ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) { + $fn = $this->parseIdentifier($right->name); + if (count($right->args) === 1 and $fn === 'any') { + $type = self::TYPE_VAR; if (!$this->hasVar($var)) { $this->addLocalVar($var, $type); } - $expr = $this->parseExpr($right->args[0]->value); - return $var . ' = ' . $this->convertExprFromType($type, $expr); + return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); + } else { + $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; + } + } elseif ($this->isStaticCall($right) and $this->isIdExpr($right->name)) { + $class = $this->parseIdentifier($right->class); + if ($class === 'std') { + $func = $this->parseIdentifier($right->name); + $type = match ($func) { + 'int' => self::TYPE_INT, + 'float' => self::TYPE_FLOAT, + 'bool' => self::TYPE_BOOL, + default => '', + }; + // Native 类型 + if ($type) { + if (!$this->hasVar($var)) { + $this->addLocalVar($var, $type); + } + $expr = $this->parseExpr($right->args[0]->value); + return $var . ' = ' . $this->convertExprFromType($type, $expr); + } + } + } elseif ($this->isVarExpr($right)) { + $rightVar = $this->parseIdentifier($right); + $type = $this->getVarType($rightVar); + if ($this->isTypedObject($rightVar) and $this->isTypedObject($var)) { + $leftClass = $this->getObjectType($var); + $rightClass = $this->getObjectType($rightVar); + $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); } } - } elseif ($this->isVarExpr($right)) { - $rightVar = $this->parseIdentifier($right); - $type = $this->getVarType($rightVar); - if ($this->isTypedObject($rightVar) and $this->isTypedObject($var)) { - $leftClass = $this->getObjectType($var); - $rightClass = $this->getObjectType($rightVar); - $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); + if (!$this->hasVar($var)) { + $this->addLocalVar($var, $type); + } elseif ($this->getVarType($var) !== self::TYPE_VAR and $this->isTypedObject($var) and $type !== self::TYPE_OBJECT) { + $this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type); } } - - if (!$this->hasVar($var)) { - $this->addLocalVar($var, $type); - } elseif ($this->getVarType($var) !== self::TYPE_VAR and $this->isTypedObject($var) and $type !== self::TYPE_OBJECT) { - $this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type); - } } elseif ($this->isPropertyFetch($left) and !$left->getAttribute('nativeProperty')) { return $this->parseAssignPropertyFetch($left, $right); } elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) { @@ -1330,7 +1340,7 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseAssignArrayDim($left, $right); } - return $var . ' = ' . $this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right)); + return $var . ' = ' . $this->convertExprType($expr, $this->detectTypeOfExpr($left), $this->detectTypeOfExpr($right)); } protected function parseEcho(mixed $v): string @@ -1431,8 +1441,8 @@ class CompilerBase extends \PhpAot\Core\Translator $this->checkVarMustExist($left, $leftExpr); $this->checkVarMustExist($right, $rightExpr); - $leftType = $this->detectExprType($left); - $rightType = $this->detectExprType($right); + $leftType = $this->detectTypeOfExpr($left); + $rightType = $this->detectTypeOfExpr($right); if ($leftType === self::TYPE_FLOAT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType); @@ -1456,6 +1466,38 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseBinaryOp($expr->left, $expr->right, '+'); } + protected function detectClassOfExpr(NodeAbstract $expr): string + { + if ($this->isNewExpr($expr) and $this->isNameExpr($expr->class)) { + $class = $this->parseIdentifier($expr->class); + if ($class === 'self') { + return $this->getFullClassName(); + } + if ($class === 'static') { + // 无法在编译期获得 static 类的准确类名 + return ''; + } else { + return $this->getNamespacedClassName($class); + } + } + if ($this->isVarExpr($expr)) { + $object = $this->parseVariable($expr); + if ($object === 'this_') { + return $this->getFullClassName(); + } + if ($this->isTypedObject($object)) { + return $this->getObjectType($object); + } + } + if ($this->isFuncCallExpr($expr) and $this->isNameExpr($expr->name)) { + $fn = $this->parseIdentifier($expr->name); + if (count($expr->args) === 2 and $fn === 'objval' and $this->isScalarString($expr->args[1]->value)) { + return $this->getNamespacedClassName($this->parseIdentifier($expr->args[1]->value)); + } + } + return ''; + } + protected function parseReturn(Node\Stmt\Return_ $v): string { if ($v->expr === null) { @@ -1466,7 +1508,7 @@ class CompilerBase extends \PhpAot\Core\Translator } } // 实际函数的返回值 - $type = $this->detectExprType($v->expr); + $type = $this->detectTypeOfExpr($v->expr); $expr = $this->parseExpr($v->expr); $returnType = $this->getReturnType(); @@ -1479,6 +1521,17 @@ class CompilerBase extends \PhpAot\Core\Translator $returnType = self::TYPE_VAR; } + // 返回值的表达式是一个类的对象 + $objectClass = $this->detectClassOfExpr($v->expr); + $returnClass = $this->getReturnClass(); + if ($returnClass) { + if (!$objectClass) { + $this->fatalError($v, 'The return value must be an instance of the `' . $returnClass . '` class'); + } elseif (!$this->isInheritedFrom($objectClass, $returnClass)) { + $this->fatalError($v, 'The return type is `' . $returnClass . '`, cannot return an instance of `' . $objectClass . '`'); + } + } + $exprCode = $this->convertExprType($expr, $returnType, $type); // return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针 // 将 Indirect 赋值给临时变量后,使用 Ctor::Copy 解除了 Indirect,保证内存安全 @@ -1721,7 +1774,7 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->getVarType($name); } - protected function detectExprType($expr): string + protected function detectTypeOfExpr($expr): string { $exprType = $expr->getType(); switch ($exprType) { @@ -1750,8 +1803,8 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': case 'Expr_BinaryOp_BooleanAnd': - $leftType = $this->detectExprType($expr->left); - $rightType = $this->detectExprType($expr->right); + $leftType = $this->detectTypeOfExpr($expr->left); + $rightType = $this->detectTypeOfExpr($expr->right); if ($leftType === self::TYPE_FLOAT || $rightType === self::TYPE_FLOAT) { return self::TYPE_FLOAT; } @@ -2043,7 +2096,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($node->var, 'Cannot assign to undefined variable'); } $type = $this->detectVarType($node->var); - $rightExprStr = $this->convertExprType($expr, $type, $this->detectExprType($node->expr)); + $rightExprStr = $this->convertExprType($expr, $type, $this->detectTypeOfExpr($node->expr)); if ($this->isAssignOpConcat($op)) { if ($this->isArrayVar($node->var)) { $this->fatalError($node->var, 'Cannot concat string to array'); @@ -2065,7 +2118,7 @@ class CompilerBase extends \PhpAot\Core\Translator * $count[$r] = $tmp_var;. */ $type = $this->detectVarType($node->var); - $rightType = $this->detectExprType($node->expr); + $rightType = $this->detectTypeOfExpr($node->expr); $tmpVar = $this->genTmpVarName(); $this->addLocalVar($tmpVar, $rightType); $dim = $this->parseIdentifier($node->var->dim); @@ -3148,6 +3201,11 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->functionDef->returnType; } + protected function getReturnClass(): string + { + return $this->functionDef->returnClass; + } + protected function isInheritedFrom(string $class, string $expected): bool { $internal = ($this->isInternalClass($expected) or $this->isInternalInterface($expected)); @@ -3175,7 +3233,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getTypeConvertedArg(Node\Arg $arg, ArgInfo $argInfo): string { $expr = $this->parseArg($arg); - $type = $this->detectExprType($arg->value); + $type = $this->detectTypeOfExpr($arg->value); if ($argInfo->byRef) { return $this->convertToRef($arg->value); @@ -3425,7 +3483,7 @@ class CompilerBase extends \PhpAot\Core\Translator { $cond = $v->cond; $tmp_var = $this->genTmpVarName(); - $type = $this->detectExprType($cond); + $type = $this->detectTypeOfExpr($cond); if ($this->isVarExpr($cond)) { $this->requireVar($v, $this->parseIdentifier($cond)); } @@ -3525,7 +3583,7 @@ class CompilerBase extends \PhpAot\Core\Translator $list = []; foreach ($v->vars as $var) { $varName = $this->escapeVarName($var->var->name); - $type = $var->default ? $this->detectExprType($var->default) : self::TYPE_VAR; + $type = $var->default ? $this->detectTypeOfExpr($var->default) : self::TYPE_VAR; $globalVar = $this->addStaticVar($var->var, $varName, $type); $list[] = self::TYPE_VAR . ' &' . $varName . ' = ' . $this->escapeGlobalVar($globalVar) . ';'; @@ -4667,7 +4725,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->errorUndefinedVariable($expr->expr); } if ($this->isVarExpr($expr->var) and !$this->hasVar($var)) { - $this->addLocalVar($var, $this->detectExprType($expr->expr)); + $this->addLocalVar($var, $this->detectTypeOfExpr($expr->expr)); } return '(' . $isset . '?' . $var . ':(' . $var . ' = ' . $right . '))'; } diff --git a/src/Php/Entity/FunctionDef.php b/src/Php/Entity/FunctionDef.php index 139944f3..1665ccfb 100644 --- a/src/Php/Entity/FunctionDef.php +++ b/src/Php/Entity/FunctionDef.php @@ -24,6 +24,10 @@ class FunctionDef public string $namespace; public bool $method = false; public bool $stub = false; + + /** + * @var string 必须是带有命名空间的完整类名 + */ public string $returnClass = ''; public function __construct(string $name, string $returnType, string $namespace) diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index e21fa3fa..bfd2911b 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -376,11 +376,7 @@ class Preprocessor extends CompilerBase protected function getParentClass(NodeAbstract $extends): string { - if ($extends instanceof Node\Name\FullyQualified) { - return $this->parseIdentifier($extends); - } else { - return $this->getNamespacedClassName($this->parseIdentifier($extends)); - } + return $this->getNamespacedClassName($this->parseIdentifier($extends)); } protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 9c1f9944..b13d2de6 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -128,6 +128,11 @@ class Translator extends Preprocessor return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); } + public function setBuildMode(string $mode): void + { + $this->buildMode = $mode; + } + public function setTargetName(string $name): void { if ($this->climate->arguments->defined('output')) { @@ -688,6 +693,9 @@ class Translator extends Preprocessor if (!empty($cfg['name'])) { $this->setTargetName($cfg['name']); } + if (!empty($cfg['type'])) { + $this->setBuildMode($cfg['type']); + } return $list; } @@ -870,6 +878,7 @@ class Translator extends Preprocessor $type2 = $v2->getType(); switch ($type2) { case 'Stmt_Class': + case 'Stmt_Trait': $code .= $this->parseClass($v2); break; case 'Stmt_Const': @@ -884,9 +893,6 @@ class Translator extends Preprocessor case 'Stmt_Interface': $code .= $this->parseInterface($v2) . PHP_EOL; break; - case 'Stmt_Trait': - $code .= $this->parseTrait($v2) . PHP_EOL; - break; default: abort($v2); } @@ -902,9 +908,9 @@ class Translator extends Preprocessor $stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file); $headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); + $this->climate->info('generate stub file: ' . $this->getRelativePath($file)); generateStubFile($file, $this->getIncludeDir() . '/' . $headerFile, true); - $this->climate->info('generate stub file: ' . $this->getRelativePath($file)); if ($this->useRegisterSymbolsFn) { preg_match('/php_(.*)_arginfo.h/', $headerFile, $matches); $registerSymbolFn = 'register_' . $matches[1] . '_symbols'; diff --git a/src/gen_stub.php b/src/gen_stub.php index 866ad9a6..e630193e 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -228,8 +228,8 @@ class SimpleType { return new SimpleType(ClassInfo::$currentClassName, false); } - assert($node->isFullyQualified()); - return new SimpleType($node->toString(), false); + $class = $node->isFullyQualified() ? $node->toString() : getTranslator()->getNamespacedClassName($node->toString()); + return new SimpleType($class, false); } if ($node instanceof Node\Identifier) { diff --git a/tests/aot/type_decl/006.phpt b/tests/aot/type_decl/006.phpt new file mode 100644 index 00000000..478573a8 --- /dev/null +++ b/tests/aot/type_decl/006.phpt @@ -0,0 +1,37 @@ +--TEST-- +Type Declarations +--FILE-- +name = $name; + } + } +} + +namespace TestApp\Command { + use TestApp\Console\Command; + trait CommandCallable { + public function callCommand(string $class): Command + { + return new Command($class); + } + } + + class Test { + use CommandCallable; + } +} +namespace { + function main() { + $o = new TestApp\Command\Test; + $o2 = $o->callCommand('TestApp\Console\Command'); + var_dump($o2->name); + } +} +?> +--EXPECT-- +string(23) "TestApp\Console\Command" \ No newline at end of file