diff --git a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php index 50d48af63a8..1dcdf9c5553 100644 --- a/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php +++ b/src/FamilyTree/NodeAnalyzer/ClassChildAnalyzer.php @@ -4,14 +4,24 @@ namespace Rector\FamilyTree\NodeAnalyzer; +use PhpParser\Node; +use PhpParser\Node\Stmt\ClassLike; +use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; use PHPStan\Type\Type; +use Rector\PhpParser\AstResolver; final readonly class ClassChildAnalyzer { + public function __construct( + private AstResolver $astResolver + ) { + } + /** * Look both parent class and interface, yes, all PHP interface methods are abstract */ @@ -42,8 +52,14 @@ public function resolveParentClassMethodReturnType(ClassReflection $classReflect } foreach ($parentClassMethods as $parentClassMethod) { - $parametersAcceptor = ParametersAcceptorSelector::combineAcceptors($parentClassMethod->getVariants()); - $nativeReturnType = $parametersAcceptor->getNativeReturnType(); + // for downgrade purpose on __toString + // @see https://3v4l.org/kdcEh#v7.4.33 + // @see https://github.com/phpstan/phpstan-src/commit/3854cbc5748a7cb51ee0b86ceffe29bd0564bc98 + if ($parentClassMethod->getDeclaringClass()->isBuiltIn() || $methodName !== '__toString') { + $nativeReturnType = $this->resolveNativeType($parentClassMethod); + } else { + $nativeReturnType = $this->resolveToStringNativeTypeFromAstResolver($parentClassMethod); + } if (! $nativeReturnType instanceof MixedType) { return $nativeReturnType; @@ -53,6 +69,27 @@ public function resolveParentClassMethodReturnType(ClassReflection $classReflect return new MixedType(); } + private function resolveNativeType(PhpMethodReflection $phpMethodReflection): Type + { + $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($phpMethodReflection->getVariants()); + return $extendedParametersAcceptor->getNativeReturnType(); + } + + private function resolveToStringNativeTypeFromAstResolver(PhpMethodReflection $phpMethodReflection): Type + { + $classReflection = $phpMethodReflection->getDeclaringClass(); + $class = $this->astResolver->resolveClassFromClassReflection($classReflection); + + if ($class instanceof ClassLike) { + $classMethod = $class->getMethod($phpMethodReflection->getName()); + if ($classMethod instanceof ClassMethod && !$classMethod->returnType instanceof Node) { + return new MixedType(); + } + } + + return new StringType(); + } + /** * @return PhpMethodReflection[] */