From d0d66a098251b5e72edf8dbb7b4550341278ac3c Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 4 Mar 2026 20:39:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(compiler):=20=E5=AE=9E=E7=8E=B0=E5=AF=B9?= =?UTF-8?q?=20static=20=E5=85=B3=E9=94=AE=E5=AD=97=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 getNativeStaticMethod 方法为 getNativeMethod,增加继承链方法查找逻辑 - 添加对 static::class 和 self::class 的差异化处理 - 实现静态编译时对 static 调用的正确解析和转换 - 增加对 late static binding 的支持 - 添加相关辅助函数 php_get_called_class 和 php_get_called_ce - 更新测试用例验证 static 关键字功能正确性 --- examples/callstack.php | 39 ++++++++++++++ src/Php/CompilerBase.php | 96 +++++++++++++++++++-------------- src/cpp/main.cc | 20 ++++++- src/cpp/php_aot_helper.h | 4 ++ src/template/extension.cc.php | 2 +- tests/aot/class-static-002.phpt | 40 ++++++++++++++ 6 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 examples/callstack.php create mode 100644 tests/aot/class-static-002.phpt diff --git a/examples/callstack.php b/examples/callstack.php new file mode 100644 index 00000000..966e7108 --- /dev/null +++ b/examples/callstack.php @@ -0,0 +1,39 @@ +escapeFunction($name), $this->nativeFunctions); } - protected function getNativeStaticMethod(string $class, string $method): string|false + protected function getNativeMethod(Node\Expr\MethodCall|Node\Expr\StaticCall $expr, string $class, string $method): string|false { if (!$this->hasNativeClass($class)) { return false; } - $class = $this->getClassDef($class); - if (!$class->hasMethod($method)) { - return false; + + $classDef = $this->getClassDef($class); + $methodDef = null; + // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 + while ($classDef) { + if (!$classDef->hasMethod($method)) { + if (!$classDef->extends) { + return false; + } + $classDef = $this->getClassDef($classDef->extends); + } else { + $methodDef = $classDef->methods[$method]; + break; + } + } + + if (!$this->checkAccessible($classDef, $methodDef)) { + $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); + } + if (count($expr->args) < $methodDef->functionDef->argCountRequired) { + $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given'); + } elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) { + $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` accepts ' . count($methodDef->functionDef->argInfoList) . ' arguments, ' . count($expr->args) . ' given'); } - return $this->getNativeName($method, $class->namespace, $class->name); + return $this->getNativeName($method, $classDef->namespace, $classDef->name); } protected function getClassDef(string $name): ClassDef @@ -3391,8 +3411,10 @@ class CompilerBase extends \PhpAot\Core\Translator * 对 static 的支持存在问题,静态编译时无法获得实际运行时的子类名,所以只能使用 self * self 是在编译期确定的,而 static 是运行时确定的,但使用 AOT 编译为可执行文件后,运行时类的名称是无法确定的 */ - if ($id === 'self' or $id === 'static') { + if ($id === 'self') { $id = $this->getNamespacedClassName($this->class); + } elseif ($id === 'static') { + return 'php_get_called_class(this_)'; } if ($this->isNameExpr($node) or $this->isIdExpr($node)) { return $literal ? $this->getLiteralString($id) : $this->genCharPtr($id, true); @@ -3409,8 +3431,8 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseStaticCall(Node\Expr\StaticCall $expr): string { - $placeHolder = ''; $self = false; + $callScope = []; if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { $var = $this->parseIdentifier($expr->class); if ($this->isTypedObject($var)) { @@ -3438,17 +3460,24 @@ class CompilerBase extends \PhpAot\Core\Translator $this->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()'; if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { - $nativeFunc = $this->getNativeStaticMethod($class, $method); + $callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)]; + } + + if ($callScope) { + $nativeFunc = $this->getNativeMethod($expr, $class, $method); if ($nativeFunc) { try { $args = $this->parseNativeCallArgs($expr->args, $nativeFunc); } catch (PlaceHolder) { - return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)])); + return $this->genPlaceHolder($this->genArray($callScope)); } - $object = 'php::null_object'; // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 if ($this->methodDef and $self) { $object = 'this_'; + } else { + $object = $this->genTmpVarName(); + $this->addLocalVar($object, self::TYPE_OBJECT); + $this->beforeStmtLines[] = 'Z_PTR_P('.$object.'.ptr()) = ' . $this->getClassEntryPtr($class) . ';'; } if ($args) { return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')'; @@ -3459,14 +3488,15 @@ class CompilerBase extends \PhpAot\Core\Translator } $ce = $this->getClassEntryPtr($class); $fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method, false); - $placeHolder = $this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)]); + $placeHolder = $this->genArray($callScope); } $call = 'php::call'; if (empty($expr->args)) { return $call . '(' . $fn . ')'; } try { - return $call . '(' . $fn . ', ' . $this->parseCallArgs($expr->args) . ')'; + $callArgs = $this->parseCallArgs($expr->args); + return $call . '(' . $fn . ', ' . $callArgs . ')'; } catch (PlaceHolder) { return $this->genPlaceHolder($placeHolder); } @@ -3561,12 +3591,23 @@ class CompilerBase extends \PhpAot\Core\Translator { $class = $this->parseIdentifier($expr->class); $self = false; - if ($class === 'self' or $class === 'this_' or $class === 'static') { + if ($class === 'self' or $class === 'this_') { $self = true; $class = $this->class; } $const = $this->escapeString($this->parseIdentifier($expr->name)); + if ($class === 'static') { + if (!$this->methodDef) { + $this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods"); + } + if ($const === 'class') { + return 'php_get_called_class(this_)'; + } else { + return 'php::constant(php_get_called_ce(), ' . $this->getLiteralString($const) . ')'; + } + } + $class = $this->getNamespacedClassName($class); if ($const === 'class') { return '"' . $this->escapeString($class) . '"'; @@ -3846,34 +3887,7 @@ class CompilerBase extends \PhpAot\Core\Translator $classDef = $this->classDef; } elseif (isset($this->objects[$object])) { $class = $this->objects[$object]; - if (!$this->hasNativeClass($class)) { - return false; - } - - $classDef = $this->getClassDef($class); - $methodDef = null; - // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 - while ($classDef) { - if (!$classDef->hasMethod($method)) { - if (!$classDef->extends) { - return false; - } - $classDef = $this->getClassDef($classDef->extends); - } else { - $methodDef = $classDef->methods[$method]; - break; - } - } - - if (!$this->checkAccessible($classDef, $methodDef)) { - $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); - } - if (count($expr->args) < $methodDef->functionDef->argCountRequired) { - $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given'); - } elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) { - $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` accepts ' . count($methodDef->functionDef->argInfoList) . ' arguments, ' . count($expr->args) . ' given'); - } - $nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name); + $nativeFunc = $this->getNativeMethod($expr, $class, $method); } $fullMethodName = $classDef->getNamespacedName(false) . '::' . $method; // 存在子类同名方法,需要转为动态调用 diff --git a/src/cpp/main.cc b/src/cpp/main.cc index 83e4ba27..32b5cefd 100644 --- a/src/cpp/main.cc +++ b/src/cpp/main.cc @@ -2,7 +2,8 @@ #if PPROF_ON #include #endif -#include + +#include extern zend_module_entry *php_embed_get_module(); @@ -17,6 +18,23 @@ void module_init(zend_module_entry *module) { } } +const char *php_get_called_class(php::Object &this_) { + auto ce = php_get_called_ce(this_); + if (ce) { + return ce->name->val; + } else { + return ""; + } +} + +zend_class_entry *php_get_called_ce(php::Object &this_) { + if (this_.isObject()) { + return this_.ce(); + } else { + return (zend_class_entry *) Z_PTR_P(this_.ptr()); + } +} + void module_shutdown(zend_module_entry *module) { /** * There is a bug in PHP's handling of internal strings. All interned strings are released in the request shutdown diff --git a/src/cpp/php_aot_helper.h b/src/cpp/php_aot_helper.h index 3312a130..287ada20 100644 --- a/src/cpp/php_aot_helper.h +++ b/src/cpp/php_aot_helper.h @@ -1,4 +1,5 @@ #include +#include #include @@ -6,6 +7,9 @@ extern zend_class_entry *php_get_class(int class_id, const php::Str &class_name) extern zend_function *php_get_func(int func_id, const php::Str &func_name); extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name); +extern const char *php_get_called_class(php::Object &this_); +extern zend_class_entry *php_get_called_ce(php::Object &this_); + static inline php::Variant CALL(int func_id, const php::Str &func_name) { return php::call(php_get_func(func_id, func_name)); } diff --git a/src/template/extension.cc.php b/src/template/extension.cc.php index 31e80101..d0886f14 100644 --- a/src/template/extension.cc.php +++ b/src/template/extension.cc.php @@ -54,7 +54,7 @@ php::Str [] = { literalStrings as $str => $index): ?> - php::String{ZEND_STRL("escapeString($str)?>"), true}, + php::String{ZEND_STRL("escapeString($str)?>"), true}, // [] }; diff --git a/tests/aot/class-static-002.phpt b/tests/aot/class-static-002.phpt new file mode 100644 index 00000000..a7fd946c --- /dev/null +++ b/tests/aot/class-static-002.phpt @@ -0,0 +1,40 @@ +--TEST-- +class static +--FILE-- + +--EXPECT-- +string(5) "hello" +string(5) "world" +string(3) "foo"