Skip to content

Commit

Permalink
stub-generator: Handle functions calling func_get_args() (#36870)
Browse files Browse the repository at this point in the history
If a function or method calls `func_get_args()`, Phan treats it as if it
has an implicit varargs argument at the end of its parameter list. But
when we stub the function it can no longer see the `func_get_args()`
call so it tries to incorrect force the declared parameter list.

So let's do the same thing when generating the stubs, if there's a
`func_get_args()` call (and no varargs param already) let's add one.
  • Loading branch information
anomiex authored Apr 12, 2024
1 parent 4ad14fc commit f651e91
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Add a fake `...$func_get_args` parameter to functions/methods that call `func_get_args()`.
30 changes: 30 additions & 0 deletions projects/packages/stub-generator/src/PhpParser/StubNodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@phan-type Definitions = array{constant:'*'|string[],function:'*'|string[],class:ClassDefs,interface:ClassDefs,trait:ClassDefs}
PHAN;

use PhpParser\BuilderFactory;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Concat as BinaryOp_Concat;
use PhpParser\Node\Expr\FuncCall;
Expand All @@ -28,6 +29,7 @@
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\NodeFinder;
use PhpParser\NodeVisitorAbstract;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter_Standard;
use RuntimeException;
Expand Down Expand Up @@ -221,6 +223,7 @@ public function enterNode( Node $node ) {
if ( $this->defs['function'] === '*' || in_array( $node->namespacedName->toString(), $this->defs['function'], true ) ) {
// Ignore anything inside the function.
if ( $node->stmts ) {
$this->mutateForFuncGetArgs( $node );
$node->stmts = array();
}
$this->debug( "Keeping function {$node->namespacedName}" );
Expand Down Expand Up @@ -287,6 +290,7 @@ public function enterNode( Node $node ) {
} elseif ( $defs === '*' || in_array( $node->name->toString(), $defs, true ) ) {
// Ignore anything inside the method.
if ( $node->stmts ) {
$this->mutateForFuncGetArgs( $node );
$node->stmts = array();
}
$this->debug( "Keeping method {$node->name}" );
Expand Down Expand Up @@ -376,4 +380,30 @@ public function leaveNode( Node $node ) {

}
}

/**
* Mutate a function/method's signature if it uses `func_get_args()`.
*
* @param Function_|ClassMethod $node Node.
*/
private function mutateForFuncGetArgs( Node $node ): void {
// First, see if the function already uses varargs.
foreach ( $node->getParams() as $param ) {
if ( $param->variadic ) {
return;
}
}

// See if the function contains a call to `func_get_args()`.
$call = ( new NodeFinder() )->findFirst(
$node->stmts,
function ( Node $n ) {
return $n instanceof FuncCall && $n->name instanceof Name && in_array( $n->name->toString(), array( 'func_get_args', 'func_get_arg', 'func_num_args' ), true );
}
);

if ( $call !== null ) {
$node->params[] = ( new BuilderFactory() )->param( 'func_get_args' )->makeVariadic()->getNode();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,78 @@ public function getBaz(): \Aliased
}
PHP,
),
'Handling of func_get_args()' => array(
<<<'PHP'
namespace Some\NS;
function no_params() {
func_get_args();
}
function no_varargs( $x, $y ) {
func_get_args();
}
function has_varargs( $x, ...$args ) {
func_get_args();
}
class Foo {
public function no_params() {
func_get_args();
}
public function no_varargs( $x, $y ) {
func_get_args();
}
public function has_varargs( $x, ...$args ) {
func_get_args();
}
}
function uses_func_get_arg() {
func_get_arg();
}
function uses_func_num_args() {
func_num_args();
}
PHP,
'*',
<<<'PHP'
namespace Some\NS;
function no_params(...$func_get_args)
{
}
function no_varargs($x, $y, ...$func_get_args)
{
}
function has_varargs($x, ...$args)
{
}
class Foo
{
public function no_params(...$func_get_args)
{
}
public function no_varargs($x, $y, ...$func_get_args)
{
}
public function has_varargs($x, ...$args)
{
}
}
function uses_func_get_arg(...$func_get_args)
{
}
function uses_func_num_args(...$func_get_args)
{
}
PHP,
),
);
}
Expand Down

0 comments on commit f651e91

Please sign in to comment.