feat(php): 添加对象类型推断和方法调用优化功能

- 新增 isNewExpr、isNameExpr、isScalarString、isFuncCallExpr 等 AST 节点类型判断方法
- 移除 PROPERTY_OFFSETS 常量并优化变量声明结构
- 添加 objects 属性用于存储变量对应的对象类型信息
- 实现对象创建和函数调用时的类型推断逻辑
- 扩展 convertObjectExpr 方法支持指定类名参数
- 重构原生方法调用检查逻辑,实现更精确的方法查找机制
- 添加访问权限检查功能确保私有方法不被外部调用
- 优化 FuncCallOptimizer 支持 strval 和 objval 函数转换
- 添加示例文件展示对象操作功能
pull/1/head
韩天峰 7 months ago
parent 0f3ccb98cd
commit f8be1767e6
  1. 27
      examples/obj.php
  2. 20
      src/Php/AstNodeType.php
  3. 84
      src/Php/CompilerBase.php
  4. 11
      src/Php/FuncCallOptimizer.php

@ -0,0 +1,27 @@
<?php
class Test
{
public int $a;
public int $b;
public function test()
{
return $this->a + $this->b;
}
}
function main()
{
$obj = new Test();
$obj->a = 1;
$obj->b = 2;
$arr = array();
$arr['obj'] = $obj;
var_dump($obj->test());
$obj2 = objval($arr['obj'], 'Test');
var_dump($obj2);
var_dump($obj2->test());
}

@ -27,4 +27,24 @@ trait AstNodeType
{
return $expr instanceof Expr\StaticPropertyFetch;
}
protected function isNewExpr(NodeAbstract $expr): bool
{
return $expr instanceof Expr\New_;
}
protected function isNameExpr(NodeAbstract $expr): bool
{
return $expr instanceof Node\Name;
}
protected function isScalarString(NodeAbstract $expr): bool
{
return $expr instanceof Node\Scalar\String_;
}
protected function isFuncCallExpr(NodeAbstract $expr): bool
{
return $expr instanceof Expr\FuncCall;
}
}

@ -39,7 +39,6 @@ class CompilerBase extends \PhpAot\Core\Translator
public const string VALUE_NAN = 'std::numeric_limits<double>::quiet_NaN()';
public const string VALUE_INF = 'std::numeric_limits<double>::infinity()';
public const string LITERAL_STRINGS = '_literal_strings';
public const string PROPERTY_OFFSETS = '_property_offsets';
public const string EXPR_VARIABLE = 'Expr_Variable';
public const string EXPR_NEW = 'Expr_New';
public const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch';
@ -84,11 +83,19 @@ class CompilerBase extends \PhpAot\Core\Translator
protected bool $useCppNamespace = false;
protected string $file;
protected string $dir;
/**
* 原始值,可能包含 `\\` 多层空间
* @var string
*/
protected string $namespace = '';
protected string $method = '';
protected string $function = '';
protected array $useNamespaces = [];
protected array $useFunctions = [];
/**
* 原始类名,不包含命名空间
* @var string
*/
protected string $class = '';
protected string $interface = '';
/**
@ -124,6 +131,10 @@ class CompilerBase extends \PhpAot\Core\Translator
'argc' => self::TYPE_INT,
'argv' => self::TYPE_ARRAY,
];
/**
* @var array<string, string>
*/
protected array $objects = [];
protected array $localVars = [];
protected array $varTypes = [];
protected array $objectWrappers = [];
@ -823,11 +834,28 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($var === 'this_') {
$this->fatalError($left, 'Cannot re-assign $this');
}
$expr = $this->parseExpr($right);
$type = $this->detectExprType($right);
if ($this->isVarExpr($left)) {
// 类型推断,获取对象的类名
if ($this->isNewExpr($right) and $this->isNameExpr($right->class)) {
$class = $this->parseIdentifier($right->class);
$this->objects[$var] = $class;
$type = self::TYPE_OBJECT;
} elseif ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) {
$fn = $this->parseIdentifier($right->name);
$arg2 = $right->args[1]->value;
if (count($right->args) === 2 and $fn === 'objval' and $this->isScalarString($arg2)) {
$this->objects[$var] = $arg2->value;
}
$type = self::TYPE_OBJECT;
}
if (!$this->hasVar($var)) {
$type = $this->detectExprType($right);
$this->addLocalVar($var, $type);
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type);
}
}
return $var . ' = ' . $this->convertExprType($expr, $this->detectExprType($left), $this->detectExprType($right));
}
@ -1730,10 +1758,14 @@ class CompilerBase extends \PhpAot\Core\Translator
return $expr;
}
protected function convertObjectExpr(string $expr): string
protected function convertObjectExpr(string $expr, string $class = ''): string
{
if (!$this->isClosedCall($expr, 'php::to_object')) {
return 'php::to_object(' . $this->trimBrackets($expr) . ')';
if ($class === '') {
return 'php::to_object(' . $this->trimBrackets($expr) . ')';
} else {
return 'php::to_object(' . $this->trimBrackets($expr) . ', ' . $class . ')';
}
}
return $expr;
}
@ -2385,8 +2417,8 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$object = $this->convertToObject($expr->var);
$method = $this->parseIdentifier($expr->name);
$nativeFunc = $this->getNativeName($method, $this->namespace, $this->class);
if ($this->isNativeMethod($object, $nativeFunc)) {
$nativeFunc = $this->findNativeMethod($expr, $object, $method);
if ($nativeFunc) {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
}
if (empty($expr->args)) {
@ -2715,17 +2747,45 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'continue;';
}
protected function isNativeMethod(string $object, string $nativeFunc): bool
protected function checkAccessible(ClassDef $classDef, MethodDef $methodDef): bool
{
// 在当前类中,允许调用所有方法
if ($classDef->namespace === $this->namespace and $classDef->name == $this->class) {
return true;
}
// 类外部调用,只允许调用 public 方法
return $methodDef->flags & Modifiers::PUBLIC;
}
protected function findNativeMethod(NodeAbstract $expr, string $object, string $method): string|false
{
$nativeFunc = '';
if ($object === 'this_') {
return $this->isNativeFunction($nativeFunc);
$nativeFunc = $this->getNativeName($method, $this->namespace, $this->class);
} elseif (isset($this->objects[$object])) {
$class = $this->objects[$object];
if (!isset($this->classes[$class])) {
return false;
}
$classDef = $this->classes[$class];
if (!isset($classDef->methods[$method])) {
return false;
}
$methodDef = $classDef->methods[$method];
if (!$this->checkAccessible($classDef, $methodDef)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible');
}
$nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name);
}
if ($nativeFunc and $this->isNativeFunction($nativeFunc)) {
return $nativeFunc;
} else {
return false;
}
return false;
}
protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string
{
if (count($args) === 0) {
return self::PREFIX . $nativeFunc . '(' . $object . ')';
} else {

@ -19,6 +19,17 @@ trait FuncCallOptimizer
return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value));
case 'boolval':
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value));
case 'strval':
return $this->convertStringExpr($this->parseExpr($expr->args[0]->value));
default:
break;
}
} elseif (count($expr->args) == 2) {
switch ($name) {
case 'objval':
$arg1 = $expr->args[0]->value;
$arg2 = $expr->args[1]->value;
return $this->convertObjectExpr($this->parseExpr($arg1), $this->parseExpr($arg2));
default:
break;
}

Loading…
Cancel
Save