From 5ad91d26e2f411505e932ecdc5a412d797c2b817 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 1 Feb 2021 11:26:28 +0100 Subject: [PATCH] Do not complain about interface and abstract class when instantiating from object --- src/Rules/Classes/InstantiationRule.php | 31 ++++++++++++------- .../Rules/Classes/InstantiationRuleTest.php | 19 ++++++++++++ tests/PHPStan/Rules/Classes/data/bug-4471.php | 28 +++++++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-4471.php diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index bb12d889ed..17b4413331 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -48,8 +48,8 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { $errors = []; - foreach ($this->getClassNames($node, $scope) as $class) { - $errors = array_merge($errors, $this->checkClassName($class, $node, $scope)); + foreach ($this->getClassNames($node, $scope) as [$class, $isName]) { + $errors = array_merge($errors, $this->checkClassName($class, $isName, $node, $scope)); } return $errors; } @@ -60,7 +60,7 @@ public function processNode(Node $node, Scope $scope): array * @param Scope $scope * @return RuleError[] */ - private function checkClassName(string $class, Node $node, Scope $scope): array + private function checkClassName(string $class, bool $isName, Node $node, Scope $scope): array { $lowercasedClass = strtolower($class); $messages = []; @@ -131,7 +131,7 @@ private function checkClassName(string $class, Node $node, Scope $scope): array $classReflection = $this->reflectionProvider->getClass($class); } - if (!$isStatic && $classReflection->isInterface()) { + if (!$isStatic && $classReflection->isInterface() && $isName) { return [ RuleErrorBuilder::message( sprintf('Cannot instantiate interface %s.', $classReflection->getDisplayName()) @@ -139,7 +139,7 @@ private function checkClassName(string $class, Node $node, Scope $scope): array ]; } - if (!$isStatic && $classReflection->isAbstract()) { + if (!$isStatic && $classReflection->isAbstract() && $isName) { return [ RuleErrorBuilder::message( sprintf('Instantiated class %s is abstract.', $classReflection->getDisplayName()) @@ -147,6 +147,10 @@ private function checkClassName(string $class, Node $node, Scope $scope): array ]; } + if (!$isName) { + return []; + } + if (!$classReflection->hasConstructor()) { if (count($node->args) > 0) { return array_merge($messages, [ @@ -200,12 +204,12 @@ private function checkClassName(string $class, Node $node, Scope $scope): array /** * @param \PhpParser\Node\Expr\New_ $node $node * @param Scope $scope - * @return string[] + * @return array */ private function getClassNames(Node $node, Scope $scope): array { if ($node->class instanceof \PhpParser\Node\Name) { - return [(string) $node->class]; + return [[(string) $node->class, true]]; } if ($node->class instanceof Node\Stmt\Class_) { @@ -214,19 +218,24 @@ private function getClassNames(Node $node, Scope $scope): array throw new \PHPStan\ShouldNotHappenException(); } - return [$anonymousClassType->getClassName()]; + return [[$anonymousClassType->getClassName(), true]]; } $type = $scope->getType($node->class); return array_merge( array_map( - static function (ConstantStringType $type): string { - return $type->getValue(); + static function (ConstantStringType $type): array { + return [$type->getValue(), true]; }, TypeUtils::getConstantStrings($type) ), - TypeUtils::getDirectClassNames($type) + array_map( + static function (string $name): array { + return [$name, false]; + }, + TypeUtils::getDirectClassNames($type) + ) ); } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 62bf98915b..5b6a2eb059 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -312,4 +312,23 @@ public function testNamedArguments(): void ]); } + public function testBug4471(): void + { + $this->analyse([__DIR__ . '/data/bug-4471.php'], [ + [ + 'Instantiated class Bug4471\Baz not found.', + 19, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Instantiated class Bug4471\Foo is abstract.', + 24, + ], + [ + 'Cannot instantiate interface Bug4471\Bar.', + 27, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-4471.php b/tests/PHPStan/Rules/Classes/data/bug-4471.php new file mode 100644 index 0000000000..46818e2ad7 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-4471.php @@ -0,0 +1,28 @@ +