Add CallLike parent class

This provides a helper to determine whether a call is a first-class
callable, and a way to strip the args type to Arg[] if it isn't.
This commit is contained in:
Nikita Popov 2021-09-12 22:29:08 +02:00
parent 08501991d4
commit a45fb2a621
7 changed files with 120 additions and 21 deletions

View File

@ -7,7 +7,10 @@ Version 4.12.1-dev
* [PHP 8.1] Added support for explicit octal literals.
* [PHP 8.1] Added support for first-class callables. These are represented using a call whose first
argument is a `VariadicPlaceholder`. The representation is intended to be forward-compatible with
partial function application, just like the PHP feature itself.
partial function application, just like the PHP feature itself. Call nodes now extend from
`Expr\CallLike`, which provides an `isFirstClassCallable()` method to determine whether a
placeholder id present. `getArgs()` can be used to assert that the call is not a first-class
callable and returns `Arg[]` rather than `array<Arg|VariadicPlaceholder>`.
Version 4.12.0 (2021-07-21)
---------------------------

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
abstract class CallLike extends Expr {
/**
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
* callables.
*
* @return array<Arg|VariadicPlaceholder>
*/
abstract public function getRawArgs(): array;
/**
* Returns whether this call expression is actually a first class callable.
*/
public function isFirstClassCallable(): bool {
foreach ($this->getRawArgs() as $arg) {
if ($arg instanceof VariadicPlaceholder) {
return true;
}
}
return false;
}
/**
* Assert that this is not a first-class callable and return only ordinary Args.
*
* @return Arg[]
*/
public function getArgs(): array {
assert(!$this->isFirstClassCallable());
return $this->getRawArgs();
}
}

View File

@ -5,18 +5,18 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class FuncCall extends Expr
class FuncCall extends CallLike
{
/** @var Node\Name|Expr Function name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
@ -32,4 +32,8 @@ class FuncCall extends Expr
public function getType() : string {
return 'Expr_FuncCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -5,14 +5,15 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class MethodCall extends Expr
class MethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
@ -20,7 +21,7 @@ class MethodCall extends Expr
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
@ -37,4 +38,8 @@ class MethodCall extends Expr
public function getType() : string {
return 'Expr_MethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,20 +3,22 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
class New_ extends Expr
class New_ extends CallLike
{
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = [], array $attributes = []) {
@ -32,4 +34,8 @@ class New_ extends Expr
public function getType() : string {
return 'Expr_New';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,16 +3,18 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class StaticCall extends Expr
class StaticCall extends CallLike
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
@ -20,7 +22,7 @@ class StaticCall extends Expr
*
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param Node\Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $args = [], array $attributes = []) {
@ -37,4 +39,8 @@ class StaticCall extends Expr
public function getType() : string {
return 'Expr_StaticCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\VariadicPlaceholder;
class CallableLikeTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestIsFirstClassCallable
*/
public function testIsFirstClassCallable(CallLike $node, bool $isFirstClassCallable) {
$this->assertSame($isFirstClassCallable, $node->isFirstClassCallable());
if (!$isFirstClassCallable) {
$this->assertSame($node->getRawArgs(), $node->getArgs());
}
}
public function provideTestIsFirstClassCallable() {
$normalArgs = [new Arg(new LNumber(1))];
$callableArgs = [new VariadicPlaceholder()];
return [
[new FuncCall(new Name('test'), $normalArgs), false],
[new FuncCall(new Name('test'), $callableArgs), true],
[new MethodCall(new Variable('this'), 'test', $normalArgs), false],
[new MethodCall(new Variable('this'), 'test', $callableArgs), true],
[new StaticCall(new Name('Test'), 'test', $normalArgs), false],
[new StaticCall(new Name('Test'), 'test', $callableArgs), true],
[new New_(new Name('Test'), $normalArgs), false],
// This is not legal code, but accepted by the parser.
[new New_(new Name('Test'), $callableArgs), true],
];
}
}