fix(compiler): 支持返回引用转发引用调用

pull/17/head
Yurun 1 month ago
parent da93efcb69
commit fe99467ea4
  1. 55
      src/CompilerBase.php
  2. 34
      tests/compiler/ref/function-return-reference-chain.phpt
  3. 33
      tests/compiler/ref/method-return-reference-chain.phpt
  4. 33
      tests/compiler/ref/static-return-reference-chain.phpt

@ -1801,12 +1801,67 @@ class CompilerBase implements PropertyAccessContext
$this->fatalError($arg, 'Only string literals or `ClassName::class` constant are supported');
}
/**
* Returns true when the expression is a function/method/static call that
* itself returns by reference, so its result can be forwarded directly
* from a `return by reference` context. In PHP, `function &f() { return g(); }`
* is valid as long as `g()` also returns by reference.
*/
protected function isRefReturningCall(Node $expr): bool
{
if ($expr instanceof Expr\FuncCall && ($this->isNameExpr($expr->name) || $this->isFullNameExpr($expr->name))) {
$name = $this->parseIdentifier($expr->name);
$function = $this->findNativeFunction($name);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
$reflection = \TypePhp\Resolver\Reflection::getFunction(ltrim($this->getNamespacedFuncName($name), '\\'));
return $reflection !== null && $reflection->isInternal() && $reflection->returnsReference();
}
if ($expr instanceof Expr\MethodCall && $this->isNamedMethod($expr->name) && $this->isVarExpr($expr->var)) {
$object = $this->parseIdentifier($expr->var);
$method = $this->parseIdentifier($expr->name);
$function = $this->findNativeMethod($expr, $object, $method);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
return false;
}
if ($expr instanceof Expr\StaticCall && ($this->isNameExpr($expr->class) || $this->isFullNameExpr($expr->class)) && $this->isIdExpr($expr->name)) {
$class = $this->parseIdentifier($expr->class);
if ($class === 'self') {
$class = $this->getFullClassName();
} elseif ($class === 'parent') {
if (!$this->classDef || !$this->classDef->extends) {
return false;
}
$class = $this->classDef->extends;
} elseif ($class !== 'static') {
$class = $this->getNamespacedClassName($class);
}
if ($class === 'static') {
return false;
}
$method = $this->parseIdentifier($expr->name);
$function = $this->getNativeMethod($expr, $class, $method);
if ($function !== false) {
return $this->getFunction($function)->returnsByRef;
}
return false;
}
return false;
}
protected function parseReturn(Node\Stmt\Return_ $v): string
{
if ($this->functionDef->returnsByRef) {
if ($v->expr === null) {
return 'return ' . Type::REF . '{};';
}
// Forwarding a call that itself returns by reference is valid PHP.
if ($this->isRefReturningCall($v->expr)) {
return 'return ' . $this->parseExpr($v->expr) . ';';
}
if (!$this->isVarExpr($v->expr)
&& !$this->isPropertyFetch($v->expr)
&& !$this->isStaticPropertyFetch($v->expr)

@ -0,0 +1,34 @@
--TEST--
Function returning by reference can forward another by-reference call
--FILE--
<?php
function main()
{
$value1 = test1();
var_dump($value1);
$value2 = &test1();
var_dump($value2);
$value2 = 0;
$value3 = test1();
var_dump($value3);
}
function &test1()
{
return test2();
}
function &test2()
{
global $value;
$value++;
return $value;
}
// main();
?>
--EXPECT--
int(1)
int(2)
int(1)

@ -0,0 +1,33 @@
--TEST--
Method returning by reference can forward another by-reference method call
--FILE--
<?php
class Test
{
private $value = 1;
public function &getValue()
{
return $this->value;
}
public function &getRefValue()
{
return $this->getValue();
}
}
function main()
{
$test = new Test;
var_dump($test->getRefValue());
$ref = &$test->getRefValue();
$ref = 2;
var_dump($test->getValue());
}
// main();
?>
--EXPECT--
int(1)
int(2)

@ -0,0 +1,33 @@
--TEST--
Static method returning by reference can forward another by-reference static call
--FILE--
<?php
class Counter
{
private static $value = 0;
public static function &next()
{
self::$value++;
return self::$value;
}
public static function &current()
{
return self::next();
}
}
function main()
{
var_dump(Counter::current());
$ref = &Counter::current();
$ref = 10;
var_dump(Counter::next());
}
// main();
?>
--EXPECT--
int(1)
int(11)
Loading…
Cancel
Save