feat(php): 添加对函数调用占位符的支持并实现作用域切换功能

- 在 AstNodeType 中添加 isPlaceholderExpr 方法用于检测占位符表达式
- 在 CompilerBase 中添加对函数调用占位符的特殊处理逻辑
- 使用 isPlaceholderExpr 替换直接的类型检查以统一占位符判断
- 在 C++ 层面实现 php_switch_scope 和 php_restore_scope 功能
- 在 PlaceHolderGenerator 中添加作用域切换代码生成逻辑
- 添加新的测试用例验证静态方法占位符调用功能
pull/1/head
韩天峰 5 months ago
parent afc4ef87f8
commit c259d20562
  1. 6
      src/Php/AstNodeType.php
  2. 8
      src/Php/CompilerBase.php
  3. 5
      src/Php/Generator/PlaceHolderGenerator.php
  4. 19
      src/cpp/main.cc
  5. 2
      src/cpp/php_aot_helper.h
  6. 24
      tests/aot/private-prop-002.phpt

@ -11,6 +11,7 @@ namespace PhpAot\Php;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
use PhpParser\NodeAbstract; use PhpParser\NodeAbstract;
use PhpParser\Node\VariadicPlaceholder;
trait AstNodeType trait AstNodeType
{ {
@ -95,4 +96,9 @@ trait AstNodeType
or $expr instanceof Node\Expr\MethodCall or $expr instanceof Node\Expr\MethodCall
or $expr instanceof Node\Expr\StaticCall; or $expr instanceof Node\Expr\StaticCall;
} }
protected function isPlaceholderExpr(NodeAbstract $expr): bool
{
return $expr instanceof VariadicPlaceholder;
}
} }

@ -1565,6 +1565,10 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->checkAccessible($classDef, $methodDef)) { if (!$this->checkAccessible($classDef, $methodDef)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible');
} }
// 函数调用占位符,不是真实的函数调用
if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) {
return false;
}
if (count($expr->args) < $methodDef->functionDef->argCountRequired) { if (count($expr->args) < $methodDef->functionDef->argCountRequired) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given'); $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given');
} elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) { } elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) {
@ -2199,7 +2203,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$hasNamedArg = false; $hasNamedArg = false;
// 对命名参数进行重排 // 对命名参数进行重排
foreach ($callArgs as $i => $arg) { foreach ($callArgs as $i => $arg) {
if ($arg instanceof Node\VariadicPlaceholder) { if ($this->isPlaceholderExpr($arg)) {
throw new PlaceHolder(); throw new PlaceHolder();
} }
if ($arg->name) { if ($arg->name) {
@ -2242,7 +2246,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$list_args = []; $list_args = [];
$last = array_key_last($args); $last = array_key_last($args);
foreach ($args as $i => $arg) { foreach ($args as $i => $arg) {
if ($arg instanceof Node\VariadicPlaceholder) { if ($this->isPlaceholderExpr($arg)) {
throw new PlaceHolder(); throw new PlaceHolder();
} }
if ($arg->name !== null) { if ($arg->name !== null) {

@ -14,6 +14,11 @@ trait PlaceHolderGenerator
{ {
$ce = $this->getClassEntryPtr(\Closure::class); $ce = $this->getClassEntryPtr(\Closure::class);
$fn = $ce . ', ' . $this->getFuncPtr('Closure::fromCallable', false); $fn = $ce . ', ' . $this->getFuncPtr('Closure::fromCallable', false);
$tmpVar = $this->genTmpVarName();
if ($this->classDef) {
$this->beforeStmtLines[] = "auto $tmpVar = php_switch_scope(this_);";
$this->afterStmtLines[] = "php_restore_scope($tmpVar);";
}
return 'php::call(' . $fn . ', {' . $callable . '})'; return 'php::call(' . $fn . ', {' . $callable . '})';
} }
} }

@ -36,6 +36,25 @@ zend_class_entry *php_get_called_ce(php::Object &this_) {
} }
} }
static zend_execute_data *get_frame() {
zend_execute_data *frame = EG(current_execute_data);
while (frame && (!frame->func || !ZEND_USER_CODE(frame->func->type))) {
frame = frame->prev_execute_data;
}
return frame;
}
zend_class_entry *php_switch_scope(php::Object &this_) {
auto frame = get_frame();
auto ori_scope = frame->func->common.scope;
frame->func->common.scope = php_get_called_ce(this_);
return ori_scope;
}
void php_restore_scope(zend_class_entry *ori_scope) {
get_frame()->func->common.scope = ori_scope;
}
void module_shutdown(zend_module_entry *module) { 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 * There is a bug in PHP's handling of internal strings. All interned strings are released in the request shutdown

@ -9,6 +9,8 @@ extern zend_function *php_get_method(int func_id, const php::Str &method_name, i
extern const char *php_get_called_class(php::Object &this_); extern const char *php_get_called_class(php::Object &this_);
extern zend_class_entry *php_get_called_ce(php::Object &this_); extern zend_class_entry *php_get_called_ce(php::Object &this_);
extern zend_class_entry *php_switch_scope(php::Object &this_);
extern void php_restore_scope(zend_class_entry *ori_scope);
static inline php::Variant CALL(int func_id, const php::Str &func_name) { static inline php::Variant CALL(int func_id, const php::Str &func_name) {
return php::call(php_get_func(func_id, func_name)); return php::call(php_get_func(func_id, func_name));

@ -0,0 +1,24 @@
--TEST--
Static Class Property Read/Write Test
--FILE--
<?php
class Worker
{
protected static function checkErrors(): void
{
var_dump(__METHOD__);
}
public static function init()
{
$fn = self::checkErrors(...);
$fn();
}
}
function main() {
Worker::init();
}
?>
--EXPECT--
string(19) "Worker::checkErrors"
Loading…
Cancel
Save