From 16c86a55ffdba37c271549e3d9642a54bb4acbe8 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 25 Oct 2025 12:54:53 +0200 Subject: [PATCH 01/13] Narrow object return type When the actual return type of a method is a subtype of the declared return type, we can replace the declared return type with the actual return type --- ..._concrete_method_in_abstract_class.php.inc | 35 +++ .../Fixture/narrow_from_object.php.inc | 43 ++++ .../narrow_from_parent_to_child.php.inc | 43 ++++ .../Fixture/skip_abstract_method.php.inc | 10 + .../Fixture/skip_final_return_type.php.inc | 17 ++ .../skip_multiple_return_types.php.inc | 25 ++ .../NarrowObjectReturnTypeRectorTest.php | 28 +++ .../config/configured_rule.php | 10 + .../NarrowObjectReturnTypeRector.php | 213 ++++++++++++++++++ 9 files changed, 424 insertions(+) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/NarrowObjectReturnTypeRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/configured_rule.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc new file mode 100644 index 00000000000..060d1a1fd08 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc @@ -0,0 +1,35 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc new file mode 100644 index 00000000000..1d1286eb562 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc @@ -0,0 +1,43 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc new file mode 100644 index 00000000000..2f5fa191283 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc @@ -0,0 +1,43 @@ + +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc new file mode 100644 index 00000000000..d482382ccd3 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc new file mode 100644 index 00000000000..4721e2e06e1 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc new file mode 100644 index 00000000000..2dda9c7298a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/NarrowObjectReturnTypeRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/NarrowObjectReturnTypeRectorTest.php new file mode 100644 index 00000000000..5ddfb346e8c --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/NarrowObjectReturnTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/configured_rule.php new file mode 100644 index 00000000000..057940777d3 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(NarrowObjectReturnTypeRector::class); +}; diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php new file mode 100644 index 00000000000..3962c78b9f5 --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -0,0 +1,213 @@ +returnType; + + if (! $returnType instanceof Identifier && ! $returnType instanceof FullyQualified) { + return null; + } + + if ($node->isAbstract()) { + return null; + } + + $actualReturnClass = $this->getActualReturnClass($node); + + if ($actualReturnClass === null) { + return null; + } + + $declaredType = $returnType->toString(); + + if ($declaredType === $actualReturnClass) { + return null; + } + + if ($this->isDeclaredTypeFinal($declaredType)) { + return null; + } + + if ($this->isActualTypeAnonymous($actualReturnClass)) { + return null; + } + + if (! $this->isNarrowingValid($declaredType, $actualReturnClass)) { + return null; + } + + $node->returnType = new FullyQualified($actualReturnClass); + + return $node; + } + + private function isDeclaredTypeFinal(string $declaredType): bool + { + if ($declaredType === 'object') { + return false; + } + + $declaredObjectType = new ObjectType($declaredType); + $classReflection = $declaredObjectType->getClassReflection(); + + if ($classReflection === null) { + return false; + } + + return $classReflection->isFinal(); + } + + private function isActualTypeAnonymous(string $actualType): bool + { + $actualObjectType = new ObjectType($actualType); + $classReflection = $actualObjectType->getClassReflection(); + + if ($classReflection === null) { + return false; + } + + return $classReflection->isAnonymous(); + } + + private function isNarrowingValid(string $declaredType, string $actualType): bool + { + if ($declaredType === 'object') { + return true; + } + + $actualObjectType = new ObjectType($actualType); + $declaredObjectType = new ObjectType($declaredType); + + return $declaredObjectType->isSuperTypeOf($actualObjectType) + ->yes(); + } + + private function getActualReturnClass(ClassMethod $node): ?string + { + $returnStatements = $this->betterNodeFinder->findInstanceOf($node, Return_::class); + + if ($returnStatements === []) { + return null; + } + + $returnedClass = null; + + foreach ($returnStatements as $returnStatement) { + if ($returnStatement->expr === null) { + return null; + } + + $returnType = $this->nodeTypeResolver->getNativeType($returnStatement->expr); + + if (! $returnType->isObject()->yes()) { + return null; + } + + $classNames = $returnType->getObjectClassNames(); + + if (count($classNames) !== 1) { + return null; + } + + $className = $classNames[0]; + + if ($returnedClass === null) { + $returnedClass = $className; + } elseif ($returnedClass !== $className) { + return null; + } + } + + return $returnedClass; + } +} From d8e89a40dd1bd9af0d9897d653d2260f7aefeaa7 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 25 Oct 2025 13:01:44 +0200 Subject: [PATCH 02/13] replace override attribute --- .../Rector/ClassMethod/NarrowObjectReturnTypeRector.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index 3962c78b9f5..e71550ad16d 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -4,7 +4,6 @@ namespace Rector\TypeDeclaration\Rector\ClassMethod; -use Override; use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; @@ -79,13 +78,17 @@ public function createConferenceTalk(): ConferenceTalk ); } - #[Override] + /** + * @return array> + */ public function getNodeTypes(): array { return [ClassMethod::class]; } - #[Override] + /** + * @param ClassMethod $node + */ public function refactor(Node $node): ?Node { if (! $node instanceof ClassMethod) { From 2c56cbbcf0203c24f20070c9091d2b065bf3a21d Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 25 Oct 2025 13:20:44 +0200 Subject: [PATCH 03/13] skip when parent has specific class return type --- ...ip_parent_has_specific_return_type.php.inc | 26 +++++++++++ .../NarrowObjectReturnTypeRector.php | 44 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc new file mode 100644 index 00000000000..cb40f383393 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index e71550ad16d..52ec50bee70 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -9,9 +9,11 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; +use Rector\Reflection\ReflectionResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -24,6 +26,7 @@ final class NarrowObjectReturnTypeRector extends AbstractRector { public function __construct( private readonly BetterNodeFinder $betterNodeFinder, + private readonly ReflectionResolver $reflectionResolver, ) { } @@ -105,6 +108,10 @@ public function refactor(Node $node): ?Node return null; } + if ($this->hasParentMethodWithNonObjectReturn($node)) { + return null; + } + $actualReturnClass = $this->getActualReturnClass($node); if ($actualReturnClass === null) { @@ -175,6 +182,43 @@ private function isNarrowingValid(string $declaredType, string $actualType): boo ->yes(); } + private function hasParentMethodWithNonObjectReturn(ClassMethod $classMethod): bool + { + $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + $ancestors = array_filter( + $classReflection->getAncestors(), + fn (ClassReflection $ancestorClassReflection): bool => + $classReflection->getName() !== $ancestorClassReflection->getName() + ); + + $methodName = $this->getName($classMethod); + foreach ($ancestors as $ancestor) { + if ($ancestor->getFileName() === null) { + continue; + } + + if (! $ancestor->hasNativeMethod($methodName)) { + continue; + } + + $parentMethod = $ancestor->getNativeMethod($methodName); + $parentReturnType = $parentMethod->getVariants()[0] + ->getReturnType(); + + if ($parentReturnType->isObject()->yes() && $parentReturnType->getObjectClassNames() === []) { + continue; + } + + return true; + } + + return false; + } + private function getActualReturnClass(ClassMethod $node): ?string { $returnStatements = $this->betterNodeFinder->findInstanceOf($node, Return_::class); From 16537f71400103fea93f9563b9381fdfc0047410 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 25 Oct 2025 13:42:57 +0200 Subject: [PATCH 04/13] apply feedback --- ..._concrete_method_in_abstract_class.php.inc | 35 ------------------- .../Fixture/narrow_from_object.php.inc | 20 +++-------- .../narrow_from_parent_to_child.php.inc | 22 ++++-------- .../Fixture/skip_abstract_class.php.inc | 15 ++++++++ .../Fixture/skip_abstract_method.php.inc | 10 ------ ...ip_parent_has_specific_return_type.php.inc | 11 ++---- .../Source/AbstractFactory.php | 9 +++++ .../Source/ConferenceTalk.php | 9 +++++ .../Source/ConferenceTalkExtended.php | 9 +++++ .../Source/Talk.php | 9 +++++ .../NarrowObjectReturnTypeRector.php | 3 +- 11 files changed, 67 insertions(+), 85 deletions(-) delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_class.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractFactory.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/ConferenceTalk.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/ConferenceTalkExtended.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/Talk.php diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc deleted file mode 100644 index 060d1a1fd08..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_concrete_method_in_abstract_class.php.inc +++ /dev/null @@ -1,35 +0,0 @@ - ------ - \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc index 1d1286eb562..b98b55f9572 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_object.php.inc @@ -2,13 +2,8 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class AbstractFactory -{ -} - -class ConferenceTalk -{ -} +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\AbstractFactory; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalk; final class TalkFactory extends AbstractFactory { @@ -24,17 +19,12 @@ final class TalkFactory extends AbstractFactory namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class AbstractFactory -{ -} - -class ConferenceTalk -{ -} +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\AbstractFactory; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalk; final class TalkFactory extends AbstractFactory { - protected function build(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture\ConferenceTalk + protected function build(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalk { return new ConferenceTalk(); } diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc index 2f5fa191283..c17afb9e332 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_parent_to_child.php.inc @@ -2,13 +2,8 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class Talk -{ -} - -class ConferenceTalkExtended extends Talk -{ -} +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\Talk; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalkExtended; final class TalkFactoryExtended { @@ -24,20 +19,15 @@ final class TalkFactoryExtended namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class Talk -{ -} - -class ConferenceTalkExtended extends Talk -{ -} +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\Talk; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalkExtended; final class TalkFactoryExtended { - public function createConferenceTalk(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture\ConferenceTalkExtended + public function createConferenceTalk(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalkExtended { return new ConferenceTalkExtended(); } } -?> +?> \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_class.php.inc new file mode 100644 index 00000000000..9ed7d55f8e8 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_class.php.inc @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc deleted file mode 100644 index d482382ccd3..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_abstract_method.php.inc +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc index cb40f383393..5a74bb0fefc 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc @@ -2,13 +2,8 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class Talk -{ -} - -class ConferenceTalkChild extends Talk -{ -} +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\Talk; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalkExtended; abstract class AbstractTalkFactory { @@ -19,7 +14,7 @@ final class ConcreteTalkFactory extends AbstractTalkFactory { public function build(): Talk { - return new ConferenceTalkChild(); + return new ConferenceTalkExtended(); } } diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractFactory.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractFactory.php new file mode 100644 index 00000000000..ab38daec5a7 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractFactory.php @@ -0,0 +1,9 @@ +isAbstract()) { + $classReflection = $this->reflectionResolver->resolveClassReflection($node); + if ($classReflection instanceof ClassReflection && $classReflection->isAbstract()) { return null; } From b8fde1d8ba11946b96884f5d9fc22d43d96e3d8c Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sat, 25 Oct 2025 13:47:01 +0200 Subject: [PATCH 05/13] skip non final classes --- .../Fixture/skip_non_final_class.php.inc | 15 +++++++++++++++ .../ClassMethod/NarrowObjectReturnTypeRector.php | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_non_final_class.php.inc diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_non_final_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_non_final_class.php.inc new file mode 100644 index 00000000000..28f40654959 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_non_final_class.php.inc @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index 54a321c03ec..0289687501d 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -105,7 +105,11 @@ public function refactor(Node $node): ?Node } $classReflection = $this->reflectionResolver->resolveClassReflection($node); - if ($classReflection instanceof ClassReflection && $classReflection->isAbstract()) { + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if (! $classReflection->isFinal()) { return null; } From a0f9b9b439741ad16de015a8ced3d40815605a79 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 09:55:03 +0100 Subject: [PATCH 06/13] scope return and allow final methods --- ...ow_final_method_in_non_final_class.php.inc | 31 +++++++++++++++++++ .../NarrowObjectReturnTypeRector.php | 5 ++- 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_final_method_in_non_final_class.php.inc diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_final_method_in_non_final_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_final_method_in_non_final_class.php.inc new file mode 100644 index 00000000000..006d7fcf390 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_final_method_in_non_final_class.php.inc @@ -0,0 +1,31 @@ + +----- + \ No newline at end of file diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index 0289687501d..9ec2ff21f7e 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -8,7 +8,6 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Return_; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; use Rector\PhpParser\Node\BetterNodeFinder; @@ -109,7 +108,7 @@ public function refactor(Node $node): ?Node return null; } - if (! $classReflection->isFinal()) { + if (! $classReflection->isFinal() && ! $node->isFinal()) { return null; } @@ -226,7 +225,7 @@ private function hasParentMethodWithNonObjectReturn(ClassMethod $classMethod): b private function getActualReturnClass(ClassMethod $node): ?string { - $returnStatements = $this->betterNodeFinder->findInstanceOf($node, Return_::class); + $returnStatements = $this->betterNodeFinder->findReturnsScoped($node); if ($returnStatements === []) { return null; From 667b6e1f70163581d1f629a98f15e72d9fcfec74 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 10:40:18 +0100 Subject: [PATCH 07/13] Handle generics using AST instead of PHPStan --- .../narrow_with_generic_parent.php.inc | 39 +++++++++++++++++++ .../Source/AbstractGenericFactory.php | 16 ++++++++ .../NarrowObjectReturnTypeRector.php | 23 ++++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_with_generic_parent.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractGenericFactory.php diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_with_generic_parent.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_with_generic_parent.php.inc new file mode 100644 index 00000000000..a107ec47b35 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_with_generic_parent.php.inc @@ -0,0 +1,39 @@ + + */ +final class ConcreteGenericFactory extends AbstractGenericFactory +{ + public function build(): object + { + return new ConferenceTalk(); + } +} + +?> +----- + + */ +final class ConcreteGenericFactory extends AbstractGenericFactory +{ + public function build(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalk + { + return new ConferenceTalk(); + } +} + +?> \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractGenericFactory.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractGenericFactory.php new file mode 100644 index 00000000000..05c0f97aa85 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractGenericFactory.php @@ -0,0 +1,16 @@ +reflectionResolver->resolveClassReflection($classMethod); + if (! $classReflection instanceof ClassReflection) { return false; } $ancestors = array_filter( $classReflection->getAncestors(), - fn (ClassReflection $ancestorClassReflection): bool => - $classReflection->getName() !== $ancestorClassReflection->getName() + fn (ClassReflection $ancestorClassReflection): bool => $classReflection->getName() !== $ancestorClassReflection->getName() ); $methodName = $this->getName($classMethod); + foreach ($ancestors as $ancestor) { if ($ancestor->getFileName() === null) { continue; @@ -209,11 +212,19 @@ private function hasParentMethodWithNonObjectReturn(ClassMethod $classMethod): b continue; } - $parentMethod = $ancestor->getNativeMethod($methodName); - $parentReturnType = $parentMethod->getVariants()[0] - ->getReturnType(); + $parentClassMethod = $this->astResolver->resolveClassMethod($ancestor->getName(), $methodName); + + if ($parentClassMethod === null) { + continue; + } + + $parentReturnType = $parentClassMethod->returnType; + + if ($parentReturnType === null) { + continue; + } - if ($parentReturnType->isObject()->yes() && $parentReturnType->getObjectClassNames() === []) { + if ($parentReturnType instanceof Identifier && $parentReturnType->name === 'object') { continue; } From a78090eae69e687e295a327ecead65afa7b2ad5e Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 10:44:40 +0100 Subject: [PATCH 08/13] remove dummy classes --- .../Fixture/skip_multiple_return_types.php.inc | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc index 2dda9c7298a..f5215c18b82 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_multiple_return_types.php.inc @@ -2,23 +2,15 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -class Product -{ -} - -class Service -{ -} - final class MixedFactory { public function create(): object { if (rand(0, 1)) { - return new Product(); + return new \DateTime(); } - return new Service(); + return new \stdClass(); } } From aed5082c3350e5f24ad1ecb59cbee08d2f7331fa Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 10:45:00 +0100 Subject: [PATCH 09/13] use isFinalByKeyword --- .../Rector/ClassMethod/NarrowObjectReturnTypeRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index d3048c9bf3c..7ed97526a6d 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -110,7 +110,7 @@ public function refactor(Node $node): ?Node return null; } - if (! $classReflection->isFinal() && ! $node->isFinal()) { + if (! $classReflection->isFinalByKeyword() && ! $node->isFinal()) { return null; } From aebdd2b95187ca9ac7a54a1b592186dac96e3291 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 16:20:35 +0100 Subject: [PATCH 10/13] move to source --- .../Fixture/skip_final_return_type.php.inc | 8 ++------ .../skip_parent_has_specific_return_type.php.inc | 6 +----- .../Source/AbstractTalkFactory.php | 10 ++++++++++ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractTalkFactory.php diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc index 4721e2e06e1..6ae19a07336 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_final_return_type.php.inc @@ -2,15 +2,11 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; -final class FinalProduct -{ -} - final class FinalProductFactory { - public function create(): FinalProduct + public function create(): \DateTime { - return new FinalProduct(); + return new \DateTime(); } } diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc index 5a74bb0fefc..6166edcc1a1 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc @@ -2,14 +2,10 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Fixture; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\AbstractTalkFactory; use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\Talk; use Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\Source\ConferenceTalkExtended; -abstract class AbstractTalkFactory -{ - abstract public function build(): Talk; -} - final class ConcreteTalkFactory extends AbstractTalkFactory { public function build(): Talk diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractTalkFactory.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractTalkFactory.php new file mode 100644 index 00000000000..d11bd3915cd --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Source/AbstractTalkFactory.php @@ -0,0 +1,10 @@ + Date: Sun, 26 Oct 2025 16:24:54 +0100 Subject: [PATCH 11/13] update description --- .../Rector/ClassMethod/NarrowObjectReturnTypeRector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index 7ed97526a6d..7ab3dc5201d 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -18,7 +18,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * Narrows return types from generic object or parent class to specific class. + * Narrows return type from generic object to specific class in final classes/methods. * * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\NarrowObjectReturnTypeRectorTest */ @@ -34,7 +34,7 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Narrows return type from generic object or parent class to specific class', + 'Narrows return type from generic object to specific class in final classes/methods', [ new CodeSample( <<<'CODE_SAMPLE' From b2e2d0e91fdfcb7f59270f5d3493faa4dc529f5f Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 16:46:00 +0100 Subject: [PATCH 12/13] use isFinalByKeyword --- .../Rector/ClassMethod/NarrowObjectReturnTypeRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index 7ab3dc5201d..c37285c5f36 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -160,7 +160,7 @@ private function isDeclaredTypeFinal(string $declaredType): bool return false; } - return $classReflection->isFinal(); + return $classReflection->isFinalByKeyword(); } private function isActualTypeAnonymous(string $actualType): bool From a84d702e3df07bd747a90c893ec356b376617930 Mon Sep 17 00:00:00 2001 From: Orest Divintari Date: Sun, 26 Oct 2025 18:28:09 +0100 Subject: [PATCH 13/13] Narrow object return type --- .../narrow_from_abstract_class.php.inc | 43 +++++++++++++ .../Fixture/narrow_from_interface.php.inc | 43 +++++++++++++ .../narrow_interface_inheritance.php.inc | 63 +++++++++++++++++++ .../narrow_interface_via_method_call.php.inc | 53 ++++++++++++++++ ...ow_parent_has_specific_return_type.php.inc | 35 +++++++++++ ...ip_parent_has_specific_return_type.php.inc | 17 ----- .../NarrowObjectReturnTypeRector.php | 56 +---------------- 7 files changed, 239 insertions(+), 71 deletions(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_abstract_class.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_interface.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_inheritance.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_via_method_call.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_parent_has_specific_return_type.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_abstract_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_abstract_class.php.inc new file mode 100644 index 00000000000..34b50700256 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_abstract_class.php.inc @@ -0,0 +1,43 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_interface.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_interface.php.inc new file mode 100644 index 00000000000..c36ed7c75aa --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_from_interface.php.inc @@ -0,0 +1,43 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_inheritance.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_inheritance.php.inc new file mode 100644 index 00000000000..557be2dfadb --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_inheritance.php.inc @@ -0,0 +1,63 @@ +createProduct(); + } + + private function createProduct(): ConcreteProduct + { + return new ConcreteProduct(); + } +} + +?> +----- +createProduct(); + } + + private function createProduct(): ConcreteProduct + { + return new ConcreteProduct(); + } +} + +?> \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_via_method_call.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_via_method_call.php.inc new file mode 100644 index 00000000000..fc8fa9e8c94 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_interface_via_method_call.php.inc @@ -0,0 +1,53 @@ +createStripePayment(); + } + + private function createStripePayment(): StripePayment + { + return new StripePayment(); + } +} + +?> +----- +createStripePayment(); + } + + private function createStripePayment(): StripePayment + { + return new StripePayment(); + } +} + +?> \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_parent_has_specific_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_parent_has_specific_return_type.php.inc new file mode 100644 index 00000000000..53daff4f5b8 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/narrow_parent_has_specific_return_type.php.inc @@ -0,0 +1,35 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc deleted file mode 100644 index 6166edcc1a1..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector/Fixture/skip_parent_has_specific_return_type.php.inc +++ /dev/null @@ -1,17 +0,0 @@ - \ No newline at end of file diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index c37285c5f36..913d6694689 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -10,7 +10,6 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\ObjectType; -use Rector\PhpParser\AstResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; @@ -18,7 +17,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * Narrows return type from generic object to specific class in final classes/methods. + * Narrows return type from generic object or parent class to specific class in final classes/methods. * * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector\NarrowObjectReturnTypeRectorTest */ @@ -27,14 +26,13 @@ final class NarrowObjectReturnTypeRector extends AbstractRector public function __construct( private readonly BetterNodeFinder $betterNodeFinder, private readonly ReflectionResolver $reflectionResolver, - private readonly AstResolver $astResolver, ) { } public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Narrows return type from generic object to specific class in final classes/methods', + 'Narrows return type from generic object or parent class to specific class in final classes/methods', [ new CodeSample( <<<'CODE_SAMPLE' @@ -114,10 +112,6 @@ public function refactor(Node $node): ?Node return null; } - if ($this->hasParentMethodWithNonObjectReturn($node)) { - return null; - } - $actualReturnClass = $this->getActualReturnClass($node); if ($actualReturnClass === null) { @@ -188,52 +182,6 @@ private function isNarrowingValid(string $declaredType, string $actualType): boo ->yes(); } - private function hasParentMethodWithNonObjectReturn(ClassMethod $classMethod): bool - { - $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); - - if (! $classReflection instanceof ClassReflection) { - return false; - } - - $ancestors = array_filter( - $classReflection->getAncestors(), - fn (ClassReflection $ancestorClassReflection): bool => $classReflection->getName() !== $ancestorClassReflection->getName() - ); - - $methodName = $this->getName($classMethod); - - foreach ($ancestors as $ancestor) { - if ($ancestor->getFileName() === null) { - continue; - } - - if (! $ancestor->hasNativeMethod($methodName)) { - continue; - } - - $parentClassMethod = $this->astResolver->resolveClassMethod($ancestor->getName(), $methodName); - - if ($parentClassMethod === null) { - continue; - } - - $parentReturnType = $parentClassMethod->returnType; - - if ($parentReturnType === null) { - continue; - } - - if ($parentReturnType instanceof Identifier && $parentReturnType->name === 'object') { - continue; - } - - return true; - } - - return false; - } - private function getActualReturnClass(ClassMethod $node): ?string { $returnStatements = $this->betterNodeFinder->findReturnsScoped($node);