EVOLUTION-NINJA
Edit File: ParentClassMethodTypeOverrideGuard.php
<?php declare (strict_types=1); namespace Rector\VendorLocker; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use Rector\Core\PhpParser\AstResolver; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\TypeDeclaration\TypeInferer\ParamTypeInferer; use RectorPrefix20211231\Symplify\SmartFileSystem\Normalizer\PathNormalizer; final class ParentClassMethodTypeOverrideGuard { /** * @readonly * @var \Rector\NodeNameResolver\NodeNameResolver */ private $nodeNameResolver; /** * @readonly * @var \Symplify\SmartFileSystem\Normalizer\PathNormalizer */ private $pathNormalizer; /** * @readonly * @var \Rector\Core\PhpParser\AstResolver */ private $astResolver; /** * @readonly * @var \Rector\TypeDeclaration\TypeInferer\ParamTypeInferer */ private $paramTypeInferer; public function __construct(\Rector\NodeNameResolver\NodeNameResolver $nodeNameResolver, \RectorPrefix20211231\Symplify\SmartFileSystem\Normalizer\PathNormalizer $pathNormalizer, \Rector\Core\PhpParser\AstResolver $astResolver, \Rector\TypeDeclaration\TypeInferer\ParamTypeInferer $paramTypeInferer) { $this->nodeNameResolver = $nodeNameResolver; $this->pathNormalizer = $pathNormalizer; $this->astResolver = $astResolver; $this->paramTypeInferer = $paramTypeInferer; } public function isReturnTypeChangeAllowed(\PhpParser\Node\Stmt\ClassMethod $classMethod) : bool { // make sure return type is not protected by parent contract $parentClassMethodReflection = $this->getParentClassMethod($classMethod); // nothing to check if (!$parentClassMethodReflection instanceof \PHPStan\Reflection\MethodReflection) { return \true; } $parametersAcceptor = \PHPStan\Reflection\ParametersAcceptorSelector::selectSingle($parentClassMethodReflection->getVariants()); if ($parametersAcceptor instanceof \PHPStan\Reflection\FunctionVariantWithPhpDocs && !$parametersAcceptor->getNativeReturnType() instanceof \PHPStan\Type\MixedType) { return \false; } $classReflection = $parentClassMethodReflection->getDeclaringClass(); $fileName = $classReflection->getFileName(); // probably internal if ($fileName === null) { return \false; } /* * Below verify that both current file name and parent file name is not in the /vendor/, if yes, then allowed. * This can happen when rector run into /vendor/ directory while child and parent both are there. * * @see https://3v4l.org/Rc0RF#v8.0.13 * * - both in /vendor/ -> allowed * - one of them in /vendor/ -> not allowed * - both not in /vendor/ -> allowed */ /** @var Scope $scope */ $scope = $classMethod->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE); /** @var ClassReflection $currentClassReflection */ $currentClassReflection = $scope->getClassReflection(); /** @var string $currentFileName */ $currentFileName = $currentClassReflection->getFileName(); // child (current) $normalizedCurrentFileName = $this->pathNormalizer->normalizePath($currentFileName); $isCurrentInVendor = \strpos($normalizedCurrentFileName, '/vendor/') !== \false; // parent $normalizedFileName = $this->pathNormalizer->normalizePath($fileName); $isParentInVendor = \strpos($normalizedFileName, '/vendor/') !== \false; return $isCurrentInVendor && $isParentInVendor || !$isCurrentInVendor && !$isParentInVendor; } public function hasParentClassMethod(\PhpParser\Node\Stmt\ClassMethod $classMethod) : bool { return $this->getParentClassMethod($classMethod) instanceof \PHPStan\Reflection\MethodReflection; } public function hasParentClassMethodDifferentType(\PhpParser\Node\Stmt\ClassMethod $classMethod, int $position, \PHPStan\Type\Type $currentType) : bool { if ($classMethod->isPrivate()) { return \false; } $methodReflection = $this->getParentClassMethod($classMethod); if (!$methodReflection instanceof \PHPStan\Reflection\MethodReflection) { return \false; } $classMethod = $this->astResolver->resolveClassMethodFromMethodReflection($methodReflection); if (!$classMethod instanceof \PhpParser\Node\Stmt\ClassMethod) { return \false; } if ($classMethod->isPrivate()) { return \false; } if (!isset($classMethod->params[$position])) { return \false; } $inferedType = $this->paramTypeInferer->inferParam($classMethod->params[$position]); return \get_class($inferedType) !== \get_class($currentType); } public function getParentClassMethod(\PhpParser\Node\Stmt\ClassMethod $classMethod) : ?\PHPStan\Reflection\MethodReflection { $scope = $classMethod->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::SCOPE); if (!$scope instanceof \PHPStan\Analyser\Scope) { return null; } /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); $classReflection = $scope->getClassReflection(); if (!$classReflection instanceof \PHPStan\Reflection\ClassReflection) { return null; } $parentClassReflections = \array_merge($classReflection->getParents(), $classReflection->getInterfaces()); foreach ($parentClassReflections as $parentClassReflection) { if (!$parentClassReflection->hasNativeMethod($methodName)) { continue; } return $parentClassReflection->getNativeMethod($methodName); } return null; } }