Updated Rector to commit 9bcded03ea2db7b01183aad2407e3f7490be9531

9bcded03ea [Experiment] Rework child classes detection on DynamicSourceLocatorProvider (#5735)
This commit is contained in:
Tomas Votruba 2024-04-25 08:24:53 +00:00
parent 7141ae6e56
commit e9ca0b2469
5 changed files with 128 additions and 7 deletions

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '8281a06bc8432d9d7f8e20842e3961df99a277bc';
public const PACKAGE_VERSION = '9bcded03ea2db7b01183aad2407e3f7490be9531';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2024-04-24 08:54:03';
public const RELEASE_DATE = '2024-04-25 10:20:45';
/**
* @var int
*/

View File

@ -289,6 +289,9 @@ final class LazyContainerFactory
$phpStanServicesFactory = $container->make(PHPStanServicesFactory::class);
return $phpStanServicesFactory->createDynamicSourceLocatorProvider();
});
$rectorConfig->afterResolving(DynamicSourceLocatorProvider::class, static function (DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, Container $container) : void {
$dynamicSourceLocatorProvider->autowire($container->make(ReflectionProvider::class));
});
// resetables
$rectorConfig->tag(DynamicSourceLocatorProvider::class, ResetableInterface::class);
$rectorConfig->tag(RenamedClassesDataCollector::class, ResetableInterface::class);

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Stmt\Interface_;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\Util\Reflection\PrivatesAccessor;
final class FamilyRelationsAnalyzer
{
/**
@ -21,10 +22,35 @@ final class FamilyRelationsAnalyzer
* @var \Rector\NodeNameResolver\NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(ReflectionProvider $reflectionProvider, NodeNameResolver $nodeNameResolver)
/**
* @readonly
* @var \Rector\Util\Reflection\PrivatesAccessor
*/
private $privatesAccessor;
public function __construct(ReflectionProvider $reflectionProvider, NodeNameResolver $nodeNameResolver, PrivatesAccessor $privatesAccessor)
{
$this->reflectionProvider = $reflectionProvider;
$this->nodeNameResolver = $nodeNameResolver;
$this->privatesAccessor = $privatesAccessor;
}
/**
* @return ClassReflection[]
*/
public function getChildrenOfClassReflection(ClassReflection $desiredClassReflection) : array
{
if ($desiredClassReflection->isFinalByKeyword()) {
return [];
}
/** @var ClassReflection[] $classReflections */
$classReflections = $this->privatesAccessor->getPrivateProperty($this->reflectionProvider, 'classes');
$childrenClassReflections = [];
foreach ($classReflections as $classReflection) {
if (!$classReflection->isSubclassOf($desiredClassReflection->getName())) {
continue;
}
$childrenClassReflections[] = $classReflection;
}
return $childrenClassReflections;
}
/**
* @api

View File

@ -3,11 +3,17 @@
declare (strict_types=1);
namespace Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider;
use PHPStan\BetterReflection\Identifier\IdentifierType;
use PHPStan\BetterReflection\Reflector\DefaultReflector;
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
use PHPStan\Broker\ClassNotFoundException;
use PHPStan\File\CouldNotReadFileException;
use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher;
use PHPStan\Reflection\BetterReflection\SourceLocator\NewOptimizedDirectorySourceLocator;
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorFactory;
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Contract\DependencyInjection\ResetableInterface;
use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment;
/**
@ -37,11 +43,19 @@ final class DynamicSourceLocatorProvider implements ResetableInterface
* @var \PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator|null
*/
private $aggregateSourceLocator;
/**
* @var \PHPStan\Reflection\ReflectionProvider
*/
private $reflectionProvider;
public function __construct(FileNodesFetcher $fileNodesFetcher, OptimizedDirectorySourceLocatorFactory $optimizedDirectorySourceLocatorFactory)
{
$this->fileNodesFetcher = $fileNodesFetcher;
$this->optimizedDirectorySourceLocatorFactory = $optimizedDirectorySourceLocatorFactory;
}
public function autowire(ReflectionProvider $reflectionProvider) : void
{
$this->reflectionProvider = $reflectionProvider;
}
public function setFilePath(string $filePath) : void
{
$this->filePaths = [$filePath];
@ -74,8 +88,9 @@ final class DynamicSourceLocatorProvider implements ResetableInterface
foreach ($this->directories as $directory) {
$sourceLocators[] = $this->optimizedDirectorySourceLocatorFactory->createByDirectory($directory);
}
$this->aggregateSourceLocator = new AggregateSourceLocator($sourceLocators);
return $this->aggregateSourceLocator;
$aggregateSourceLocator = $this->aggregateSourceLocator = new AggregateSourceLocator($sourceLocators);
$this->collectClasses($aggregateSourceLocator, $sourceLocators);
return $aggregateSourceLocator;
}
public function isPathsEmpty() : bool
{
@ -90,4 +105,33 @@ final class DynamicSourceLocatorProvider implements ResetableInterface
$this->directories = [];
$this->aggregateSourceLocator = null;
}
/**
* @param OptimizedSingleFileSourceLocator[]|NewOptimizedDirectorySourceLocator[] $sourceLocators
*/
private function collectClasses(AggregateSourceLocator $aggregateSourceLocator, array $sourceLocators) : void
{
if ($sourceLocators === []) {
return;
}
// no need to collect classes on single file, will auto collected
if (\count($sourceLocators) === 1 && $sourceLocators[0] instanceof OptimizedSingleFileSourceLocator) {
return;
}
$reflector = new DefaultReflector($aggregateSourceLocator);
$identifierClass = new IdentifierType(IdentifierType::IDENTIFIER_CLASS);
foreach ($sourceLocators as $sourceLocator) {
// trigger collect "classes" on get class on locate identifier
try {
$reflections = $sourceLocator->locateIdentifiersByType($reflector, $identifierClass);
foreach ($reflections as $reflection) {
// make 'classes' collection
try {
$this->reflectionProvider->getClass($reflection->getName());
} catch (ClassNotFoundException $exception) {
}
}
} catch (CouldNotReadFileException $exception) {
}
}
}
}

