From 94b61d58f8b5ef8ebc8848c606e17c965e016820 Mon Sep 17 00:00:00 2001 From: Yurun Date: Thu, 9 Jul 2026 19:28:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(compiler):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1/=E6=8E=A5=E5=8F=A3=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E6=8C=89=E5=BC=95=E7=94=A8=E5=8F=82=E6=95=B0=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit/code/abstract_method_byref.php | 24 ++++++++++ .../code/abstract_method_byref_interface.php | 22 ++++++++++ .../code/abstract_method_byref_multilevel.php | 30 +++++++++++++ .../code/abstract_method_byref_namespace.php | 34 ++++++++++++++ phpunit/src/InheritanceErrorTest.php | 24 ++++++++++ src/CompilerBase.php | 10 +++++ .../abstract-method-byref-defined-var.phpt | 33 ++++++++++++++ .../abstract-method-byref-interface.phpt | 29 ++++++++++++ .../abstract-method-byref-multi-param.phpt | 36 +++++++++++++++ .../abstract-method-byref-multilevel.phpt | 43 ++++++++++++++++++ .../abstract-method-byref-namespace.phpt | 44 +++++++++++++++++++ tests/aot/class/abstract-method-byref.phpt | 31 +++++++++++++ 12 files changed, 360 insertions(+) create mode 100644 phpunit/code/abstract_method_byref.php create mode 100644 phpunit/code/abstract_method_byref_interface.php create mode 100644 phpunit/code/abstract_method_byref_multilevel.php create mode 100644 phpunit/code/abstract_method_byref_namespace.php create mode 100644 tests/aot/class/abstract-method-byref-defined-var.phpt create mode 100644 tests/aot/class/abstract-method-byref-interface.phpt create mode 100644 tests/aot/class/abstract-method-byref-multi-param.phpt create mode 100644 tests/aot/class/abstract-method-byref-multilevel.phpt create mode 100644 tests/aot/class/abstract-method-byref-namespace.phpt create mode 100644 tests/aot/class/abstract-method-byref.phpt diff --git a/phpunit/code/abstract_method_byref.php b/phpunit/code/abstract_method_byref.php new file mode 100644 index 00000000..953ce429 --- /dev/null +++ b/phpunit/code/abstract_method_byref.php @@ -0,0 +1,24 @@ +abc($value); + var_dump($value); + } + + abstract public function abc(&$value); +} + +class AbstractByRefChild extends AbstractByRefBase +{ + public function abc(&$value) + { + $value = 1; + } +} + +function main() +{ + new AbstractByRefChild; +} diff --git a/phpunit/code/abstract_method_byref_interface.php b/phpunit/code/abstract_method_byref_interface.php new file mode 100644 index 00000000..d4bf6445 --- /dev/null +++ b/phpunit/code/abstract_method_byref_interface.php @@ -0,0 +1,22 @@ +abc($v); + var_dump($v); + } +} diff --git a/phpunit/code/abstract_method_byref_multilevel.php b/phpunit/code/abstract_method_byref_multilevel.php new file mode 100644 index 00000000..7be2bbd7 --- /dev/null +++ b/phpunit/code/abstract_method_byref_multilevel.php @@ -0,0 +1,30 @@ +abc($value); + var_dump($value); + } + } + + abstract class ByRefMultilevelMid extends ByRefMultilevelBase + { + } + + class ByRefMultilevelChild extends ByRefMultilevelMid + { + public function abc(&$value) + { + $value = [1, 2]; + } + } + + function main() + { + (new ByRefMultilevelChild)->run(); + } +} diff --git a/phpunit/code/abstract_method_byref_namespace.php b/phpunit/code/abstract_method_byref_namespace.php new file mode 100644 index 00000000..575ddcf2 --- /dev/null +++ b/phpunit/code/abstract_method_byref_namespace.php @@ -0,0 +1,34 @@ +abc($value); + var_dump($value); + } + + abstract public function abc(&$value); + } +} + +namespace B { + use A\AbstractByRefBase; + + class AbstractByRefChild extends AbstractByRefBase + { + public function abc(&$value) + { + $value = [1]; + } + } +} + +namespace { + use B\AbstractByRefChild; + + function main() + { + new AbstractByRefChild; + } +} diff --git a/phpunit/src/InheritanceErrorTest.php b/phpunit/src/InheritanceErrorTest.php index 77d37ea3..a9735865 100644 --- a/phpunit/src/InheritanceErrorTest.php +++ b/phpunit/src/InheritanceErrorTest.php @@ -213,6 +213,30 @@ class InheritanceErrorTest extends TestCase $this->assertCompiles('interface_abstract_method_signature.php'); } + public function testAbstractMethodWithReferenceParameter() + { + // 抽象方法的按引用参数签名应被正确识别,基类构造中向未定义变量按引用传参不报错 + $this->assertCompiles('abstract_method_byref.php'); + } + + public function testAbstractMethodWithReferenceParameterAcrossNamespace() + { + // 跨命名空间的抽象方法按引用参数签名应被正确识别(使用完全限定类名解析) + $this->assertCompiles('abstract_method_byref_namespace.php'); + } + + public function testInterfaceMethodWithReferenceParameter() + { + // 接口的按引用方法签名应被正确识别 + $this->assertCompiles('abstract_method_byref_interface.php'); + } + + public function testAbstractMethodWithReferenceParameterMultilevel() + { + // 多级继承下,沿父类链查找抽象方法的按引用参数签名 + $this->assertCompiles('abstract_method_byref_multilevel.php'); + } + public function testAbstractInterfaceMethodSignatureMismatch() { $this->exec('must be compatible', 'interface_abstract_method_mismatch.php'); diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 1be14458..37636eb2 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3674,6 +3674,9 @@ class CompilerBase implements PropertyAccessContext 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; } @@ -3700,6 +3703,10 @@ class CompilerBase implements PropertyAccessContext $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; } @@ -6468,6 +6475,9 @@ class CompilerBase implements PropertyAccessContext } if ($this->isTypedObject($object)) { $class = $this->getObjectType($object); + } elseif ($object === 'this_') { + // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 + $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; } } diff --git a/tests/aot/class/abstract-method-byref-defined-var.phpt b/tests/aot/class/abstract-method-byref-defined-var.phpt new file mode 100644 index 00000000..c753ffda --- /dev/null +++ b/tests/aot/class/abstract-method-byref-defined-var.phpt @@ -0,0 +1,33 @@ +--TEST-- +abstract method with reference parameter, passing an already-defined variable +--FILE-- +abc($v); + var_dump($v); + } + } + + class Test extends Base + { + public function abc(&$value) + { + $value = 42; + } + } + + function main() + { + (new Test)->run(); + } +} +?> +--EXPECT-- +int(42) diff --git a/tests/aot/class/abstract-method-byref-interface.phpt b/tests/aot/class/abstract-method-byref-interface.phpt new file mode 100644 index 00000000..ad91b40a --- /dev/null +++ b/tests/aot/class/abstract-method-byref-interface.phpt @@ -0,0 +1,29 @@ +--TEST-- +interface method declared with reference parameter, implemented by a class +--FILE-- +abc($v); + var_dump($v); + } +} +?> +--EXPECT-- +string(1) "x" diff --git a/tests/aot/class/abstract-method-byref-multi-param.phpt b/tests/aot/class/abstract-method-byref-multi-param.phpt new file mode 100644 index 00000000..9023cc2d --- /dev/null +++ b/tests/aot/class/abstract-method-byref-multi-param.phpt @@ -0,0 +1,36 @@ +--TEST-- +abstract method with two reference parameters, one defined and one undefined +--FILE-- +abc($x, $y); + var_dump($x, $y); + } + } + + class Test extends Base + { + public function abc(&$a, &$b) + { + $a *= 2; + $b = 'done'; + } + } + + function main() + { + (new Test)->run(); + } +} +?> +--EXPECT-- +int(10) +string(4) "done" diff --git a/tests/aot/class/abstract-method-byref-multilevel.phpt b/tests/aot/class/abstract-method-byref-multilevel.phpt new file mode 100644 index 00000000..060bdb6e --- /dev/null +++ b/tests/aot/class/abstract-method-byref-multilevel.phpt @@ -0,0 +1,43 @@ +--TEST-- +abstract method with reference parameter across multiple levels of inheritance +--FILE-- +abc($value); + var_dump($value); + } + } + + // 中间类继续继承抽象方法,不实现 + abstract class Mid extends Base + { + } + + class Test extends Mid + { + public function abc(&$value) + { + $value = [1, 2]; + } + } + + function main() + { + (new Test)->run(); + } +} +?> +--EXPECT-- +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} diff --git a/tests/aot/class/abstract-method-byref-namespace.phpt b/tests/aot/class/abstract-method-byref-namespace.phpt new file mode 100644 index 00000000..4e9cef2d --- /dev/null +++ b/tests/aot/class/abstract-method-byref-namespace.phpt @@ -0,0 +1,44 @@ +--TEST-- +abstract method with reference parameter across namespaces, called from base constructor with undefined variable +--FILE-- +abc($value); + var_dump($value); + } + + abstract public function abc(&$value); + } +} + +namespace B { + use A\Base; + + class Test extends Base + { + public function abc(&$value) + { + $value = [1]; + } + } +} + +namespace { + use B\Test; + + function main() + { + new Test; + } +} +?> +--EXPECT-- +array(1) { + [0]=> + int(1) +} diff --git a/tests/aot/class/abstract-method-byref.phpt b/tests/aot/class/abstract-method-byref.phpt new file mode 100644 index 00000000..cfb8fac6 --- /dev/null +++ b/tests/aot/class/abstract-method-byref.phpt @@ -0,0 +1,31 @@ +--TEST-- +abstract method with reference parameter called from base constructor with undefined variable +--FILE-- +abc($value); + var_dump($value); + } + + abstract public function abc(&$value); +} + +class Test extends Base +{ + public function abc(&$value) + { + $value = 1; + } +} + +function main() +{ + new Test; +} +?> +--EXPECT-- +int(1) From 20add2008ad3b93a077ee08cb206d6eabe4c6d22 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 10 Jul 2026 12:06:13 +0800 Subject: [PATCH 2/2] fix(compiler): resolve reference args through interfaces --- .../abstract_method_byref_interface_typed.php | 29 +++++ phpunit/src/InheritanceErrorTest.php | 6 ++ src/CompilerBase.php | 101 ++++++++++++------ .../class/interface-method-byref-typed.phpt | 34 ++++++ 4 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 phpunit/code/abstract_method_byref_interface_typed.php create mode 100644 tests/aot/class/interface-method-byref-typed.phpt 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)