From 3f75d8ac0c43650a77b0774810cb79802db016aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 27 Apr 2022 14:02:37 +0200 Subject: [PATCH] Fix first-class callable internal error --- src/Analyser/NodeScopeResolver.php | 4 +- src/Node/ClassStatementsGatherer.php | 5 +++ src/Node/FunctionCallableNode.php | 12 +++--- src/Node/InstantiationCallableNode.php | 12 +++--- .../Analyser/AnalyserIntegrationTest.php | 11 ++++++ tests/PHPStan/Analyser/data/bug-7135.php | 38 +++++++++++++++++++ 6 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-7135.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 9a966f8630..6575f9af57 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1665,13 +1665,13 @@ private function processExprNode(Expr $expr, MutatingScope $scope, callable $nod { if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { if ($expr instanceof FuncCall) { - $newExpr = new FunctionCallableNode($expr->name, $expr->getAttributes()); + $newExpr = new FunctionCallableNode($expr->name, $expr); } elseif ($expr instanceof MethodCall) { $newExpr = new MethodCallableNode($expr->var, $expr->name, $expr); } elseif ($expr instanceof StaticCall) { $newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr); } elseif ($expr instanceof New_ && !$expr->class instanceof Class_) { - $newExpr = new InstantiationCallableNode($expr->class, $expr->getAttributes()); + $newExpr = new InstantiationCallableNode($expr->class, $expr); } else { throw new ShouldNotHappenException(); } diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 80f5a3e3d6..6b8d1f5587 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -175,6 +175,11 @@ private function gatherNodes(Node $node, Scope $scope): void if ($node instanceof Node\Scalar\EncapsedStringPart) { return; } + if ($node instanceof FunctionCallableNode) { + $node = $node->getOriginalNode(); + } elseif ($node instanceof InstantiationCallableNode) { + $node = $node->getOriginalNode(); + } $inAssign = $scope->isInExpressionAssign($node); if ($inAssign) { diff --git a/src/Node/FunctionCallableNode.php b/src/Node/FunctionCallableNode.php index 4b2c5589d9..d43e0228ce 100644 --- a/src/Node/FunctionCallableNode.php +++ b/src/Node/FunctionCallableNode.php @@ -9,12 +9,9 @@ class FunctionCallableNode extends Expr implements VirtualNode { - /** - * @param mixed[] $attributes - */ - public function __construct(private Name|Expr $name, array $attributes = []) + public function __construct(private Name|Expr $name, private Expr\FuncCall $originalNode) { - parent::__construct($attributes); + parent::__construct($this->originalNode->getAttributes()); } /** @@ -25,6 +22,11 @@ public function getName() return $this->name; } + public function getOriginalNode(): Expr\FuncCall + { + return $this->originalNode; + } + public function getType(): string { return 'PHPStan_Node_FunctionCallableNode'; diff --git a/src/Node/InstantiationCallableNode.php b/src/Node/InstantiationCallableNode.php index acf9a44bf9..382431f70a 100644 --- a/src/Node/InstantiationCallableNode.php +++ b/src/Node/InstantiationCallableNode.php @@ -9,12 +9,9 @@ class InstantiationCallableNode extends Expr implements VirtualNode { - /** - * @param mixed[] $attributes - */ - public function __construct(private Name|Expr $class, array $attributes = []) + public function __construct(private Name|Expr $class, private Expr\New_ $originalNode) { - parent::__construct($attributes); + parent::__construct($this->originalNode->getAttributes()); } /** @@ -25,6 +22,11 @@ public function getClass() return $this->class; } + public function getOriginalNode(): Expr\New_ + { + return $this->originalNode; + } + public function getType(): string { return 'PHPStan_Node_InstantiationCallableNode'; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 037448da0a..f9da9db463 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -757,6 +757,17 @@ public function testBug3853(): void $this->assertNoErrors($errors); } + public function testBug7135(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $errors = $this->runAnalyse(__DIR__ . '/data/bug-7135.php'); + $this->assertCount(1, $errors); + $this->assertSame('Cannot create callable from the new operator.', $errors[0]->getMessage()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-7135.php b/tests/PHPStan/Analyser/data/bug-7135.php new file mode 100644 index 0000000000..aaf98ba1d4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7135.php @@ -0,0 +1,38 @@ += 8.1 + +namespace Bug7135; + +class HelloWorld +{ + private \Closure $closure; + public function sayHello(callable $callable): void + { + $this->closure = $callable(...); + } + public function sayHello2(callable $callable): void + { + $this->closure = $this->sayHello(...); + } + public function sayHello3(callable $callable): void + { + $this->closure = strlen(...); + } + public function sayHello4(callable $callable): void + { + $this->closure = new HelloWorld(...); + } + public function sayHello5(callable $callable): void + { + $this->closure = self::doFoo(...); + } + + public static function doFoo(): void + { + + } + + public function getClosure(): \Closure + { + return $this->closure; + } +}