From f7f16f414c342af15c6e099d2bd59f8c65b544a9 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 28 Jun 2026 22:56:42 +0200 Subject: [PATCH] [TypeDeclaration] Drop AstResolver from NarrowObjectReturnTypeRector parent check The parent-method lookup only needs the parent's native return type and whether its @return is a generic type. PHPStan's ExtendedMethodReflection exposes both via getNativeReturnType() and getReturnType(), so resolve the parent method through reflection instead of re-parsing its source file with AstResolver. --- .../NarrowObjectReturnTypeRector.php | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php index bafc1688487..39e5185d633 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/NarrowObjectReturnTypeRector.php @@ -11,6 +11,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; @@ -19,7 +20,6 @@ use Rector\BetterPhpDocParser\ValueObject\Type\FullyQualifiedIdentifierTypeNode; use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; -use Rector\PhpParser\AstResolver; use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; @@ -37,7 +37,6 @@ final class NarrowObjectReturnTypeRector extends AbstractRector public function __construct( private readonly BetterNodeFinder $betterNodeFinder, private readonly ReflectionResolver $reflectionResolver, - private readonly AstResolver $astResolver, private readonly StaticTypeMapper $staticTypeMapper, private readonly TypeComparator $typeComparator, private readonly PhpDocInfoFactory $phpDocInfoFactory, @@ -242,6 +241,8 @@ private function isNarrowingValidFromParent(ClassMethod $classMethod, string $ac $methodName = $this->getName($classMethod); + $actualObjectType = new ObjectType($actualReturnClass); + foreach ($ancestors as $ancestor) { if ($ancestor->getFileName() === null) { continue; @@ -251,19 +252,23 @@ private function isNarrowingValidFromParent(ClassMethod $classMethod, string $ac continue; } - $parentClassMethod = $this->astResolver->resolveClassMethod($ancestor->getName(), $methodName); - if (! $parentClassMethod instanceof ClassMethod) { - continue; - } + $parametersAcceptor = ParametersAcceptorSelector::combineAcceptors( + $ancestor->getNativeMethod($methodName) + ->getVariants() + ); - $parentReturnType = $parentClassMethod->returnType; - if (! $parentReturnType instanceof Identifier && ! $parentReturnType instanceof FullyQualified) { + // only a single, bare class-name return type can constrain narrowing + $parentNativeReturnType = $parametersAcceptor->getNativeReturnType(); + if (! $parentNativeReturnType instanceof ObjectType) { continue; } - $parentReturnTypeName = $parentReturnType->toString(); + if (! $parentNativeReturnType->isSuperTypeOf($actualObjectType)->yes()) { + return false; + } - if (! $this->isNarrowingValid($parentClassMethod, $parentReturnTypeName, $actualReturnClass)) { + // skip narrowing when the parent @return is a generic type, e.g. Collection + if ($parametersAcceptor->getReturnType() instanceof GenericObjectType) { return false; } }