From a45fb2a6212467f646a56a85b4118e3bd5430772 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sun, 12 Sep 2021 22:29:08 +0200 Subject: [PATCH] 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. --- CHANGELOG.md | 5 ++- lib/PhpParser/Node/Expr/CallLike.php | 39 +++++++++++++++++++ lib/PhpParser/Node/Expr/FuncCall.php | 14 ++++--- lib/PhpParser/Node/Expr/MethodCall.php | 17 +++++--- lib/PhpParser/Node/Expr/New_.php | 12 ++++-- lib/PhpParser/Node/Expr/StaticCall.php | 18 ++++++--- test/PhpParser/Node/Expr/CallableLikeTest.php | 36 +++++++++++++++++ 7 files changed, 120 insertions(+), 21 deletions(-) create mode 100644 lib/PhpParser/Node/Expr/CallLike.php create mode 100644 test/PhpParser/Node/Expr/CallableLikeTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d585e..c7c07d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. Version 4.12.0 (2021-07-21) --------------------------- diff --git a/lib/PhpParser/Node/Expr/CallLike.php b/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 0000000..78e1cf3 --- /dev/null +++ b/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,39 @@ + + */ + 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(); + } +} \ No newline at end of file diff --git a/lib/PhpParser/Node/Expr/FuncCall.php b/lib/PhpParser/Node/Expr/FuncCall.php index 1e8afa5..2de4d0d 100644 --- a/lib/PhpParser/Node/Expr/FuncCall.php +++ b/lib/PhpParser/Node/Expr/FuncCall.php @@ -5,19 +5,19 @@ 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 Arguments */ public $args; /** * Constructs a function call node. * - * @param Node\Name|Expr $name Function name - * @param Node\Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct($name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -32,4 +32,8 @@ class FuncCall extends Expr public function getType() : string { return 'Expr_FuncCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/PhpParser/Node/Expr/MethodCall.php b/lib/PhpParser/Node/Expr/MethodCall.php index bd81bb4..49ca483 100644 --- a/lib/PhpParser/Node/Expr/MethodCall.php +++ b/lib/PhpParser/Node/Expr/MethodCall.php @@ -5,23 +5,24 @@ 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 Arguments */ public $args; /** * Constructs a function call node. * - * @param Expr $var Variable holding object - * @param string|Identifier|Expr $name Method name - * @param Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -37,4 +38,8 @@ class MethodCall extends Expr public function getType() : string { return 'Expr_MethodCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/PhpParser/Node/Expr/New_.php b/lib/PhpParser/Node/Expr/New_.php index c86f0c6..e2bb649 100644 --- a/lib/PhpParser/Node/Expr/New_.php +++ b/lib/PhpParser/Node/Expr/New_.php @@ -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 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 $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; + } } diff --git a/lib/PhpParser/Node/Expr/StaticCall.php b/lib/PhpParser/Node/Expr/StaticCall.php index 9883f5a..d0d099c 100644 --- a/lib/PhpParser/Node/Expr/StaticCall.php +++ b/lib/PhpParser/Node/Expr/StaticCall.php @@ -3,25 +3,27 @@ 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 Arguments */ public $args; /** * Constructs a static method call node. * - * @param Node\Name|Expr $class Class name - * @param string|Identifier|Expr $name Method name - * @param Node\Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct($class, $name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -37,4 +39,8 @@ class StaticCall extends Expr public function getType() : string { return 'Expr_StaticCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/test/PhpParser/Node/Expr/CallableLikeTest.php b/test/PhpParser/Node/Expr/CallableLikeTest.php new file mode 100644 index 0000000..0dfc19a --- /dev/null +++ b/test/PhpParser/Node/Expr/CallableLikeTest.php @@ -0,0 +1,36 @@ +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], + ]; + } +} \ No newline at end of file