fix(compiler): resolve reference args through interfaces

pull/15/head
韩天峰 2 months ago
parent 94b61d58f8
commit 20add2008a
  1. 29
      phpunit/code/abstract_method_byref_interface_typed.php
  2. 6
      phpunit/src/InheritanceErrorTest.php
  3. 97
      src/CompilerBase.php
  4. 34
      tests/aot/class/interface-method-byref-typed.phpt

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

@ -231,6 +231,12 @@ class InheritanceErrorTest extends TestCase
$this->assertCompiles('abstract_method_byref_interface.php'); $this->assertCompiles('abstract_method_byref_interface.php');
} }
public function testInterfaceTypedReceiverWithReferenceParameter()
{
// 接口类型接收者必须从接口及其父接口解析按引用参数签名。
$this->assertCompiles('abstract_method_byref_interface_typed.php');
}
public function testAbstractMethodWithReferenceParameterMultilevel() public function testAbstractMethodWithReferenceParameterMultilevel()
{ {
// 多级继承下,沿父类链查找抽象方法的按引用参数签名 // 多级继承下,沿父类链查找抽象方法的按引用参数签名

@ -3666,22 +3666,11 @@ class CompilerBase implements PropertyAccessContext
protected function getAotCallArgInfo(string $funcName, string $className, int $argIndex): ?ArgInfo protected function getAotCallArgInfo(string $funcName, string $className, int $argIndex): ?ArgInfo
{ {
if ($className !== '') { if ($className !== '') {
if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { $functionDef = $this->findAotMethodFunctionDef($className, $funcName);
return null; if ($functionDef === 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; return null;
} }
$classDef = $this->getClass($classDef->extends); return $this->getArgInfoByIndex($functionDef, $argIndex);
}
} }
if (!$this->hasFunction($funcName)) { if (!$this->hasFunction($funcName)) {
@ -3694,42 +3683,87 @@ class CompilerBase implements PropertyAccessContext
{ {
$functionDef = null; $functionDef = null;
if ($className !== '') { if ($className !== '') {
if ($className === self::DYNAMIC_CALLED_CLASS || !$this->hasClass($className)) { $functionDef = $this->findAotMethodFunctionDef($className, $funcName);
} elseif ($this->hasFunction($funcName)) {
$functionDef = $this->getFunction($funcName);
}
if ($functionDef === null) {
return null; return null;
} }
$variadicArgInfo = null;
foreach ($functionDef->argInfoList as $argInfo) {
if ($argInfo->variadic) {
$variadicArgInfo = $argInfo;
}
if (($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) === $argName) {
return $argInfo;
}
}
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); $classDef = $this->getClass($className);
while (true) { while (true) {
if ($classDef->hasMethod($funcName)) { if ($classDef->hasMethod($funcName)) {
$functionDef = $classDef->getMethod($funcName)->functionDef; return $classDef->getMethod($funcName)->functionDef;
break;
} }
if ($classDef->hasAbstractMethod($funcName)) { if ($classDef->hasAbstractMethod($funcName)) {
$functionDef = $classDef->getAbstractMethod($funcName)->functionDef; return $classDef->getAbstractMethod($funcName)->functionDef;
break; }
foreach ($classDef->implements as $interface) {
$functionDef = $this->findAotInterfaceMethodFunctionDef($interface, $funcName);
if ($functionDef !== null) {
return $functionDef;
}
} }
if (!$classDef->extends || !$this->hasClass($classDef->extends)) { if (!$classDef->extends || !$this->hasClass($classDef->extends)) {
return null; return null;
} }
$classDef = $this->getClass($classDef->extends); $classDef = $this->getClass($classDef->extends);
} }
} elseif ($this->hasFunction($funcName)) {
$functionDef = $this->getFunction($funcName);
} }
if ($functionDef === null) { /** Resolve a method from an interface or one of its parent interfaces. */
return null; 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;
$variadicArgInfo = null; $interfaceDef = $this->getInterface($current);
foreach ($functionDef->argInfoList as $argInfo) { if ($interfaceDef->hasMethod($funcName)) {
if ($argInfo->variadic) { return $interfaceDef->methods[strtolower($funcName)]->functionDef;
$variadicArgInfo = $argInfo;
} }
if (($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) === $argName) { foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parent) {
return $argInfo; $pending[] = $parent;
} }
} }
return $variadicArgInfo;
return null;
} }
protected function getArgInfoByIndex(FunctionDef $functionDef, int $argIndex): ?ArgInfo protected function getArgInfoByIndex(FunctionDef $functionDef, int $argIndex): ?ArgInfo
@ -6478,6 +6512,9 @@ class CompilerBase implements PropertyAccessContext
} elseif ($object === 'this_') { } elseif ($object === 'this_') {
// $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名 // $this 在构造函数/方法中静态类型为当前类,便于解析抽象方法等按引用参数签名
$class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class; $class = $this->classDef !== null ? $this->classDef->getNamespacedName(false) : $this->class;
} else {
// 接口和抽象类类型的变量没有具体对象类型,仍可从声明签名解析按引用参数。
$class = $this->getDeclaredObjectType($object);
} }
} }

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