diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 7dd65488a0..4c0a424c1b 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\New_; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; +use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; @@ -17,6 +18,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; +use function array_filter; use function array_map; use function array_merge; use function count; @@ -245,6 +247,20 @@ private function getClassNames(Node $node, Scope $scope): array $type = $scope->getType($node->class); + if ($type->isClassString()->yes()) { + $concretes = array_filter( + $type->getClassStringObjectType()->getObjectClassReflections(), + static fn (ClassReflection $classReflection): bool => !$classReflection->isAbstract() && !$classReflection->isInterface(), + ); + + if (count($concretes) > 0) { + return array_map( + static fn (ClassReflection $classReflection): array => [$classReflection->getName(), true], + $concretes, + ); + } + } + return array_merge( array_map( static fn (ConstantStringType $type): array => [$type->getValue(), true], diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 43b9091daf..31d241bee6 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -504,4 +504,46 @@ public function testBug11815(): void $this->analyse([__DIR__ . '/data/bug-11815.php'], []); } + public function testClassString(): void + { + $this->analyse([__DIR__ . '/data/class-string.php'], [ + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 65, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 66, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 67, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 75, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 76, + ], + [ + 'Parameter #1 $i of class ClassString\C constructor expects int, string given.', + 77, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 85, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 86, + ], + [ + 'Parameter #1 $i of class ClassString\A constructor expects int, string given.', + 87, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/class-string.php b/tests/PHPStan/Rules/Classes/data/class-string.php new file mode 100644 index 0000000000..bb07d5954a --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/class-string.php @@ -0,0 +1,87 @@ += 8.0 + +declare(strict_types = 1); + +namespace ClassString; + +class A +{ + public function __construct(public int $i) + { + } +} + +abstract class B +{ + public function __construct(public int $i) + { + } +} + +class C extends B +{ +} + +interface D +{ +} + +class Foo +{ + /** + * @return class-string + */ + public static function returnClassStringA(): string + { + return A::class; + } + + /** + * @return class-string + */ + public static function returnClassStringB(): string + { + return B::class; + } + + /** + * @return class-string + */ + public static function returnClassStringC(): string + { + return C::class; + } + + /** + * @return class-string + */ + public static function returnClassStringD(): string + { + return D::class; + } +} + +$classString = Foo::returnClassStringA(); +$error = new (Foo::returnClassStringA())('O_O'); +$error = new ($classString)('O_O'); +$error = new $classString('O_O'); + +$classString = Foo::returnClassStringB(); +$ok = new (Foo::returnClassStringB())('O_O'); +$ok = new ($classString)('O_O'); +$ok = new $classString('O_O'); + +$classString = Foo::returnClassStringC(); +$error = new (Foo::returnClassStringC())('O_O'); +$error = new ($classString)('O_O'); +$error = new $classString('O_O'); + +$classString = Foo::returnClassStringD(); +$ok = new (Foo::returnClassStringD())('O_O'); +$ok = new ($classString)('O_O'); +$ok = new $classString('O_O'); + +$className = A::class; +$error = new ($className)('O_O'); +$error = new $className('O_O'); +$error = new A('O_O');