mirror of https://github.com/rectorphp/rector.git
166 lines
6.8 KiB
PHP
166 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare (strict_types=1);
|
|
namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\BinaryOp;
|
|
use PhpParser\Node\FunctionLike;
|
|
use PhpParser\Node\Stmt;
|
|
use PhpParser\Node\Stmt\ClassLike;
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
|
use PHPStan\Reflection\ClassReflection;
|
|
use PHPStan\Reflection\ReflectionProvider;
|
|
use PHPStan\Type\Constant\ConstantStringType;
|
|
use PHPStan\Type\Generic\GenericClassStringType;
|
|
use PHPStan\Type\ObjectType;
|
|
use PHPStan\Type\UnionType;
|
|
use Rector\Core\Contract\Rector\RectorInterface;
|
|
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
|
|
final class UnionTypeCommonTypeNarrower
|
|
{
|
|
/**
|
|
* @readonly
|
|
* @var \Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector
|
|
*/
|
|
private $genericClassStringTypeCorrector;
|
|
/**
|
|
* @readonly
|
|
* @var \PHPStan\Reflection\ReflectionProvider
|
|
*/
|
|
private $reflectionProvider;
|
|
/**
|
|
* Key = the winner Array = the group of types matched
|
|
*
|
|
* @var array<string, array<class-string<Node>|class-string<\PHPStan\PhpDocParser\Ast\Node>|class-string<RectorInterface>>>
|
|
*/
|
|
private const PRIORITY_TYPES = [ClassLike::class => [ClassLike::class], FunctionLike::class => [FunctionLike::class], BinaryOp::class => [BinaryOp::class, Expr::class], Expr::class => [Node::class, Expr::class], Stmt::class => [Node::class, Stmt::class], PhpDocTagValueNode::class => [PhpDocTagValueNode::class, \PHPStan\PhpDocParser\Ast\Node::class], Node::class => [Node::class], RectorInterface::class => [RectorInterface::class]];
|
|
public function __construct(GenericClassStringTypeCorrector $genericClassStringTypeCorrector, ReflectionProvider $reflectionProvider)
|
|
{
|
|
$this->genericClassStringTypeCorrector = $genericClassStringTypeCorrector;
|
|
$this->reflectionProvider = $reflectionProvider;
|
|
}
|
|
public function narrowToSharedObjectType(UnionType $unionType) : ?ObjectType
|
|
{
|
|
$sharedTypes = $this->narrowToSharedTypes($unionType);
|
|
if ($sharedTypes !== []) {
|
|
foreach (self::PRIORITY_TYPES as $winningType => $groupTypes) {
|
|
$intersectedGroupTypes = \array_intersect($groupTypes, $sharedTypes);
|
|
if ($intersectedGroupTypes === $groupTypes) {
|
|
return new ObjectType($winningType);
|
|
}
|
|
}
|
|
$firstSharedType = $sharedTypes[0];
|
|
return new ObjectType($firstSharedType);
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* @return \PHPStan\Type\UnionType|\PHPStan\Type\Generic\GenericClassStringType
|
|
*/
|
|
public function narrowToGenericClassStringType(UnionType $unionType)
|
|
{
|
|
$availableTypes = [];
|
|
foreach ($unionType->getTypes() as $unionedType) {
|
|
if ($unionedType instanceof ConstantStringType) {
|
|
$unionedType = $this->genericClassStringTypeCorrector->correct($unionedType);
|
|
}
|
|
if (!$unionedType instanceof GenericClassStringType) {
|
|
return $unionType;
|
|
}
|
|
$genericClassStrings = [];
|
|
if ($unionedType->getGenericType() instanceof ObjectType) {
|
|
$parentClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType->getGenericType());
|
|
foreach ($parentClassReflections as $parentClassReflection) {
|
|
$genericClassStrings[] = $parentClassReflection->getName();
|
|
}
|
|
}
|
|
$availableTypes[] = $genericClassStrings;
|
|
}
|
|
$genericClassStringType = $this->createGenericClassStringType($availableTypes);
|
|
if ($genericClassStringType instanceof GenericClassStringType) {
|
|
return $genericClassStringType;
|
|
}
|
|
return $unionType;
|
|
}
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function narrowToSharedTypes(UnionType $unionType) : array
|
|
{
|
|
$availableTypes = [];
|
|
foreach ($unionType->getTypes() as $unionedType) {
|
|
if (!$unionedType instanceof ObjectType) {
|
|
return [];
|
|
}
|
|
$typeClassReflections = $this->resolveClassParentClassesAndInterfaces($unionedType);
|
|
$typeClassNames = [];
|
|
foreach ($typeClassReflections as $typeClassReflection) {
|
|
$typeClassNames[] = $typeClassReflection->getName();
|
|
}
|
|
if ($typeClassNames === []) {
|
|
continue;
|
|
}
|
|
$availableTypes[] = $typeClassNames;
|
|
}
|
|
return $this->narrowAvailableTypes($availableTypes);
|
|
}
|
|
/**
|
|
* @return ClassReflection[]
|
|
*/
|
|
private function resolveClassParentClassesAndInterfaces(ObjectType $objectType) : array
|
|
{
|
|
if (!$this->reflectionProvider->hasClass($objectType->getClassName())) {
|
|
return [];
|
|
}
|
|
$classReflection = $this->reflectionProvider->getClass($objectType->getClassName());
|
|
// put earliest interfaces first
|
|
$implementedInterfaceClassReflections = \array_reverse($classReflection->getInterfaces());
|
|
/** @var ClassReflection[] $parentClassAndInterfaceReflections */
|
|
$parentClassAndInterfaceReflections = \array_merge($implementedInterfaceClassReflections, $classReflection->getParents());
|
|
return $this->filterOutNativeClassReflections($parentClassAndInterfaceReflections);
|
|
}
|
|
/**
|
|
* @param string[][] $availableTypes
|
|
* @return string[]
|
|
*/
|
|
private function narrowAvailableTypes(array $availableTypes) : array
|
|
{
|
|
if (\count($availableTypes) < 2) {
|
|
return [];
|
|
}
|
|
/** @var string[] $sharedTypes */
|
|
$sharedTypes = \array_intersect(...$availableTypes);
|
|
return \array_values($sharedTypes);
|
|
}
|
|
/**
|
|
* @param string[][] $availableTypes
|
|
*/
|
|
private function createGenericClassStringType(array $availableTypes) : ?GenericClassStringType
|
|
{
|
|
$sharedTypes = $this->narrowAvailableTypes($availableTypes);
|
|
if ($sharedTypes !== []) {
|
|
foreach (self::PRIORITY_TYPES as $winningType => $groupTypes) {
|
|
$intersectedGroupTypes = \array_intersect($groupTypes, $sharedTypes);
|
|
if ($intersectedGroupTypes === $groupTypes) {
|
|
return new GenericClassStringType(new ObjectType($winningType));
|
|
}
|
|
}
|
|
$firstSharedType = $sharedTypes[0];
|
|
return new GenericClassStringType(new ObjectType($firstSharedType));
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* @param ClassReflection[] $classReflections
|
|
* @return ClassReflection[]
|
|
*/
|
|
private function filterOutNativeClassReflections(array $classReflections) : array
|
|
{
|
|
return \array_filter($classReflections, static function (ClassReflection $classReflection) : bool {
|
|
return !$classReflection->isBuiltin();
|
|
});
|
|
}
|
|
}
|