diff --git a/phpunit/code/abstract_method_byref_interface_typed.php b/phpunit/code/abstract_method_byref_interface_typed.php new file mode 100644 index 00000000..69395d59 --- /dev/null +++ b/phpunit/code/abstract_method_byref_interface_typed.php @@ -0,0 +1,29 @@ +setValue(value: $value); + var_dump($value); +} + +function main(): void +{ + invokeByRefInterface(new ByRefInterfaceReceiver()); +} diff --git a/phpunit/src/InheritanceErrorTest.php b/phpunit/src/InheritanceErrorTest.php index a9735865..6c537f0c 100644 --- a/phpunit/src/InheritanceErrorTest.php +++ b/phpunit/src/InheritanceErrorTest.php @@ -231,6 +231,12 @@ class InheritanceErrorTest extends TestCase $this->assertCompiles('abstract_method_byref_interface.php'); } + public function testInterfaceTypedReceiverWithReferenceParameter() + { + // 接口类型接收者必须从接口及其父接口解析按引用参数签名。 + $this->assertCompiles('abstract_method_byref_interface_typed.php'); + } + public function testAbstractMethodWithReferenceParameterMultilevel() { // 多级继承下,沿父类链查找抽象方法的按引用参数签名 diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 37636eb2..664ba835 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3666,22 +3666,11 @@ class CompilerBase implements PropertyAccessContext protected function getAotCallArgInfo(string $funcName, string $className, int $argIndex): ?ArgInfo { if ($className !== '') { - if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { + $functionDef = $this->findAotMethodFunctionDef($className, $funcName); + if ($functionDef === null) { return null; } - $classDef = $this->getClass($className); - while (true) { - if ($classDef->hasMethod($funcName)) { - return $this->getArgInfoByIndex($classDef->getMethod($funcName)->functionDef, $argIndex); - } - if ($classDef->hasAbstractMethod($funcName)) { - return $this->getArgInfoByIndex($classDef->getAbstractMethod($funcName)->functionDef, $argIndex); - } - if (!$classDef->extends || !$this->hasClass($classDef->extends)) { - return null; - } - $classDef = $this->getClass($classDef->extends); - } + return $this->getArgInfoByIndex($functionDef, $argIndex); } if (!$this->hasFunction($funcName)) { @@ -3694,24 +3683,7 @@ class CompilerBase implements PropertyAccessContext { $functionDef = null; if ($className !== '') { - if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { - return null; - } - $classDef = $this->getClass($className); - while (true) { - if ($classDef->hasMethod($funcName)) { - $functionDef = $classDef->getMethod($funcName)->functionDef; - break; - } - if ($classDef->hasAbstractMethod($funcName)) { - $functionDef = $classDef->getAbstractMethod($funcName)->functionDef; - break; - } - if (!$classDef->extends || !$this->hasClass($classDef->extends)) { - return null; - } - $classDef = $this->getClass($classDef->extends); - } + $functionDef = $this->findAotMethodFunctionDef($className, $funcName); } elseif ($this->hasFunction($funcName)) { $functionDef = $this->getFunction($funcName); } @@ -3732,6 +3704,68 @@ class CompilerBase implements PropertyAccessContext return $variadicArgInfo; } + /** Resolve a project class or interface method declaration for AOT call arguments. */ + protected function findAotMethodFunctionDef(string $className, string $funcName): ?FunctionDef + { + if ($className === self::DYNAMIC_CALLED_CLASS) { + return null; + } + + if ($this->hasInterface($className)) { + return $this->findAotInterfaceMethodFunctionDef($className, $funcName); + } + + if (!$this->hasClass($className)) { + return null; + } + + $classDef = $this->getClass($className); + while (true) { + if ($classDef->hasMethod($funcName)) { + return $classDef->getMethod($funcName)->functionDef; + } + if ($classDef->hasAbstractMethod($funcName)) { + return $classDef->getAbstractMethod($funcName)->functionDef; + } + foreach ($classDef->implements as $interface) { + $functionDef = $this->findAotInterfaceMethodFunctionDef($interface, $funcName); + if ($functionDef !== null) { + return $functionDef; + } + } + if (!$classDef->extends || !$this->hasClass($classDef->extends)) { + return null; + } + $classDef = $this->getClass($classDef->extends); + } + } + + /** Resolve a method from an interface or one of its parent interfaces. */ + protected function findAotInterfaceMethodFunctionDef(string $interfaceName, string $funcName): ?FunctionDef + { + $pending = [$interfaceName]; + $visited = []; + + while ($pending) { + $current = array_pop($pending); + $key = strtolower($current); + if (isset($visited[$key]) || !$this->hasInterface($current)) { + continue; + } + $visited[$key] = true; + + $interfaceDef = $this->getInterface($current); + if ($interfaceDef->hasMethod($funcName)) { + return $interfaceDef->methods[strtolower($funcName)]->functionDef; + } + foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parent) { + $pending[] = $parent; + } + } + + return null; + } + protected function getArgInfoByIndex(FunctionDef $functionDef, int $argIndex): ?ArgInfo { if (array_key_exists($argIndex, $functionDef->argInfoList)) { @@ -6478,6 +6512,9 @@ class CompilerBase implements PropertyAccessContext } elseif ($object === 'this_') { // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; + } else { + // 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。 + $class = $this->getDeclaredObjectType($object); } } diff --git a/tests/aot/class/interface-method-byref-typed.phpt b/tests/aot/class/interface-method-byref-typed.phpt new file mode 100644 index 00000000..7d02048c --- /dev/null +++ b/tests/aot/class/interface-method-byref-typed.phpt @@ -0,0 +1,34 @@ +--TEST-- +interface-typed receiver resolves inherited reference parameter signature +--FILE-- +setValue(value: $value); + var_dump($value); +} + +function main(): void +{ + invokeByRefInterface(new ByRefInterfaceReceiver()); +} +?> +--EXPECT-- +int(42)