diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 6323fb6a83..1ff8114300 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -7,17 +7,15 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\PhpDoc\Tag\RequireExtendsTag; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function array_column; -use function array_map; use function array_merge; use function count; -use function sort; use function sprintf; use function strtolower; @@ -26,6 +24,7 @@ final class RequireExtendsCheck { public function __construct( + private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, #[AutowiredParameter] private bool $checkClassCaseSensitivity, @@ -59,12 +58,8 @@ public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): continue; } - sort($classNames); - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); - $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { - $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; - if ($referencedClassReflection === null) { + if (!$this->reflectionProvider->hasClass($class)) { $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) ->identifier('class.notFound'); @@ -76,16 +71,17 @@ public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): continue; } - if ($referencedClassReflection->isInterface()) { + $reflection = $this->reflectionProvider->getClass($class); + if ($reflection->isInterface()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain an interface %s, expected a class.', $class)) ->tip('If you meant an interface, use @phpstan-require-implements instead.') ->identifier('requireExtends.interface') ->build(); - } elseif (!$referencedClassReflection->isClass()) { + } elseif (!$reflection->isClass()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class)) - ->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->identifier(sprintf('requireExtends.%s', strtolower($reflection->getClassTypeDescription()))) ->build(); - } elseif ($referencedClassReflection->isFinal()) { + } elseif ($reflection->isFinal()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class)) ->identifier('requireExtends.finalClass') ->build(); diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index f27d022dd2..5a834b9277 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -13,8 +13,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function array_column; -use function array_map; use function array_merge; use function count; use function sprintf; @@ -66,11 +64,8 @@ public function processNode(Node $node, Scope $scope): array continue; } - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); - $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { - $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; - if ($referencedClassReflection === null) { + if (!$this->reflectionProvider->hasClass($class)) { $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) ->identifier('class.notFound'); @@ -82,9 +77,10 @@ public function processNode(Node $node, Scope $scope): array continue; } - if (!$referencedClassReflection->isInterface()) { + $reflection = $this->reflectionProvider->getClass($class); + if (!$reflection->isInterface()) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements cannot contain non-interface type %s.', $class)) - ->identifier(sprintf('requireImplements.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->identifier(sprintf('requireImplements.%s', strtolower($reflection->getClassTypeDescription()))) ->build(); } else { $errors = array_merge( diff --git a/src/Rules/PhpDoc/SealedDefinitionClassRule.php b/src/Rules/PhpDoc/SealedDefinitionClassRule.php index 3ef882d2ae..a5752e7c75 100644 --- a/src/Rules/PhpDoc/SealedDefinitionClassRule.php +++ b/src/Rules/PhpDoc/SealedDefinitionClassRule.php @@ -7,14 +7,13 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\RegisteredRule; use PHPStan\Node\InClassNode; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassNameCheck; use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function array_column; -use function array_map; use function array_merge; use function count; use function sprintf; @@ -27,6 +26,7 @@ final class SealedDefinitionClassRule implements Rule { public function __construct( + private ReflectionProvider $reflectionProvider, private ClassNameCheck $classCheck, #[AutowiredParameter] private bool $checkClassCaseSensitivity, @@ -69,11 +69,8 @@ public function processNode(Node $node, Scope $scope): array continue; } - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); - $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { - $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; - if ($referencedClassReflection === null) { + if (!$this->reflectionProvider->hasClass($class)) { $errorBuilder = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-sealed contains unknown class %s.', $class)) ->identifier('class.notFound'); diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index a86ee69a86..c51c43d5b1 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -22,6 +22,7 @@ protected function getRule(): Rule $container = self::getContainer(); return new RequireExtendsDefinitionClassRule( new RequireExtendsCheck( + $reflectionProvider, new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck($container), @@ -86,11 +87,6 @@ public function testRule(): void 183, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], - [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', - 183, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index 0c8d232f5d..b2aba5e175 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -23,6 +23,7 @@ protected function getRule(): Rule return new RequireExtendsDefinitionTraitRule( $reflectionProvider, new RequireExtendsCheck( + $reflectionProvider, new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck($container), @@ -56,11 +57,6 @@ public function testRule(): void 192, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], - [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', - 192, - 'Learn more at https://phpstan.org/user-guide/discovering-symbols', - ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php index 1a4307d5e6..852bdf2044 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireImplementsDefinitionTraitRuleTest.php @@ -62,6 +62,11 @@ public function testRule(): void 'PHPDoc tag @phpstan-require-implements contains non-object type *NEVER*.', 34, ], + [ + 'PHPDoc tag @phpstan-require-implements contains unknown class IncompatibleRequireImplements\TypeDoesNotExist.', + 175, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]; $this->analyse([__DIR__ . '/data/incompatible-require-implements.php'], $expectedErrors); diff --git a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php index 7332e38674..bca1baeccc 100644 --- a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php @@ -21,6 +21,7 @@ protected function getRule(): Rule $container = self::getContainer(); return new SealedDefinitionClassRule( + $reflectionProvider, new ClassNameCheck( new ClassCaseSensitivityCheck($reflectionProvider, true), new ClassForbiddenNameCheck($container), @@ -50,6 +51,11 @@ public function testRule(): void 26, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], + [ + 'PHPDoc tag @phpstan-sealed contains unknown class IncompatibleSealed\UnknownClass.', + 46, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php index 246d01a6e8..e16edc04b7 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-implements.php @@ -168,3 +168,8 @@ trait ValidPsalmTrait {} new class { use ValidPsalmTrait; }; + +/** + * @phpstan-require-implements RequiredInterface|TypeDoesNotExist + */ +trait InvalidTraitWithUnknown {} diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-sealed.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-sealed.php index ab1f47ba28..36b5680d23 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-sealed.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-sealed.php @@ -39,3 +39,8 @@ interface ValidInterface {} * @phpstan-sealed SomeInterface */ interface ValidInterface2 {} + +/** + * @phpstan-sealed UnknownClass|SomeClass + */ +class InvalidClassWithUnion {}