From 56143e9c180a5f657ace748203f73a13588209a2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 9 Nov 2025 22:39:27 +0100 Subject: [PATCH 1/4] Access Union::getObjectClassReflections --- src/Rules/PhpDoc/RequireExtendsCheck.php | 13 ++++++++++++- .../PhpDoc/RequireImplementsDefinitionTraitRule.php | 13 ++++++++++++- src/Rules/PhpDoc/SealedDefinitionClassRule.php | 13 ++++++++++++- .../Rules/PhpDoc/SealedDefinitionClassRuleTest.php | 5 +++++ .../Rules/PhpDoc/data/incompatible-sealed.php | 5 +++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 6323fb6a83..3fe4a4f445 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -12,6 +12,7 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -59,8 +60,18 @@ public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): continue; } + if ($type instanceof UnionType) { + $classReflections = []; + foreach ($type->getTypes() as $subType) { + $classReflections[] = $subType->getObjectClassReflections(); + } + $classReflections = array_merge(...$classReflections); + } else { + $classReflections = $type->getObjectClassReflections(); + } + sort($classNames); - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index f27d022dd2..8992cfadc4 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -12,6 +12,7 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -66,7 +67,17 @@ public function processNode(Node $node, Scope $scope): array continue; } - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + if ($type instanceof UnionType) { + $classReflections = []; + foreach ($type->getTypes() as $subType) { + $classReflections[] = $subType->getObjectClassReflections(); + } + $classReflections = array_merge(...$classReflections); + } else { + $classReflections = $type->getObjectClassReflections(); + } + + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; diff --git a/src/Rules/PhpDoc/SealedDefinitionClassRule.php b/src/Rules/PhpDoc/SealedDefinitionClassRule.php index 3ef882d2ae..f44e6ae692 100644 --- a/src/Rules/PhpDoc/SealedDefinitionClassRule.php +++ b/src/Rules/PhpDoc/SealedDefinitionClassRule.php @@ -12,6 +12,7 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -69,7 +70,17 @@ public function processNode(Node $node, Scope $scope): array continue; } - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + if ($type instanceof UnionType) { + $classReflections = []; + foreach ($type->getTypes() as $subType) { + $classReflections[] = $subType->getObjectClassReflections(); + } + $classReflections = array_merge(...$classReflections); + } else { + $classReflections = $type->getObjectClassReflections(); + } + + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); foreach ($classNames as $class) { $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; diff --git a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php index 7332e38674..87b0c20882 100644 --- a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php @@ -50,6 +50,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-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 {} From 14257a08ea0cd3687d3b56fdeb5494090de66ebd Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 9 Nov 2025 22:47:49 +0100 Subject: [PATCH 2/4] Rework --- src/Rules/PhpDoc/RequireExtendsCheck.php | 13 +------------ .../PhpDoc/RequireImplementsDefinitionTraitRule.php | 13 +------------ src/Rules/PhpDoc/SealedDefinitionClassRule.php | 13 +------------ src/Type/UnionType.php | 12 ++++++++---- .../RequireExtendsDefinitionClassRuleTest.php | 5 ----- .../RequireExtendsDefinitionTraitRuleTest.php | 5 ----- 6 files changed, 11 insertions(+), 50 deletions(-) diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 3fe4a4f445..6323fb6a83 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -12,7 +12,6 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -60,18 +59,8 @@ public function checkExtendsTags(Scope $scope, Node $node, array $extendsTags): continue; } - if ($type instanceof UnionType) { - $classReflections = []; - foreach ($type->getTypes() as $subType) { - $classReflections[] = $subType->getObjectClassReflections(); - } - $classReflections = array_merge(...$classReflections); - } else { - $classReflections = $type->getObjectClassReflections(); - } - sort($classNames); - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); + $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; diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 8992cfadc4..f27d022dd2 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -12,7 +12,6 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -67,17 +66,7 @@ public function processNode(Node $node, Scope $scope): array continue; } - if ($type instanceof UnionType) { - $classReflections = []; - foreach ($type->getTypes() as $subType) { - $classReflections[] = $subType->getObjectClassReflections(); - } - $classReflections = array_merge(...$classReflections); - } else { - $classReflections = $type->getObjectClassReflections(); - } - - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); + $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; diff --git a/src/Rules/PhpDoc/SealedDefinitionClassRule.php b/src/Rules/PhpDoc/SealedDefinitionClassRule.php index f44e6ae692..3ef882d2ae 100644 --- a/src/Rules/PhpDoc/SealedDefinitionClassRule.php +++ b/src/Rules/PhpDoc/SealedDefinitionClassRule.php @@ -12,7 +12,6 @@ use PHPStan\Rules\ClassNameUsageLocation; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use function array_column; use function array_map; @@ -70,17 +69,7 @@ public function processNode(Node $node, Scope $scope): array continue; } - if ($type instanceof UnionType) { - $classReflections = []; - foreach ($type->getTypes() as $subType) { - $classReflections[] = $subType->getObjectClassReflections(); - } - $classReflections = array_merge(...$classReflections); - } else { - $classReflections = $type->getObjectClassReflections(); - } - - $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $classReflections); + $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; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 178e72eeb9..2ab0f3885d 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -162,10 +162,14 @@ public function getObjectClassNames(): array public function getObjectClassReflections(): array { - return $this->pickFromTypes( - static fn (Type $type) => $type->getObjectClassReflections(), - static fn (Type $type) => $type->isObject()->yes(), - ); + $reflections = []; + foreach ($this->types as $type) { + foreach ($type->getObjectClassReflections() as $reflection) { + $reflections[] = $reflection; + } + } + + return $reflections; } public function getArrays(): array diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index a86ee69a86..ad8486a482 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -86,11 +86,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..d52cd957c7 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -56,11 +56,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', - ], ]); } From c6709b6c541b39ad3f72c10b39c039a4925d12e9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 5 Dec 2025 10:49:51 +0100 Subject: [PATCH 3/4] Rework --- src/Rules/PhpDoc/RequireExtendsCheck.php | 20 ++++++++----------- .../RequireImplementsDefinitionTraitRule.php | 12 ++++------- .../PhpDoc/SealedDefinitionClassRule.php | 9 +++------ src/Type/UnionType.php | 12 ++++------- .../RequireExtendsDefinitionClassRuleTest.php | 1 + .../RequireExtendsDefinitionTraitRuleTest.php | 1 + .../PhpDoc/SealedDefinitionClassRuleTest.php | 1 + 7 files changed, 22 insertions(+), 34 deletions(-) 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/src/Type/UnionType.php b/src/Type/UnionType.php index 2ab0f3885d..178e72eeb9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -162,14 +162,10 @@ public function getObjectClassNames(): array public function getObjectClassReflections(): array { - $reflections = []; - foreach ($this->types as $type) { - foreach ($type->getObjectClassReflections() as $reflection) { - $reflections[] = $reflection; - } - } - - return $reflections; + return $this->pickFromTypes( + static fn (Type $type) => $type->getObjectClassReflections(), + static fn (Type $type) => $type->isObject()->yes(), + ); } public function getArrays(): array diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index ad8486a482..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), diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index d52cd957c7..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), diff --git a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php index 87b0c20882..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), From cf293a97f316a25a47a3572acc888b8304d33a7f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 5 Dec 2025 11:13:08 +0100 Subject: [PATCH 4/4] Add test --- .../PhpDoc/RequireImplementsDefinitionTraitRuleTest.php | 5 +++++ .../Rules/PhpDoc/data/incompatible-require-implements.php | 5 +++++ 2 files changed, 10 insertions(+) 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/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 {}