fix(compiler): 支持抽象/接口方法的按引用参数签名解析 #15

Merged
韩天峰 merged 2 commits from fix-ref-20260709 into master 2 months ago
  1. 24
      phpunit/code/abstract_method_byref.php
  2. 22
      phpunit/code/abstract_method_byref_interface.php
  3. 29
      phpunit/code/abstract_method_byref_interface_typed.php
  4. 30
      phpunit/code/abstract_method_byref_multilevel.php
  5. 34
      phpunit/code/abstract_method_byref_namespace.php
  6. 30
      phpunit/src/InheritanceErrorTest.php
  7. 97
      src/CompilerBase.php
  8. 33
      tests/aot/class/abstract-method-byref-defined-var.phpt
  9. 29
      tests/aot/class/abstract-method-byref-interface.phpt
  10. 36
      tests/aot/class/abstract-method-byref-multi-param.phpt
  11. 43
      tests/aot/class/abstract-method-byref-multilevel.phpt
  12. 44
      tests/aot/class/abstract-method-byref-namespace.phpt
  13. 31
      tests/aot/class/abstract-method-byref.phpt
  14. 34
      tests/aot/class/interface-method-byref-typed.phpt

@ -0,0 +1,24 @@
<?php
abstract class AbstractByRefBase
{
public function __construct()
{
$this->abc($value);
var_dump($value);
}
abstract public function abc(&$value);
}
class AbstractByRefChild extends AbstractByRefBase
{
public function abc(&$value)
{
$value = 1;
}
}
function main()
{
new AbstractByRefChild;
}

@ -0,0 +1,22 @@
<?php
namespace {
interface IByRef
{
public function abc(&$value);
}
class ByRefInterfaceImpl implements IByRef
{
public function abc(&$value)
{
$value = 'x';
}
}
function main()
{
$t = new ByRefInterfaceImpl;
$t->abc($v);
var_dump($v);
}
}

@ -0,0 +1,29 @@
<?php
interface ParentByRef
{
public function setValue(&$value): void;
}
interface ChildByRef extends ParentByRef
{
}
final class ByRefInterfaceReceiver implements ChildByRef
{
public function setValue(&$value): void
{
$value = 42;
}
}
function invokeByRefInterface(ChildByRef $receiver): void
{
$receiver->setValue(value: $value);
var_dump($value);
}
function main(): void
{
invokeByRefInterface(new ByRefInterfaceReceiver());
}

@ -0,0 +1,30 @@
<?php
namespace {
abstract class ByRefMultilevelBase
{
abstract public function abc(&$value);
public function run()
{
$this->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();
}
}

@ -0,0 +1,34 @@
<?php
namespace A {
abstract class AbstractByRefBase
{
public function __construct()
{
$this->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;
}
}

@ -213,6 +213,36 @@ 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 testInterfaceTypedReceiverWithReferenceParameter()
{
// 接口类型接收者必须从接口及其父接口解析按引用参数签名。
$this->assertCompiles('abstract_method_byref_interface_typed.php');
}
public function testAbstractMethodWithReferenceParameterMultilevel()
{
// 多级继承下,沿父类链查找抽象方法的按引用参数签名
$this->assertCompiles('abstract_method_byref_multilevel.php');
}
public function testAbstractInterfaceMethodSignatureMismatch()
{
$this->exec('must be compatible', 'interface_abstract_method_mismatch.php');

@ -3666,19 +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->extends || !$this->hasClass($classDef->extends)) {
return null;
}
$classDef = $this->getClass($classDef->extends);
}
return $this->getArgInfoByIndex($functionDef, $argIndex);
}
if (!$this->hasFunction($funcName)) {
@ -3691,20 +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->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);
}
@ -3725,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)) {
@ -6468,6 +6509,12 @@ 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;
} else {
// 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。
$class = $this->getDeclaredObjectType($object);
}
}

@ -0,0 +1,33 @@
--TEST--
abstract method with reference parameter, passing an already-defined variable
--FILE--
<?php
namespace {
abstract class Base
{
abstract public function abc(&$value);
public function run()
{
$v = 0;
$this->abc($v);
var_dump($v);
}
}
class Test extends Base
{
public function abc(&$value)
{
$value = 42;
}
}
function main()
{
(new Test)->run();
}
}
?>
--EXPECT--
int(42)

@ -0,0 +1,29 @@
--TEST--
interface method declared with reference parameter, implemented by a class
--FILE--
<?php
namespace {
interface IByRef
{
public function abc(&$value);
}
class Test implements IByRef
{
public function abc(&$value)
{
$value = 'x';
}
}
function main()
{
$t = new Test;
// $v 未定义,按引用传给接口的按引用方法后由实现类赋值
$t->abc($v);
var_dump($v);
}
}
?>
--EXPECT--
string(1) "x"

@ -0,0 +1,36 @@
--TEST--
abstract method with two reference parameters, one defined and one undefined
--FILE--
<?php
namespace {
abstract class Base
{
abstract public function abc(&$a, &$b);
public function run()
{
$x = 5;
// $x 已定义;$y 未定义,按引用传参后由实现类赋值
$this->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"

@ -0,0 +1,43 @@
--TEST--
abstract method with reference parameter across multiple levels of inheritance
--FILE--
<?php
namespace {
abstract class Base
{
abstract public function abc(&$value);
public function run()
{
// $value 在调用前未定义,按引用传给抽象方法后由最终实现类赋值
$this->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)
}

@ -0,0 +1,44 @@
--TEST--
abstract method with reference parameter across namespaces, called from base constructor with undefined variable
--FILE--
<?php
namespace A {
abstract class Base
{
public function __construct()
{
// $value 在调用前未定义,按引用传给抽象方法后由实现类赋值
$this->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)
}

@ -0,0 +1,31 @@
--TEST--
abstract method with reference parameter called from base constructor with undefined variable
--FILE--
<?php
abstract class Base
{
public function __construct()
{
// $value 在调用前未定义,按引用传给抽象方法后由实现类赋值
$this->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)

@ -0,0 +1,34 @@
--TEST--
interface-typed receiver resolves inherited reference parameter signature
--FILE--
<?php
interface ParentByRef
{
public function setValue(&$value): void;
}
interface ChildByRef extends ParentByRef
{
}
final class ByRefInterfaceReceiver implements ChildByRef
{
public function setValue(&$value): void
{
$value = 42;
}
}
function invokeByRefInterface(ChildByRef $receiver): void
{
$receiver->setValue(value: $value);
var_dump($value);
}
function main(): void
{
invokeByRefInterface(new ByRefInterfaceReceiver());
}
?>
--EXPECT--
int(42)
Loading…
Cancel
Save