From 7871eb2820823081525caa1d4cabbd3ec48e65fb Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 4 Mar 2026 12:04:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(compiler):=20=E6=B7=BB=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E9=87=8D=E5=86=99=E6=94=AF=E6=8C=81=E5=92=8C?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E7=BB=91=E5=AE=9A=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 parentClass 属性用于存储父类名称 - 实现 classMethodOverride 和 classExtends 数组用于检测方法重写 - 添加方法重写预处理逻辑,支持子类和父类同名方法的动态绑定 - 修复静态方法调用时的对象指针传递问题 - 增强方法查找机制,支持向上递归查找父类方法 - 添加测试用例验证方法重写功能的正确性 - 改进错误提示信息,提供更准确的错误定位 --- src/Php/CompilerBase.php | 69 +++++++++++++++++++++++----- src/Php/Preprocessor.php | 27 ++++++++++- tests/aot/class-method-override.phpt | 44 ++++++++++++++++++ 3 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 tests/aot/class-method-override.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index d55d7ca2..6bf4aaf0 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -147,6 +147,7 @@ class CompilerBase extends \PhpAot\Core\Translator * 原始类名,不包含命名空间. */ protected string $class = ''; + protected string $parentClass = ''; protected string $interface = ''; /** @@ -225,6 +226,15 @@ class CompilerBase extends \PhpAot\Core\Translator protected Parser $parser; protected PrettyPrinter $printer; + /** + * 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题 + * `static::methodCall()` + * `$this->methodCall()` 子类和父类中存在同名方法 + * @var array + */ + protected array $classMethodOverride = []; + protected array $classExtends = []; + public function __construct(string $rootPath) { $this->rootPath = $rootPath; @@ -605,6 +615,9 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getNamespacedClassName(string $class): string { + if ($class === '') { + $this->error('Class name can not be empty'); + } if ($class[0] === '\\') { return ltrim($class, '\\'); } @@ -2025,7 +2038,8 @@ class CompilerBase extends \PhpAot\Core\Translator /** * 查找原生函数. * - * @return bool + * @param string $fname + * @return string|false */ protected function findNativeFunction(string $fname): string|false { @@ -2105,7 +2119,7 @@ class CompilerBase extends \PhpAot\Core\Translator } $placeHolder = $this->identifierToStr($expr->name); $fn = $this->getFuncPtr($name); - $this->beforeStmtLines[] = '// Func Call: ' . $name; + $this->beforeStmtLines[] = '// Func Call: ' . $name . '()'; $call = $silent ? 'CALL_SILENT' : 'CALL'; } else { $tmpVar = $this->genTmpVarName(); @@ -3332,9 +3346,14 @@ class CompilerBase extends \PhpAot\Core\Translator } $method = $this->identifierToStr($expr->name); - $nativeFunc = $this->findNativeMethod($expr, $object, $method); - if ($nativeFunc) { - return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); + + // 可转为原生调用的 MethodCall + if ($this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { + $this->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()'; + $nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name)); + if ($nativeFunc) { + return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); + } } if ($this->isNamedMethod($expr->name)) { @@ -3391,6 +3410,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseStaticCall(Node\Expr\StaticCall $expr): string { $placeHolder = ''; + $self = false; if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { $var = $this->parseIdentifier($expr->class); if ($this->isTypedObject($var)) { @@ -3407,6 +3427,7 @@ class CompilerBase extends \PhpAot\Core\Translator $class = $this->parseIdentifier($expr->class); if ($class === 'self') { $class = $this->class; + $self = true; } elseif ($class === 'parent') { return $this->parseParentMethodCall($expr); } @@ -3414,7 +3435,7 @@ class CompilerBase extends \PhpAot\Core\Translator _do_call: $method = $this->parseIdentifier($expr->name); - $this->beforeStmtLines[] = '// Static Call: ' . $class . '::' . $method; + $this->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()'; if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { $nativeFunc = $this->getNativeStaticMethod($class, $method); @@ -3424,10 +3445,15 @@ class CompilerBase extends \PhpAot\Core\Translator } catch (PlaceHolder) { return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)])); } + $object = 'php::null_object'; + // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 + if ($this->methodDef and $self) { + $object = 'this_'; + } if ($args) { - return self::PREFIX . $nativeFunc . '(php::null_object, ' . $args . ')'; + return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')'; } else { - return self::PREFIX . $nativeFunc . '(php::null_object)'; + return self::PREFIX . $nativeFunc . '(' . $object . ')'; } } } @@ -3814,18 +3840,31 @@ class CompilerBase extends \PhpAot\Core\Translator protected function findNativeMethod(Node\Expr\MethodCall $expr, string $object, string $method): string|false { $nativeFunc = ''; + $classDef = null; if ($object === 'this_') { $nativeFunc = $this->getNativeName($method, $this->namespace, $this->class); + $classDef = $this->classDef; } elseif (isset($this->objects[$object])) { $class = $this->objects[$object]; if (!$this->hasNativeClass($class)) { return false; } + $classDef = $this->getClassDef($class); - if (!$classDef->hasMethod($method)) { - return false; + $methodDef = null; + // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 + while ($classDef) { + if (!$classDef->hasMethod($method)) { + if (!$classDef->extends) { + return false; + } + $classDef = $this->getClassDef($classDef->extends); + } else { + $methodDef = $classDef->methods[$method]; + break; + } } - $methodDef = $classDef->methods[$method]; + if (!$this->checkAccessible($classDef, $methodDef)) { $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); } @@ -3836,6 +3875,11 @@ class CompilerBase extends \PhpAot\Core\Translator } $nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name); } + $fullMethodName = $classDef->getNamespacedName(false) . '::' . $method; + // 存在子类同名方法,需要转为动态调用 + if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) { + return false; + } if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) { return $nativeFunc; } @@ -3866,6 +3910,9 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseParentMethodCall(Node\Expr\StaticCall $expr): string { + if (!$this->classDef->extends) { + $this->fatalError($expr, 'Cannot call parent method `' . $this->classDef->name . '::' . $this->parseIdentifier($expr->name) . '()` because class `' . $this->classDef->name . '` does not extend any class'); + } $method = $this->identifierToStr($expr->name); if (empty($expr->args)) { return 'this_.callParentMethod(' . $method . ')'; diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index b486ec03..0a57dd6e 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -170,6 +170,11 @@ class Preprocessor extends CompilerBase protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string { $this->class = $this->parseIdentifier($class->name); + if ($class->extends) { + $this->parentClass = $this->getNamespacedClassName($this->parseIdentifier($class->extends)); + $fullClassName = $this->getNamespacedClassName($this->class); + $this->classExtends[$fullClassName] = $this->parentClass; + } $code = ''; foreach ($class->stmts as $v) { $type = $v->getType(); @@ -181,7 +186,7 @@ class Preprocessor extends CompilerBase case 'Stmt_EnumCase': break; case 'Stmt_ClassMethod': - $code .= $this->prepareFunction($v) . PHP_EOL; + $this->prepareMethod($v); break; case 'Stmt_Expression': $this->foundStrayCode($v); @@ -191,7 +196,27 @@ class Preprocessor extends CompilerBase } } $this->class = ''; + $this->parentClass = ''; return $code; } + + protected function prepareMethod(Node\Stmt\ClassMethod $v): void + { + $this->prepareFunction($v) . PHP_EOL; + if ($this->isIdExpr($v->name) or $this->isNameExpr($v->name)) { + $fullClassName = $this->getNamespacedClassName($this->class); + $fullName = $fullClassName . '::' . $v->name; + $this->classMethodOverride[$fullName] = false; + // 查找父类是否有同名方法,递归查找 + while (isset($this->classExtends[$fullClassName])) { + $parentClass = $this->classExtends[$fullClassName]; + $parentMethod = $parentClass . '::' . $v->name; + if (isset($this->classMethodOverride[$parentMethod])) { + $this->classMethodOverride[$parentMethod] = true; + } + $fullClassName = $parentClass; + } + } + } } diff --git a/tests/aot/class-method-override.phpt b/tests/aot/class-method-override.phpt new file mode 100644 index 00000000..d7904089 --- /dev/null +++ b/tests/aot/class-method-override.phpt @@ -0,0 +1,44 @@ +--TEST-- +class method override +--FILE-- +id); + return "hello"; + } + + function foo() + { + var_dump($this->hello()); + var_dump(self::hello()); + } + } + + class Worker2 extends Worker1 + { + function hello() + { + return "world"; + } + } +} + +namespace { + use Test\Worker2; + function main() + { + $obj = new Worker2(); + $obj->hello(); + $obj->foo(); + } +} +?> +--EXPECT-- +string(5) "world" +string(3) "foo" +string(5) "hello"