View File

@ -3,24 +3,40 @@
declare (strict_types=1);
namespace Rector\VendorLocker\NodeVendorLocker;
use PhpParser\Node;
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\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\FileSystem\FilePathHelper;
use Rector\NodeAnalyzer\MagicClassMethodAnalyzer;
use Rector\NodeTypeResolver\PHPStan\ParametersAcceptorSelectorVariantsWrapper;
use Rector\Reflection\ReflectionResolver;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\VendorLocker\ParentClassMethodTypeOverrideGuard;
final class ClassMethodReturnTypeOverrideGuard
{
/**
* @readonly
* @var \Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer
*/
private $familyRelationsAnalyzer;
/**
* @readonly
* @var \Rector\Reflection\ReflectionResolver
*/
private $reflectionResolver;
/**
* @readonly
* @var \Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer
*/
private $returnTypeInferer;
/**
* @readonly
* @var \Rector\VendorLocker\ParentClassMethodTypeOverrideGuard
@ -36,9 +52,11 @@ final class ClassMethodReturnTypeOverrideGuard
* @var \Rector\NodeAnalyzer\MagicClassMethodAnalyzer
*/
private $magicClassMethodAnalyzer;
public function __construct(ReflectionResolver $reflectionResolver, ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard, FilePathHelper $filePathHelper, MagicClassMethodAnalyzer $magicClassMethodAnalyzer)
public function __construct(FamilyRelationsAnalyzer $familyRelationsAnalyzer, ReflectionResolver $reflectionResolver, ReturnTypeInferer $returnTypeInferer, ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard, FilePathHelper $filePathHelper, MagicClassMethodAnalyzer $magicClassMethodAnalyzer)
{
$this->familyRelationsAnalyzer = $familyRelationsAnalyzer;
$this->reflectionResolver = $reflectionResolver;
$this->returnTypeInferer = $returnTypeInferer;
$this->parentClassMethodTypeOverrideGuard = $parentClassMethodTypeOverrideGuard;
$this->filePathHelper = $filePathHelper;
$this->magicClassMethodAnalyzer = $magicClassMethodAnalyzer;
@ -65,7 +83,37 @@ final class ClassMethodReturnTypeOverrideGuard
if (!$this->isReturnTypeChangeAllowed($classMethod, $scope)) {
return \true;
}
return $classMethod->isFinal();
if ($classMethod->isFinal()) {
return \false;
}
$childrenClassReflections = $this->familyRelationsAnalyzer->getChildrenOfClassReflection($classReflection);
if ($childrenClassReflections === []) {
return \false;
}
if ($classMethod->returnType instanceof Node) {
return \true;
}
$returnType = $this->returnTypeInferer->inferFunctionLike($classMethod);
return $this->hasChildrenDifferentTypeClassMethod($classMethod, $childrenClassReflections, $returnType);
}
/**
* @param ClassReflection[] $childrenClassReflections
*/
private function hasChildrenDifferentTypeClassMethod(ClassMethod $classMethod, array $childrenClassReflections, Type $returnType) : bool
{
$methodName = $classMethod->name->toString();
foreach ($childrenClassReflections as $childClassReflection) {
$methodReflection = $childClassReflection->getNativeMethod($methodName);
if (!$methodReflection instanceof PhpMethodReflection) {
continue;
}
$parametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants());
$childReturnType = $parametersAcceptor->getNativeReturnType();
if (!$returnType->isSuperTypeOf($childReturnType)->yes()) {
return \true;
}
}
return \false;
}
private function isReturnTypeChangeAllowed(ClassMethod $classMethod, Scope $scope) : bool
{