diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 75b21849..6a18f004 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -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) diff --git a/tests/compiler/ref/function-return-reference-chain.phpt b/tests/compiler/ref/function-return-reference-chain.phpt new file mode 100644 index 00000000..edf4464f --- /dev/null +++ b/tests/compiler/ref/function-return-reference-chain.phpt @@ -0,0 +1,34 @@ +--TEST-- +Function returning by reference can forward another by-reference call +--FILE-- + +--EXPECT-- +int(1) +int(2) +int(1) diff --git a/tests/compiler/ref/method-return-reference-chain.phpt b/tests/compiler/ref/method-return-reference-chain.phpt new file mode 100644 index 00000000..36285141 --- /dev/null +++ b/tests/compiler/ref/method-return-reference-chain.phpt @@ -0,0 +1,33 @@ +--TEST-- +Method returning by reference can forward another by-reference method call +--FILE-- +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) diff --git a/tests/compiler/ref/static-return-reference-chain.phpt b/tests/compiler/ref/static-return-reference-chain.phpt new file mode 100644 index 00000000..749be438 --- /dev/null +++ b/tests/compiler/ref/static-return-reference-chain.phpt @@ -0,0 +1,33 @@ +--TEST-- +Static method returning by reference can forward another by-reference static call +--FILE-- + +--EXPECT-- +int(1) +int(11)