diff --git a/src/CompilerBase.php b/src/CompilerBase.php index bb2b37c1..54844205 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -40,6 +40,7 @@ use TypePhp\Parser\BinaryOpTrait; use TypePhp\Parser\ClassConstantFetchTrait; use TypePhp\Parser\ExceptionControlFlowTrait; use TypePhp\Parser\FunctionCallTrait; +use TypePhp\Parser\MethodCallTrait; use TypePhp\Parser\NullsafeAccessTrait; use TypePhp\Parser\TypeConversionTrait; use TypePhp\Parser\TypeDetectionTrait; @@ -94,6 +95,7 @@ class CompilerBase implements PropertyAccessContext use ClassConstantFetchTrait; use ExceptionControlFlowTrait; use FunctionCallTrait; + use MethodCallTrait; use NullsafeAccessTrait; use TypeConversionTrait; use TypeDetectionTrait; @@ -5962,181 +5964,6 @@ class CompilerBase implements PropertyAccessContext return 'php::toObject(' . $receiver . ', ' . $this->getClassEntryPtr($className) . ')'; } - protected function genToObjectCall(Expr\MethodCall $expr, string $receiver): string - { - if (empty($expr->args)) { - return 'php::toObject(' . $receiver . ')'; - } - $className = $this->resolveClassNameArg($expr->args[0]->value); - return 'php::toObject(' . $receiver . ', ' . $this->getClassEntryPtr($className) . ')'; - } - - protected function genToRefCall(Expr\MethodCall $expr): string - { - if (!empty($expr->args)) { - $this->fatalError($expr, 'The toRef method does not accept parameters'); - } - return $this->parseChainedExpr($expr->var, self::OP_REFVAL); - } - - protected function parseMethodCall(Expr\MethodCall $expr): string - { - if ($this->containsNullsafeChain($expr->var)) { - return $this->parseNullsafeExpr($expr); - } - - $class = ''; - $object = $this->parseIdentifier($expr->var); - if ($this->isVarExpr($expr->var)) { - if (!$this->hasVar($object)) { - $this->errorUndefinedVariable($expr->var); - } - if ($this->isTypedObject($object)) { - $class = $this->getObjectType($object); - } elseif ($object === 'this_') { - // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 - $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; - } else { - // 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。 - $class = $this->getDeclaredObjectType($object); - } - } - - $magicMethod = false; - $method = $this->identifierToStr($expr->name, literal: true); - - // keyword methods (to* builtins + __ extensions) — dispatched before type-specific logic - if ($this->isNamedMethod($expr->name)) { - $methodName = $expr->name->toString(); - $receiverType = $this->isVarExpr($expr->var) ? $this->getVarType($object) : $this->detectTypeOfExpr($expr->var); - if ($receiverType === self::TYPE_VOID) { - $receiverType = self::TYPE_VAR; - } - // to* builtins - if (isset(self::KEYWORD_METHOD_MAP[$methodName])) { - if ($methodName === 'toObject') { - return $this->genToObjectCall($expr, $object); - } - if ($methodName === 'toRef') { - return $this->genToRefCall($expr); - } - if ($methodName === 'toAny' && !empty($expr->args)) { - $this->fatalError($expr, 'The toAny method does not accept parameters'); - } - return $this->genToConvertCall($object, $methodName, $receiverType); - } - // __ keyword extensions - $kwExt = $this->findKeywordExtensionMethod($methodName); - if ($kwExt) { - return $this->parseUniversalMethodCall($expr, $object, $methodName, $kwExt, $this->isVarExpr($expr->var)); - } - } - - // 可转为原生调用的 MethodCall - if ($this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { - $type = $this->getVarType($object); - // 引用参数允许方法调用:有class信息走原生调用,无class信息走动态调用 - if (!$this->checkArgType($type, self::TYPE_OBJECT) and $type !== self::TYPE_REF) { - $methodName = $expr->name->toString(); - // 非对象类型可使用内置方法 - $fn = $this->findUniversalMethodAnyType($type, $methodName); - if ($fn) { - if ($type === self::TYPE_STREAM) { - return $this->genStreamNullGuard($expr, $object, $methodName, $fn); - } - return $this->parseUniversalMethodCall($expr, $object, $methodName, $fn); - } - $this->fatalError($expr, "Cannot call method `{$methodName}()` on variable of type {$type}"); - } - $this->context->beforeStmtLines[] = $this->formatCppLineComment( - 'Method Call: ', - $object . '->' . $this->parseIdentifier($expr->name) . '()' - ); - try { - $nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name)); - if ($nativeFunc) { - $expr->setAttribute('nativeCall', $nativeFunc); - try { - if ($this->shouldUseDynamicCallForNativeArgs($nativeFunc, $expr->args)) { - return $this->genRuntimeObjectMethodCall($object, $this->getMethodPtr($class, $methodName), $expr->args, $methodName, $class); - } - return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); - } catch (PlaceHolder) { - return $this->genPlaceHolder($this->genArray([$object, $method])); - } - } - } catch (DynamicCall) { - $extension = $this->findObjectExtensionMethod($class, $methodName); - if ($extension !== null) { - return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension); - } - $magicMethod = true; - } - if (!$nativeFunc) { - $extension = $this->findObjectExtensionMethod($class, $methodName); - if ($extension !== null) { - return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension); - } - } - } - - // 表达式返回值也可使用内置方法:fn()->method(), $obj->fn()->method(), Foo::fn()->method(), $obj->prop->method() - if (!$this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { - $type = $this->detectTypeOfExpr($expr->var); - if ($type === self::TYPE_VOID) { - $type = self::TYPE_VAR; - } - if ($type !== self::TYPE_VAR && !$this->checkArgType($type, self::TYPE_OBJECT)) { - $methodName = $expr->name->toString(); - $fn = $this->findUniversalMethodAnyType($type, $methodName); - if ($fn) { - // Wrap receiver in type conversion for direct_method handlers - // since the raw expression (often from php::call()) is php::Variant - $receiver = $object; - if ($fn['handler'] === 'direct_method') { - $receiver = $this->wrapUniversalReceiver($type, $object); - } - if ($type === self::TYPE_STREAM) { - return $this->genStreamNullGuard($expr, $receiver, $methodName, $fn); - } - return $this->parseUniversalMethodCall($expr, $receiver, $methodName, $fn, false); - } - } - - $extensionClass = $this->detectClassOfExpr($expr->var); - $extension = $this->findObjectExtensionMethod($extensionClass, $methodName); - if ($extension !== null) { - return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension, false); - } - } - - if ($this->isNamedMethod($expr->name)) { - $funcName = $this->parseIdentifier($expr->name); - } else { - $funcName = ''; - } - - if ($class && $funcName && !$magicMethod && $this->isInternalClass($class)) { - $methodPtr = $this->getMethodPtr($class, $funcName); - } else { - $methodPtr = $method; - } - - if ($object === 'this_' or $object === 'self' or $object === 'static') { - $this->methodDef->hasDynamicCall = true; - } - - if (empty($expr->args)) { - return $object . '.call(' . $methodPtr . ')'; - } - try { - $class = empty($class) ? self::DYNAMIC_CALLED_CLASS : $class; - return $this->genRuntimeObjectMethodCall($object, $methodPtr, $expr->args, $funcName, $class); - } catch (PlaceHolder) { - return $this->genPlaceHolder($this->genArray([$object, $method])); - } - } - protected function identifierToStr(NodeAbstract $node, bool $require = true, bool $literal = false): string { $id = $this->parseIdentifier($node); @@ -6167,119 +5994,6 @@ class CompilerBase implements PropertyAccessContext } } - protected function parseStaticCall(Expr\StaticCall $expr): string - { - $self = false; - $callScope = []; - $class = $this->parseIdentifier($expr->class); - - // parent::$method() still has a lexical parent class even when the - // method name itself is dynamic. Handle it before the generic dynamic - // static-call branch below. - if ($this->isNameExpr($expr->class) && $class === 'parent') { - return $this->parseParentMethodCall($expr); - } - - if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { - $var = $class; - if ($this->isTypedObject($var)) { - $class = $this->getObjectType($var); - goto _do_call; - } - if ($this->getVarType($var) == self::TYPE_OBJECT) { - $fn = 'php::concat({' . $var . '.getClassName(), "::", ' . $this->identifierToStr($expr->name) . '})'; - } else { - $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; - } - $placeHolder = $fn; - } elseif ($this->isNameExpr($expr->class) and $class === 'static') { - $methodPtr = $this->identifierToStr($expr->name, literal: true); - $fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')'; - $this->context->beforeStmtLines[] = $this->formatCppLineComment( - 'Static Method Call: ', - 'static::' . $this->parseIdentifier($expr->name) . '()' - ); - $placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]); - } elseif ($this->isNameExpr($expr->class)) { - if ($class === 'self') { - $class = $this->class; - $self = true; - } elseif ($class === 'std') { - return $this->parseStdCall($expr); - } - $class = $this->getNamespacedClassName($class); - - _do_call: - $method = $this->parseIdentifier($expr->name); - $dynamicCall = false; - $this->context->beforeStmtLines[] = $this->formatCppLineComment( - 'Static Method Call: ', - $class . '::' . $method . '()' - ); - - if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { - $callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)]; - } - - if ($callScope) { - $nativeFunc = $this->getNativeMethod($expr, $class, $method); - // 存在 Native 类,但是没有找到方法,可能是动态调用 - if (!$nativeFunc and $this->hasClass($class) and $this->getNativeMethod($expr, $class, '__callStatic', false)) { - $dynamicCall = true; - } - - if ($nativeFunc) { - try { - if ($this->shouldUseDynamicCallForNativeArgs($nativeFunc, $expr->args)) { - return $this->genRuntimeFunctionCall( - $this->getClassEntryPtr($class) . ', ' . $this->getFuncPtr($class . '::' . $method), - $expr->args, - $method, - $class - ); - } - $args = $this->parseNativeCallArgs($expr->args, $nativeFunc); - $expr->setAttribute('nativeCall', $nativeFunc); - } catch (PlaceHolder) { - return $this->genPlaceHolder($this->genArray($callScope)); - } - // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 - if ($this->methodDef and $self) { - $object = 'this_'; - } else { - $object = $this->getCeWrapper($class); - } - if ($args) { - return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')'; - } else { - return self::PREFIX . $nativeFunc . '(' . $object . ')'; - } - } - } - - if ($dynamicCall) { - $fn = $this->getLiteralString($class . '::' . $method); - } else { - $ce = $this->getClassEntryPtr($class); - $fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method); - } - $placeHolder = $this->genArray($callScope); - } else { - $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; - $placeHolder = $fn; - } - - $call = 'php::call'; - if (empty($expr->args)) { - return $call . '(' . $fn . ')'; - } - try { - return $this->genRuntimeFunctionCall($fn, $expr->args); - } catch (PlaceHolder) { - return $this->genPlaceHolder($placeHolder); - } - } - protected function resolveNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): ?StaticPropertyFetchResolution { $target = $this->resolveStaticPropertyFetchTarget($expr); @@ -6987,251 +6701,6 @@ class CompilerBase implements PropertyAccessContext return true; } - protected function isOverrideMethod(string $fullMethodName): bool - { - $fullMethodNameLower = strtolower($fullMethodName); - return isset($this->classMethodOverride[$fullMethodNameLower]) and $this->classMethodOverride[$fullMethodNameLower]; - } - - protected function getOverrideMethodName(string $class, string $method): string - { - if (!$this->hasClass($class) && !$this->hasInterface($class) - && !$this->isInternalClass($class) && !$this->isInternalInterface($class)) { - $class = $this->getNamespacedClassName($class); - } - return $class . '::' . $method; - } - - protected function hasSubClasses(string $classNameLower): bool - { - return !empty($this->classSubClasses[$classNameLower]); - } - - protected function isCurrentClassFinal(): bool - { - return $this->classDef && ($this->classDef->flags & Modifiers::FINAL) !== 0; - } - - protected function isFinalClass(string $class): bool - { - return $this->hasClass($class) && ($this->getClass($class)->flags & Modifiers::FINAL) !== 0; - } - - protected function getMethodFlags(string $class, string $method): int - { - if (!$this->hasClass($class)) { - return 0; - } - $classDef = $this->getClass($class); - while (true) { - $flags = $classDef->getMethodFlags($method); - if ($flags !== 0) { - return $flags; - } - if (!$classDef->extends || !$this->hasClass($classDef->extends)) { - return 0; - } - $classDef = $this->getClass($classDef->extends); - } - } - - protected function guardAbstractMethod(string $class, string $method, Node $expr): void - { - $flags = $this->getMethodFlags($class, $method); - if ($flags & Modifiers::ABSTRACT) { - $this->fatalError($expr, "Cannot call abstract method `{$class}::{$method}()`"); - } - } - - /** - * Determine whether a method call can be devirtualized to a direct native call. - * - * Returns true when the exact class is known at compile time: - * 1. $this->m() in a final class (no subclass possible) - * 2. $this->m() where m is final (can't be overridden) - * 3. $this->m() where m is private (not virtual) - * 4. $obj->m() where obj's class has no known subclasses - * 5. $obj->m() where obj is SSA-stable and its class is final - */ - protected function canDevirtualize(string $object, string $class, string $method): bool - { - // Case 1: Calling on 'this_' in a final class - if ($object === 'this_' && $this->isCurrentClassFinal()) { - return true; - } - - // Case 2 & 3: Method is final or private - $flags = $this->getMethodFlags($class, $method); - if ($flags & (Modifiers::FINAL | Modifiers::PRIVATE)) { - return true; - } - - // Case 4: Typed object whose class has no known subclasses - if ($object !== 'this_' && $this->hasClass($class)) { - $classLower = strtolower($class); - if (!$this->hasSubClasses($classLower) && !$this->isInterface($class) && !$this->isAbstractClass($class)) { - return true; - } - } - - // Case 5: SSA stability proves the variable identity, not necessarily - // the runtime class. Only final classes are exact enough here. - if ($object !== 'this_' && isset($this->context->stableObjects[$object])) { - $stableClass = $this->context->stableObjects[$object]; - if ($this->isFinalClass($stableClass)) { - return true; - } - } - - return false; - } - - protected function findNativeMethod(CallLike $expr, string $object, string $method): string|false - { - $classDef = null; - if ($object === 'this_') { - $class = $this->getFullClassName(); - $classDef = $this->classDef; - } elseif (isset($this->context->objects[$object])) { - $class = $this->context->objects[$object]; - // SSA-stable: use exact type from stableObjects (more specific than declared type) - if (isset($this->context->stableObjects[$object])) { - $class = $this->context->stableObjects[$object]; - } - } else { - return false; - } - - $nativeFunc = $this->getNativeMethod($expr, $class, $method); - // 存在 Native 类,但是没有找到方法,可能是动态调用 - if (!$nativeFunc) { - if ($this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) { - throw new DynamicCall(); - } - } - - $fullMethodName = $this->getOverrideMethodName($class, $method); - - // 存在子类同名方法,尝试去虚化 - if ($this->isOverrideMethod($fullMethodName)) { - if (!$this->canDevirtualize($object, $class, $method)) { - return false; - } - } - if ($nativeFunc) { - if ($this->hasClass($class) && $this->getMethodFlags($class, $method) & Modifiers::ABSTRACT) { - return false; - } - if ($object !== 'this_' && !isset($this->context->stableObjects[$object]) - && ($this->isAbstractClass($class) || $this->isInterface($class))) { - return false; - } - $this->checkFunction($nativeFunc); - if ($this->hasFunction($nativeFunc)) { - return $nativeFunc; - } - } - return false; - } - - protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string - { - if ($this->getVarType($object) != self::TYPE_OBJECT) { - $tmpVar = $this->genTmpVarName(); - $this->context->beforeStmtLines[] = self::TYPE_OBJECT . ' ' . $tmpVar . ' = ' . $object . ';'; - $object = $tmpVar; - } - if (count($args) === 0) { - return self::PREFIX . $nativeFunc . '(' . $object . ')'; - } - return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')'; - } - - protected function parseStdCall(Expr\StaticCall $expr): string - { - $func = strtolower($this->parseIdentifier($expr->name)); - $type = match ($func) { - 'int' => self::TYPE_INT, - 'float' => self::TYPE_FLOAT, - 'bool' => self::TYPE_BOOL, - 'bigint' => self::TYPE_BIGINT, - 'decimal' => self::TYPE_DECIMAL, - 'bigfloat' => self::TYPE_BIGFLOAT, - default => '', - }; - if ($type) { - $expr->setAttribute('nativeType', $type); - $valueExpr = $this->parseExpr($expr->args[0]->value); - if (in_array($type, [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL])) { - return $this->convertExprFromType($type, $valueExpr); - } - $argType = $this->detectTypeOfExpr($expr->args[0]->value); - if ($argType === $type) { - return $valueExpr; - } - if ($type === self::TYPE_BIGINT) { - if ($argType === self::TYPE_FLOAT) { - $this->fatalError($expr, 'Cannot construct BigInt from float, use string or int instead'); - } - if ($argType === self::TYPE_INT) { - return 'php::toBigInt(' . $valueExpr . ')'; - } - return 'php::BigInt::newInstance(' . $valueExpr . ')'; - } - if ($type === self::TYPE_DECIMAL) { - if ($argType === self::TYPE_FLOAT) { - $argNode = $expr->args[0]->value; - if ($argNode instanceof Node\Scalar\Float_) { - $rawValue = $argNode->getAttribute('rawValue'); - $clean = $rawValue !== null ? $this->stripNumericUnderscores($rawValue) : (string)$argNode->value; - return 'php::toDecimal(' . $this->getLiteralString($clean) . ')'; - } - $this->fatalError($expr, 'Cannot construct Decimal from float variable, use string or int instead'); - } - if ($argType === self::TYPE_INT) { - return 'php::toDecimal(' . $valueExpr . ')'; - } - return 'php::Decimal::newInstance(' . $valueExpr . ')'; - } - if ($type === self::TYPE_BIGFLOAT) { - if ($argType === self::TYPE_INT) { - return 'php::toBigFloat(' . $valueExpr . ')'; - } - if ($argType === self::TYPE_FLOAT) { - return 'php::toBigFloat(' . $valueExpr . ')'; - } - return 'php::BigFloat::newInstance(' . $valueExpr . ')'; - } - return $valueExpr; - } else { - $this->fatalError($expr, 'Unknown std method: ' . $func); - } - } - - - protected function parseParentMethodCall(Expr\StaticCall $expr): string - { - if (!$this->classDef->extends) { - $this->fatalError($expr, 'Cannot call parent method because class `' . $this->classDef->name . '` does not extend any class'); - } - $parentClass = $this->classDef->extends; - if ($this->isIdExpr($expr->name)) { - $method = $this->parseIdentifier($expr->name); - $this->guardAbstractMethod($parentClass, $method, $expr); - $methodPtr = $this->getMethodPtr($parentClass, $method); - } else { - // parent:: is bound to the lexical parent class, not the runtime - // object's parent. Look the method up on that class, then invoke it - // through this_ so Zend receives the current call scope. - $methodPtr = 'php::getMethod(' . $this->getClassEntryPtr($parentClass) . ', ' - . $this->identifierToStr($expr->name) . ')'; - } - if (empty($expr->args)) { - return 'this_.call(' . $methodPtr . ')'; - } - return 'this_.call(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args) . ')'; - } - protected function genDebugInfo(?NodeAbstract $stmt = null, string $functionName = '', int $startLine = 0): string { $code = ''; diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php new file mode 100644 index 00000000..f5b08f08 --- /dev/null +++ b/src/Parser/MethodCallTrait.php @@ -0,0 +1,555 @@ +classMethodOverride[$fullMethodNameLower]) and $this->classMethodOverride[$fullMethodNameLower]; + } + + protected function getOverrideMethodName(string $class, string $method): string + { + if (!$this->hasClass($class) && !$this->hasInterface($class) + && !$this->isInternalClass($class) && !$this->isInternalInterface($class)) { + $class = $this->getNamespacedClassName($class); + } + return $class . '::' . $method; + } + + protected function hasSubClasses(string $classNameLower): bool + { + return !empty($this->classSubClasses[$classNameLower]); + } + + protected function isCurrentClassFinal(): bool + { + return $this->classDef && ($this->classDef->flags & Modifiers::FINAL) !== 0; + } + + protected function isFinalClass(string $class): bool + { + return $this->hasClass($class) && ($this->getClass($class)->flags & Modifiers::FINAL) !== 0; + } + + protected function getMethodFlags(string $class, string $method): int + { + if (!$this->hasClass($class)) { + return 0; + } + $classDef = $this->getClass($class); + while (true) { + $flags = $classDef->getMethodFlags($method); + if ($flags !== 0) { + return $flags; + } + if (!$classDef->extends || !$this->hasClass($classDef->extends)) { + return 0; + } + $classDef = $this->getClass($classDef->extends); + } + } + + protected function guardAbstractMethod(string $class, string $method, Node $expr): void + { + $flags = $this->getMethodFlags($class, $method); + if ($flags & Modifiers::ABSTRACT) { + $this->fatalError($expr, "Cannot call abstract method `{$class}::{$method}()`"); + } + } + + /** + * Determine whether a method call can be devirtualized to a direct native call. + * + * Returns true when the exact class is known at compile time: + * 1. $this->m() in a final class (no subclass possible) + * 2. $this->m() where m is final (can't be overridden) + * 3. $this->m() where m is private (not virtual) + * 4. $obj->m() where obj's class has no known subclasses + * 5. $obj->m() where obj is SSA-stable and its class is final + */ + protected function canDevirtualize(string $object, string $class, string $method): bool + { + // Case 1: Calling on 'this_' in a final class + if ($object === 'this_' && $this->isCurrentClassFinal()) { + return true; + } + + // Case 2 & 3: Method is final or private + $flags = $this->getMethodFlags($class, $method); + if ($flags & (Modifiers::FINAL | Modifiers::PRIVATE)) { + return true; + } + + // Case 4: Typed object whose class has no known subclasses + if ($object !== 'this_' && $this->hasClass($class)) { + $classLower = strtolower($class); + if (!$this->hasSubClasses($classLower) && !$this->isInterface($class) && !$this->isAbstractClass($class)) { + return true; + } + } + + // Case 5: SSA stability proves the variable identity, not necessarily + // the runtime class. Only final classes are exact enough here. + if ($object !== 'this_' && isset($this->context->stableObjects[$object])) { + $stableClass = $this->context->stableObjects[$object]; + if ($this->isFinalClass($stableClass)) { + return true; + } + } + + return false; + } + + protected function findNativeMethod(CallLike $expr, string $object, string $method): string|false + { + $classDef = null; + if ($object === 'this_') { + $class = $this->getFullClassName(); + $classDef = $this->classDef; + } elseif (isset($this->context->objects[$object])) { + $class = $this->context->objects[$object]; + // SSA-stable: use exact type from stableObjects (more specific than declared type) + if (isset($this->context->stableObjects[$object])) { + $class = $this->context->stableObjects[$object]; + } + } else { + return false; + } + + $nativeFunc = $this->getNativeMethod($expr, $class, $method); + // 存在 Native 类,但是没有找到方法,可能是动态调用 + if (!$nativeFunc) { + if ($this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) { + throw new DynamicCall(); + } + } + + $fullMethodName = $this->getOverrideMethodName($class, $method); + + // 存在子类同名方法,尝试去虚化 + if ($this->isOverrideMethod($fullMethodName)) { + if (!$this->canDevirtualize($object, $class, $method)) { + return false; + } + } + if ($nativeFunc) { + if ($this->hasClass($class) && $this->getMethodFlags($class, $method) & Modifiers::ABSTRACT) { + return false; + } + if ($object !== 'this_' && !isset($this->context->stableObjects[$object]) + && ($this->isAbstractClass($class) || $this->isInterface($class))) { + return false; + } + $this->checkFunction($nativeFunc); + if ($this->hasFunction($nativeFunc)) { + return $nativeFunc; + } + } + return false; + } + + protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string + { + if ($this->getVarType($object) != self::TYPE_OBJECT) { + $tmpVar = $this->genTmpVarName(); + $this->context->beforeStmtLines[] = self::TYPE_OBJECT . ' ' . $tmpVar . ' = ' . $object . ';'; + $object = $tmpVar; + } + if (count($args) === 0) { + return self::PREFIX . $nativeFunc . '(' . $object . ')'; + } + return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')'; + } + + protected function parseStdCall(Expr\StaticCall $expr): string + { + $func = strtolower($this->parseIdentifier($expr->name)); + $type = match ($func) { + 'int' => self::TYPE_INT, + 'float' => self::TYPE_FLOAT, + 'bool' => self::TYPE_BOOL, + 'bigint' => self::TYPE_BIGINT, + 'decimal' => self::TYPE_DECIMAL, + 'bigfloat' => self::TYPE_BIGFLOAT, + default => '', + }; + if ($type) { + $expr->setAttribute('nativeType', $type); + $valueExpr = $this->parseExpr($expr->args[0]->value); + if (in_array($type, [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL])) { + return $this->convertExprFromType($type, $valueExpr); + } + $argType = $this->detectTypeOfExpr($expr->args[0]->value); + if ($argType === $type) { + return $valueExpr; + } + if ($type === self::TYPE_BIGINT) { + if ($argType === self::TYPE_FLOAT) { + $this->fatalError($expr, 'Cannot construct BigInt from float, use string or int instead'); + } + if ($argType === self::TYPE_INT) { + return 'php::toBigInt(' . $valueExpr . ')'; + } + return 'php::BigInt::newInstance(' . $valueExpr . ')'; + } + if ($type === self::TYPE_DECIMAL) { + if ($argType === self::TYPE_FLOAT) { + $argNode = $expr->args[0]->value; + if ($argNode instanceof Node\Scalar\Float_) { + $rawValue = $argNode->getAttribute('rawValue'); + $clean = $rawValue !== null ? $this->stripNumericUnderscores($rawValue) : (string)$argNode->value; + return 'php::toDecimal(' . $this->getLiteralString($clean) . ')'; + } + $this->fatalError($expr, 'Cannot construct Decimal from float variable, use string or int instead'); + } + if ($argType === self::TYPE_INT) { + return 'php::toDecimal(' . $valueExpr . ')'; + } + return 'php::Decimal::newInstance(' . $valueExpr . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + if ($argType === self::TYPE_INT) { + return 'php::toBigFloat(' . $valueExpr . ')'; + } + if ($argType === self::TYPE_FLOAT) { + return 'php::toBigFloat(' . $valueExpr . ')'; + } + return 'php::BigFloat::newInstance(' . $valueExpr . ')'; + } + return $valueExpr; + } else { + $this->fatalError($expr, 'Unknown std method: ' . $func); + } + } + + + protected function parseParentMethodCall(Expr\StaticCall $expr): string + { + if (!$this->classDef->extends) { + $this->fatalError($expr, 'Cannot call parent method because class `' . $this->classDef->name . '` does not extend any class'); + } + $parentClass = $this->classDef->extends; + if ($this->isIdExpr($expr->name)) { + $method = $this->parseIdentifier($expr->name); + $this->guardAbstractMethod($parentClass, $method, $expr); + $methodPtr = $this->getMethodPtr($parentClass, $method); + } else { + // parent:: is bound to the lexical parent class, not the runtime + // object's parent. Look the method up on that class, then invoke it + // through this_ so Zend receives the current call scope. + $methodPtr = 'php::getMethod(' . $this->getClassEntryPtr($parentClass) . ', ' + . $this->identifierToStr($expr->name) . ')'; + } + if (empty($expr->args)) { + return 'this_.call(' . $methodPtr . ')'; + } + return 'this_.call(' . $methodPtr . ', ' . $this->parseCallArgs($expr->args) . ')'; + } + + + protected function genToObjectCall(Expr\MethodCall $expr, string $receiver): string + { + if (empty($expr->args)) { + return 'php::toObject(' . $receiver . ')'; + } + $className = $this->resolveClassNameArg($expr->args[0]->value); + return 'php::toObject(' . $receiver . ', ' . $this->getClassEntryPtr($className) . ')'; + } + + protected function genToRefCall(Expr\MethodCall $expr): string + { + if (!empty($expr->args)) { + $this->fatalError($expr, 'The toRef method does not accept parameters'); + } + return $this->parseChainedExpr($expr->var, self::OP_REFVAL); + } + + protected function parseMethodCall(Expr\MethodCall $expr): string + { + if ($this->containsNullsafeChain($expr->var)) { + return $this->parseNullsafeExpr($expr); + } + + $class = ''; + $object = $this->parseIdentifier($expr->var); + if ($this->isVarExpr($expr->var)) { + if (!$this->hasVar($object)) { + $this->errorUndefinedVariable($expr->var); + } + if ($this->isTypedObject($object)) { + $class = $this->getObjectType($object); + } elseif ($object === 'this_') { + // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 + $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; + } else { + // 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。 + $class = $this->getDeclaredObjectType($object); + } + } + + $magicMethod = false; + $method = $this->identifierToStr($expr->name, literal: true); + + // keyword methods (to* builtins + __ extensions) — dispatched before type-specific logic + if ($this->isNamedMethod($expr->name)) { + $methodName = $expr->name->toString(); + $receiverType = $this->isVarExpr($expr->var) ? $this->getVarType($object) : $this->detectTypeOfExpr($expr->var); + if ($receiverType === self::TYPE_VOID) { + $receiverType = self::TYPE_VAR; + } + // to* builtins + if (isset(self::KEYWORD_METHOD_MAP[$methodName])) { + if ($methodName === 'toObject') { + return $this->genToObjectCall($expr, $object); + } + if ($methodName === 'toRef') { + return $this->genToRefCall($expr); + } + if ($methodName === 'toAny' && !empty($expr->args)) { + $this->fatalError($expr, 'The toAny method does not accept parameters'); + } + return $this->genToConvertCall($object, $methodName, $receiverType); + } + // __ keyword extensions + $kwExt = $this->findKeywordExtensionMethod($methodName); + if ($kwExt) { + return $this->parseUniversalMethodCall($expr, $object, $methodName, $kwExt, $this->isVarExpr($expr->var)); + } + } + + // 可转为原生调用的 MethodCall + if ($this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { + $type = $this->getVarType($object); + // 引用参数允许方法调用:有class信息走原生调用,无class信息走动态调用 + if (!$this->checkArgType($type, self::TYPE_OBJECT) and $type !== self::TYPE_REF) { + $methodName = $expr->name->toString(); + // 非对象类型可使用内置方法 + $fn = $this->findUniversalMethodAnyType($type, $methodName); + if ($fn) { + if ($type === self::TYPE_STREAM) { + return $this->genStreamNullGuard($expr, $object, $methodName, $fn); + } + return $this->parseUniversalMethodCall($expr, $object, $methodName, $fn); + } + $this->fatalError($expr, "Cannot call method `{$methodName}()` on variable of type {$type}"); + } + $this->context->beforeStmtLines[] = $this->formatCppLineComment( + 'Method Call: ', + $object . '->' . $this->parseIdentifier($expr->name) . '()' + ); + try { + $nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name)); + if ($nativeFunc) { + $expr->setAttribute('nativeCall', $nativeFunc); + try { + if ($this->shouldUseDynamicCallForNativeArgs($nativeFunc, $expr->args)) { + return $this->genRuntimeObjectMethodCall($object, $this->getMethodPtr($class, $methodName), $expr->args, $methodName, $class); + } + return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); + } catch (PlaceHolder) { + return $this->genPlaceHolder($this->genArray([$object, $method])); + } + } + } catch (DynamicCall) { + $extension = $this->findObjectExtensionMethod($class, $methodName); + if ($extension !== null) { + return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension); + } + $magicMethod = true; + } + if (!$nativeFunc) { + $extension = $this->findObjectExtensionMethod($class, $methodName); + if ($extension !== null) { + return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension); + } + } + } + + // 表达式返回值也可使用内置方法:fn()->method(), $obj->fn()->method(), Foo::fn()->method(), $obj->prop->method() + if (!$this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) { + $type = $this->detectTypeOfExpr($expr->var); + if ($type === self::TYPE_VOID) { + $type = self::TYPE_VAR; + } + if ($type !== self::TYPE_VAR && !$this->checkArgType($type, self::TYPE_OBJECT)) { + $methodName = $expr->name->toString(); + $fn = $this->findUniversalMethodAnyType($type, $methodName); + if ($fn) { + // Wrap receiver in type conversion for direct_method handlers + // since the raw expression (often from php::call()) is php::Variant + $receiver = $object; + if ($fn['handler'] === 'direct_method') { + $receiver = $this->wrapUniversalReceiver($type, $object); + } + if ($type === self::TYPE_STREAM) { + return $this->genStreamNullGuard($expr, $receiver, $methodName, $fn); + } + return $this->parseUniversalMethodCall($expr, $receiver, $methodName, $fn, false); + } + } + + $extensionClass = $this->detectClassOfExpr($expr->var); + $extension = $this->findObjectExtensionMethod($extensionClass, $methodName); + if ($extension !== null) { + return $this->parseUniversalMethodCall($expr, $object, $methodName, $extension, false); + } + } + + if ($this->isNamedMethod($expr->name)) { + $funcName = $this->parseIdentifier($expr->name); + } else { + $funcName = ''; + } + + if ($class && $funcName && !$magicMethod && $this->isInternalClass($class)) { + $methodPtr = $this->getMethodPtr($class, $funcName); + } else { + $methodPtr = $method; + } + + if ($object === 'this_' or $object === 'self' or $object === 'static') { + $this->methodDef->hasDynamicCall = true; + } + + if (empty($expr->args)) { + return $object . '.call(' . $methodPtr . ')'; + } + try { + $class = empty($class) ? self::DYNAMIC_CALLED_CLASS : $class; + return $this->genRuntimeObjectMethodCall($object, $methodPtr, $expr->args, $funcName, $class); + } catch (PlaceHolder) { + return $this->genPlaceHolder($this->genArray([$object, $method])); + } + } + + + protected function parseStaticCall(Expr\StaticCall $expr): string + { + $self = false; + $callScope = []; + $class = $this->parseIdentifier($expr->class); + + // parent::$method() still has a lexical parent class even when the + // method name itself is dynamic. Handle it before the generic dynamic + // static-call branch below. + if ($this->isNameExpr($expr->class) && $class === 'parent') { + return $this->parseParentMethodCall($expr); + } + + if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { + $var = $class; + if ($this->isTypedObject($var)) { + $class = $this->getObjectType($var); + goto _do_call; + } + if ($this->getVarType($var) == self::TYPE_OBJECT) { + $fn = 'php::concat({' . $var . '.getClassName(), "::", ' . $this->identifierToStr($expr->name) . '})'; + } else { + $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; + } + $placeHolder = $fn; + } elseif ($this->isNameExpr($expr->class) and $class === 'static') { + $methodPtr = $this->identifierToStr($expr->name, literal: true); + $fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')'; + $this->context->beforeStmtLines[] = $this->formatCppLineComment( + 'Static Method Call: ', + 'static::' . $this->parseIdentifier($expr->name) . '()' + ); + $placeHolder = $this->genArray([Symbol::getCalledClass(), $methodPtr]); + } elseif ($this->isNameExpr($expr->class)) { + if ($class === 'self') { + $class = $this->class; + $self = true; + } elseif ($class === 'std') { + return $this->parseStdCall($expr); + } + $class = $this->getNamespacedClassName($class); + + _do_call: + $method = $this->parseIdentifier($expr->name); + $dynamicCall = false; + $this->context->beforeStmtLines[] = $this->formatCppLineComment( + 'Static Method Call: ', + $class . '::' . $method . '()' + ); + + if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { + $callScope = [$this->genCharPtr($class, true), $this->genCharPtr($method)]; + } + + if ($callScope) { + $nativeFunc = $this->getNativeMethod($expr, $class, $method); + // 存在 Native 类,但是没有找到方法,可能是动态调用 + if (!$nativeFunc and $this->hasClass($class) and $this->getNativeMethod($expr, $class, '__callStatic', false)) { + $dynamicCall = true; + } + + if ($nativeFunc) { + try { + if ($this->shouldUseDynamicCallForNativeArgs($nativeFunc, $expr->args)) { + return $this->genRuntimeFunctionCall( + $this->getClassEntryPtr($class) . ', ' . $this->getFuncPtr($class . '::' . $method), + $expr->args, + $method, + $class + ); + } + $args = $this->parseNativeCallArgs($expr->args, $nativeFunc); + $expr->setAttribute('nativeCall', $nativeFunc); + } catch (PlaceHolder) { + return $this->genPlaceHolder($this->genArray($callScope)); + } + // 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针 + if ($this->methodDef and $self) { + $object = 'this_'; + } else { + $object = $this->getCeWrapper($class); + } + if ($args) { + return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')'; + } else { + return self::PREFIX . $nativeFunc . '(' . $object . ')'; + } + } + } + + if ($dynamicCall) { + $fn = $this->getLiteralString($class . '::' . $method); + } else { + $ce = $this->getClassEntryPtr($class); + $fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method); + } + $placeHolder = $this->genArray($callScope); + } else { + $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; + $placeHolder = $fn; + } + + $call = 'php::call'; + if (empty($expr->args)) { + return $call . '(' . $fn . ')'; + } + try { + return $this->genRuntimeFunctionCall($fn, $expr->args); + } catch (PlaceHolder) { + return $this->genPlaceHolder($placeHolder); + } + } + +}