From 9ff2ba18232da649a5a3d2bd8d526eb5e4b3345e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 9 Nov 2025 22:39:27 +0100 Subject: [PATCH 1/2] 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 3ec5da8925..f898643dcb 100644 --- a/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/SealedDefinitionClassRuleTest.php @@ -49,6 +49,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 52264b1f7503b66a91d57907ce43d3cf3b7ca96f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 9 Nov 2025 22:47:49 +0100 Subject: [PATCH 2/2] 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 a1855a713b..b3ddcf1d69 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 8a9546216e..78c1b75438 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -85,11 +85,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 e345685e67..7d94f1ce79 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -55,11 +55,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', - ], ]); }