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

pull/15/head
Yurun 2 months ago
parent 631a1ef6b2
commit 94b61d58f8
  1. 24
      phpunit/code/abstract_method_byref.php
  2. 22
      phpunit/code/abstract_method_byref_interface.php
  3. 30
      phpunit/code/abstract_method_byref_multilevel.php
  4. 34
      phpunit/code/abstract_method_byref_namespace.php
  5. 24
      phpunit/src/InheritanceErrorTest.php
  6. 10
      src/CompilerBase.php
  7. 33
      tests/aot/class/abstract-method-byref-defined-var.phpt
  8. 29
      tests/aot/class/abstract-method-byref-interface.phpt
  9. 36
      tests/aot/class/abstract-method-byref-multi-param.phpt
  10. 43
      tests/aot/class/abstract-method-byref-multilevel.phpt
  11. 44
      tests/aot/class/abstract-method-byref-namespace.phpt
  12. 31
      tests/aot/class/abstract-method-byref.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,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,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');

@ -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;
}
}

@ -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)
Loading…
Cancel
Save