refactor(php): 重构关键词方法查找机制以支持扩展方法

- 将 TO_METHOD_TYPE_MAP 重命名为 KEYWORD_METHOD_MAP
- 实现统一的关键词方法查找函数 findKeywordMethod()
- 添加对 __ 扩展方法的支持,允许作为 $receiver->camelCase() 调用
- 在方法分派前先检查关键词扩展方法
- 为关键词扩展方法实现蛇形命名转换和参数验证逻辑
- 更新现有方法调用以使用新的关键词方法映射常量
pull/1/head
韩天峰 3 months ago
parent 1bb0324605
commit 7a164aa8ee
  1. 1
      phpunit/src/UniversalMethodCallTest.php
  2. 28
      src/Php/CompilerBase.php
  3. 59
      src/Php/UniversalMethodCall.php

@ -21,4 +21,5 @@ class UniversalMethodCallTest extends \BaseTest
{
$this->exec('Cannot call method on void', 'void-method-call.php');
}
}

@ -76,8 +76,10 @@ class CompilerBase extends \PhpAot\Core\Translator
public const string TYPE_BOX = 'php::Box';
/**
* to* conversion methods are keywords with mandated return types. */
public const array TO_METHOD_TYPE_MAP = [
* Keyword methods (to* builtins) with mandated return types.
* Use findKeywordMethod() for unified lookup including keyword extension methods.
*/
public const array KEYWORD_METHOD_MAP = [
'toInt' => self::TYPE_INT,
'toFloat' => self::TYPE_FLOAT,
'toString' => self::TYPE_STR,
@ -2519,9 +2521,10 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Expr_MethodCall':
if ($this->isNamedMethod($expr->name)) {
$method = $this->parseIdentifier($expr->name);
// to* methods are keywords — their return type is mandated regardless of receiver
if (isset(self::TO_METHOD_TYPE_MAP[$method])) {
return self::TO_METHOD_TYPE_MAP[$method];
// keyword methods (to* builtins + __ extensions) — return type is known regardless of receiver
$kwType = $this->findKeywordMethod($method);
if ($kwType !== null) {
return $kwType;
}
// Class definition resolution (handles this_, typed VarExpr)
$classDef = $this->resolveObjectClassDef($expr->var);
@ -5444,7 +5447,7 @@ class CompilerBase extends \PhpAot\Core\Translator
// to* conversion methods are language keywords — always dispatched directly
if ($this->isNamedMethod($expr->name)) {
$methodName = $expr->name->toString();
if (isset(self::TO_METHOD_TYPE_MAP[$methodName])) {
if (isset(self::KEYWORD_METHOD_MAP[$methodName])) {
if ($this->isVarExpr($expr->var)) {
$receiverType = $this->getVarType($object);
} else {
@ -5458,6 +5461,19 @@ class CompilerBase extends \PhpAot\Core\Translator
}
return $this->genToConvertCall($object, $methodName, $receiverType);
}
// keyword extension methods (__ functions) — dispatched before type-specific logic
$kwExt = $this->findKeywordExtensionMethod($methodName);
if ($kwExt) {
if ($this->isVarExpr($expr->var)) {
$receiverType = $this->getVarType($object);
} else {
$receiverType = $this->detectTypeOfExpr($expr->var);
}
if ($receiverType === self::TYPE_VOID) {
$this->fatalError($expr->var, 'Cannot call method on void');
}
return $this->parseUniversalMethodCall($expr, $object, $methodName, $kwExt, $this->isVarExpr($expr->var));
}
}
// 可转为原生调用的 MethodCall

@ -428,6 +428,65 @@ trait UniversalMethodCall
return null;
}
/**
* Look up a keyword extension method: function __snake_case in root namespace
* whose first parameter is mixed/any. Callable as $receiver->camelCase() on any type.
*/
protected function findKeywordExtensionMethod(string $method): ?array
{
$snakeMethod = $this->camelToSnake($method);
$funcName = '__' . $snakeMethod;
if (!$this->hasFunction($funcName)) {
return null;
}
$funcDef = $this->getFunction($funcName);
// Must be in root namespace
if ($funcDef->namespace !== '') {
return null;
}
// First param must be mixed/any (TYPE_VAR)
if (empty($funcDef->argInfoList)) {
return null;
}
$firstParam = $funcDef->argInfoList[0];
if ($firstParam->type !== CompilerBase::TYPE_VAR) {
return null;
}
$totalParams = count($funcDef->argInfoList);
$minArgs = max(0, $funcDef->argCountRequired - 1);
$maxArgs = $totalParams - 1;
if ($funcDef->hasVariadicArg()) {
$maxArgs = -1;
}
return [
'handler' => 'php_fn',
'fn' => $funcName,
'receiver_pos' => 1,
'return_type' => $funcDef->returnType,
'min_args' => $minArgs,
'max_args' => $maxArgs,
];
}
/**
* Unified keyword method lookup (to* builtins + __ extension methods).
* Returns the return type string, or null if the method is not a keyword method.
*/
protected function findKeywordMethod(string $method): ?string
{
if (isset(CompilerBase::KEYWORD_METHOD_MAP[$method])) {
return CompilerBase::KEYWORD_METHOD_MAP[$method];
}
$kwExt = $this->findKeywordExtensionMethod($method);
return $kwExt ? $kwExt['return_type'] : null;
}
private function buildInternalExtensionMethod(string $type, string $funcName): ?array
{
$ref = Reflection::getFunction($funcName);

Loading…
Cancel
Save