From f651e91306da36a0bdadcaa530efaf4978b30577 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Fri, 12 Apr 2024 04:02:53 -0400 Subject: [PATCH] stub-generator: Handle functions calling `func_get_args()` (#36870) 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. --- .../add-stub-generator-func_get_args | 4 ++ .../src/PhpParser/StubNodeVisitor.php | 30 ++++++++ .../php/PhpParser/StubNodeVisitorTest.php | 72 +++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 projects/packages/stub-generator/changelog/add-stub-generator-func_get_args diff --git a/projects/packages/stub-generator/changelog/add-stub-generator-func_get_args b/projects/packages/stub-generator/changelog/add-stub-generator-func_get_args new file mode 100644 index 0000000000000..c122276465234 --- /dev/null +++ b/projects/packages/stub-generator/changelog/add-stub-generator-func_get_args @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Add a fake `...$func_get_args` parameter to functions/methods that call `func_get_args()`. diff --git a/projects/packages/stub-generator/src/PhpParser/StubNodeVisitor.php b/projects/packages/stub-generator/src/PhpParser/StubNodeVisitor.php index d1b6dabecc6e2..5b4318b9e4eb8 100644 --- a/projects/packages/stub-generator/src/PhpParser/StubNodeVisitor.php +++ b/projects/packages/stub-generator/src/PhpParser/StubNodeVisitor.php @@ -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; @@ -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; @@ -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}" ); @@ -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}" ); @@ -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(); + } + } } diff --git a/projects/packages/stub-generator/tests/php/PhpParser/StubNodeVisitorTest.php b/projects/packages/stub-generator/tests/php/PhpParser/StubNodeVisitorTest.php index 15e024187ee07..fa4bb1fc7b804 100644 --- a/projects/packages/stub-generator/tests/php/PhpParser/StubNodeVisitorTest.php +++ b/projects/packages/stub-generator/tests/php/PhpParser/StubNodeVisitorTest.php @@ -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, + ), ); }