2020-01-31 08:18:45 +00:00
|
|
|
<?php
|
|
|
|
|
2021-05-09 20:15:43 +00:00
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace Rector\NodeTypeResolver\TypeComparator;
|
2020-01-31 08:18:45 +00:00
|
|
|
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node;
|
|
|
|
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
2023-06-11 08:57:05 +00:00
|
|
|
use PHPStan\Reflection\ClassReflection;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PHPStan\Type\ArrayType;
|
|
|
|
use PHPStan\Type\BooleanType;
|
|
|
|
use PHPStan\Type\Constant\ConstantArrayType;
|
|
|
|
use PHPStan\Type\Constant\ConstantBooleanType;
|
|
|
|
use PHPStan\Type\ConstantScalarType;
|
|
|
|
use PHPStan\Type\Generic\GenericClassStringType;
|
|
|
|
use PHPStan\Type\IntegerType;
|
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\ObjectType;
|
|
|
|
use PHPStan\Type\StaticType;
|
|
|
|
use PHPStan\Type\ThisType;
|
|
|
|
use PHPStan\Type\Type;
|
|
|
|
use PHPStan\Type\TypeTraverser;
|
|
|
|
use PHPStan\Type\UnionType;
|
|
|
|
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
|
|
|
|
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
|
|
|
|
use Rector\NodeTypeResolver\PHPStan\TypeHasher;
|
2024-01-02 02:40:38 +00:00
|
|
|
use Rector\Reflection\ReflectionResolver;
|
2022-06-06 17:12:56 +00:00
|
|
|
use Rector\StaticTypeMapper\StaticTypeMapper;
|
|
|
|
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
|
|
|
|
use Rector\TypeDeclaration\TypeNormalizer;
|
2020-01-31 08:18:45 +00:00
|
|
|
final class TypeComparator
|
|
|
|
{
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\NodeTypeResolver\PHPStan\TypeHasher
|
2020-01-31 08:18:45 +00:00
|
|
|
*/
|
|
|
|
private $typeHasher;
|
2020-09-01 17:56:30 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\TypeDeclaration\TypeNormalizer
|
2020-09-01 17:56:30 +00:00
|
|
|
*/
|
|
|
|
private $typeNormalizer;
|
2020-12-05 00:31:05 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\StaticTypeMapper\StaticTypeMapper
|
2020-12-05 00:31:05 +00:00
|
|
|
*/
|
|
|
|
private $staticTypeMapper;
|
2021-02-23 01:25:34 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\NodeTypeResolver\TypeComparator\ArrayTypeComparator
|
2021-02-23 01:25:34 +00:00
|
|
|
*/
|
|
|
|
private $arrayTypeComparator;
|
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\NodeTypeResolver\TypeComparator\ScalarTypeComparator
|
2021-02-23 01:25:34 +00:00
|
|
|
*/
|
|
|
|
private $scalarTypeComparator;
|
2021-04-04 09:01:11 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2021-05-10 23:39:21 +00:00
|
|
|
* @var \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory
|
2021-04-04 09:01:11 +00:00
|
|
|
*/
|
|
|
|
private $typeFactory;
|
2022-05-11 05:14:30 +00:00
|
|
|
/**
|
2023-06-11 23:01:39 +00:00
|
|
|
* @readonly
|
2024-01-02 02:40:38 +00:00
|
|
|
* @var \Rector\Reflection\ReflectionResolver
|
2022-05-11 05:14:30 +00:00
|
|
|
*/
|
2023-06-11 08:57:05 +00:00
|
|
|
private $reflectionResolver;
|
|
|
|
public function __construct(TypeHasher $typeHasher, TypeNormalizer $typeNormalizer, StaticTypeMapper $staticTypeMapper, \Rector\NodeTypeResolver\TypeComparator\ArrayTypeComparator $arrayTypeComparator, \Rector\NodeTypeResolver\TypeComparator\ScalarTypeComparator $scalarTypeComparator, TypeFactory $typeFactory, ReflectionResolver $reflectionResolver)
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2020-01-31 08:18:45 +00:00
|
|
|
$this->typeHasher = $typeHasher;
|
2020-09-01 17:56:30 +00:00
|
|
|
$this->typeNormalizer = $typeNormalizer;
|
2020-12-05 00:31:05 +00:00
|
|
|
$this->staticTypeMapper = $staticTypeMapper;
|
2021-02-23 01:25:34 +00:00
|
|
|
$this->arrayTypeComparator = $arrayTypeComparator;
|
|
|
|
$this->scalarTypeComparator = $scalarTypeComparator;
|
2021-04-04 09:01:11 +00:00
|
|
|
$this->typeFactory = $typeFactory;
|
2023-06-11 08:57:05 +00:00
|
|
|
$this->reflectionResolver = $reflectionResolver;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function areTypesEqual(Type $firstType, Type $secondType) : bool
|
2020-01-31 08:18:45 +00:00
|
|
|
{
|
2021-10-27 23:25:15 +00:00
|
|
|
$firstTypeHash = $this->typeHasher->createTypeHash($firstType);
|
|
|
|
$secondTypeHash = $this->typeHasher->createTypeHash($secondType);
|
|
|
|
if ($firstTypeHash === $secondTypeHash) {
|
|
|
|
return \true;
|
|
|
|
}
|
2021-02-23 01:25:34 +00:00
|
|
|
if ($this->scalarTypeComparator->areEqualScalar($firstType, $secondType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
|
|
|
// aliases and types
|
|
|
|
if ($this->areAliasedObjectMatchingFqnObject($firstType, $secondType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2021-04-04 09:01:11 +00:00
|
|
|
if ($this->areArrayUnionConstantEqualTypes($firstType, $secondType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2020-09-01 17:56:30 +00:00
|
|
|
$firstType = $this->typeNormalizer->normalizeArrayOfUnionToUnionArray($firstType);
|
|
|
|
$secondType = $this->typeNormalizer->normalizeArrayOfUnionToUnionArray($secondType);
|
2020-01-31 08:18:45 +00:00
|
|
|
if ($this->typeHasher->areTypesEqual($firstType, $secondType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2021-03-09 15:49:33 +00:00
|
|
|
// is template of
|
2020-01-31 08:18:45 +00:00
|
|
|
return $this->areArrayTypeWithSingleObjectChildToParent($firstType, $secondType);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function arePhpParserAndPhpStanPhpDocTypesEqual(Node $phpParserNode, TypeNode $phpStanDocTypeNode, Node $node) : bool
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2020-12-05 00:31:05 +00:00
|
|
|
$phpParserNodeType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($phpParserNode);
|
2021-05-09 20:15:43 +00:00
|
|
|
$phpStanDocType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($phpStanDocTypeNode, $node);
|
2024-02-09 08:11:26 +00:00
|
|
|
if (!$this->areTypesEqual($phpParserNodeType, $phpStanDocType) && $this->isSubtype($phpStanDocType, $phpParserNodeType)) {
|
|
|
|
return \false;
|
|
|
|
}
|
2021-04-10 23:28:08 +00:00
|
|
|
// normalize bool union types
|
|
|
|
$phpParserNodeType = $this->normalizeConstantBooleanType($phpParserNodeType);
|
|
|
|
$phpStanDocType = $this->normalizeConstantBooleanType($phpStanDocType);
|
2021-12-14 15:31:59 +00:00
|
|
|
// is scalar replace by another - remove it?
|
|
|
|
$areDifferentScalarTypes = $this->scalarTypeComparator->areDifferentScalarTypes($phpParserNodeType, $phpStanDocType);
|
|
|
|
if (!$areDifferentScalarTypes && !$this->areTypesEqual($phpParserNodeType, $phpStanDocType)) {
|
2021-11-15 15:48:16 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2022-01-12 15:50:28 +00:00
|
|
|
if ($this->isTypeSelfAndDocParamTypeStatic($phpStanDocType, $phpParserNodeType, $phpStanDocTypeNode)) {
|
|
|
|
return \false;
|
|
|
|
}
|
2022-01-14 15:04:58 +00:00
|
|
|
if ($this->areTypesSameWithLiteralTypeInPhpDoc($areDifferentScalarTypes, $phpStanDocType, $phpParserNodeType)) {
|
|
|
|
return \false;
|
2021-11-15 15:48:16 +00:00
|
|
|
}
|
2022-05-11 05:14:30 +00:00
|
|
|
return $this->isThisTypeInFinalClass($phpStanDocType, $phpParserNodeType, $phpParserNode);
|
2020-12-05 00:31:05 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
public function isSubtype(Type $checkedType, Type $mainType) : bool
|
2021-01-10 18:57:16 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($mainType instanceof MixedType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-01-10 18:57:16 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$mainType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return $mainType->isSuperTypeOf($checkedType)->yes();
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$checkedType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return $mainType->isSuperTypeOf($checkedType)->yes();
|
2021-02-21 09:32:45 +00:00
|
|
|
}
|
2021-02-23 01:25:34 +00:00
|
|
|
return $this->arrayTypeComparator->isSubtype($checkedType, $mainType);
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function areAliasedObjectMatchingFqnObject(Type $firstType, Type $secondType) : bool
|
2020-01-31 08:18:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($firstType instanceof AliasedObjectType && $secondType instanceof ObjectType) {
|
2022-05-11 05:14:30 +00:00
|
|
|
return $firstType->getFullyQualifiedName() === $secondType->getClassName();
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$firstType instanceof ObjectType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-12-19 15:24:53 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$secondType instanceof AliasedObjectType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-12-19 15:24:53 +00:00
|
|
|
}
|
2021-10-27 23:25:15 +00:00
|
|
|
return $secondType->getFullyQualifiedName() === $firstType->getClassName();
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
|
|
|
/**
|
2020-05-12 15:20:40 +00:00
|
|
|
* E.g. class A extends B, class B → A[] is subtype of B[] → keep A[]
|
2020-01-31 08:18:45 +00:00
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
private function areArrayTypeWithSingleObjectChildToParent(Type $firstType, Type $secondType) : bool
|
2020-01-31 08:18:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$firstType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-12-24 16:28:56 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$secondType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
|
|
|
$firstArrayItemType = $firstType->getItemType();
|
|
|
|
$secondArrayItemType = $secondType->getItemType();
|
2021-03-09 15:49:33 +00:00
|
|
|
if ($this->isMutualObjectSubtypes($firstArrayItemType, $secondArrayItemType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2021-03-09 15:49:33 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$firstArrayItemType instanceof GenericClassStringType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-03-09 15:49:33 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$secondArrayItemType instanceof GenericClassStringType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-03-09 15:49:33 +00:00
|
|
|
}
|
|
|
|
// @todo resolve later better with template map, @see https://github.com/symplify/symplify/pull/3034/commits/4f6be8b87e52117b1aa1613b9b689ae958a9d6f4
|
2022-06-07 08:22:29 +00:00
|
|
|
return $firstArrayItemType->getGenericType() instanceof ObjectType && $secondArrayItemType->getGenericType() instanceof ObjectType;
|
2021-03-09 15:49:33 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isMutualObjectSubtypes(Type $firstArrayItemType, Type $secondArrayItemType) : bool
|
2021-03-09 15:49:33 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$firstArrayItemType instanceof ObjectType) {
|
2022-05-11 05:14:30 +00:00
|
|
|
return \false;
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$secondArrayItemType instanceof ObjectType) {
|
2022-05-11 05:14:30 +00:00
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
if ($firstArrayItemType->isSuperTypeOf($secondArrayItemType)->yes()) {
|
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
return $secondArrayItemType->isSuperTypeOf($firstArrayItemType)->yes();
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function normalizeSingleUnionType(Type $type) : Type
|
2021-04-04 09:01:11 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$type instanceof UnionType) {
|
2022-04-22 14:28:27 +00:00
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
$uniqueTypes = $this->typeFactory->uniquateTypes($type->getTypes());
|
|
|
|
if (\count($uniqueTypes) !== 1) {
|
|
|
|
return $type;
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2022-04-22 14:28:27 +00:00
|
|
|
return $uniqueTypes[0];
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function areArrayUnionConstantEqualTypes(Type $firstType, Type $secondType) : bool
|
2021-04-04 09:01:11 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$firstType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$secondType instanceof ArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($firstType instanceof ConstantArrayType || $secondType instanceof ConstantArrayType) {
|
2022-03-07 11:34:50 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2021-04-04 09:01:11 +00:00
|
|
|
$firstKeyType = $this->normalizeSingleUnionType($firstType->getKeyType());
|
|
|
|
$secondKeyType = $this->normalizeSingleUnionType($secondType->getKeyType());
|
|
|
|
// mixed and integer type are mutual replaceable in practise
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($firstKeyType instanceof MixedType) {
|
|
|
|
$firstKeyType = new IntegerType();
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($secondKeyType instanceof MixedType) {
|
|
|
|
$secondKeyType = new IntegerType();
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
if (!$this->areTypesEqual($firstKeyType, $secondKeyType)) {
|
|
|
|
return \false;
|
2021-04-04 09:01:11 +00:00
|
|
|
}
|
|
|
|
$firstArrayType = $this->normalizeSingleUnionType($firstType->getItemType());
|
|
|
|
$secondArrayType = $this->normalizeSingleUnionType($secondType->getItemType());
|
|
|
|
return $this->areTypesEqual($firstArrayType, $secondArrayType);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function normalizeConstantBooleanType(Type $type) : Type
|
2021-04-10 23:28:08 +00:00
|
|
|
{
|
2022-06-21 15:38:46 +00:00
|
|
|
return TypeTraverser::map($type, static function (Type $type, callable $callable) : Type {
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($type instanceof ConstantBooleanType) {
|
|
|
|
return new BooleanType();
|
2021-04-10 23:28:08 +00:00
|
|
|
}
|
|
|
|
return $callable($type);
|
|
|
|
});
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isTypeSelfAndDocParamTypeStatic(Type $phpStanDocType, Type $phpParserNodeType, TypeNode $phpStanDocTypeNode) : bool
|
2022-01-12 15:50:28 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
return $phpStanDocType instanceof StaticType && $phpParserNodeType instanceof ThisType && $phpStanDocTypeNode->getAttribute(PhpDocAttributeKey::PARENT) instanceof ParamTagValueNode;
|
2022-01-12 15:50:28 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function areTypesSameWithLiteralTypeInPhpDoc(bool $areDifferentScalarTypes, Type $phpStanDocType, Type $phpParserNodeType) : bool
|
2022-01-14 15:04:58 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
return $areDifferentScalarTypes && $phpStanDocType instanceof ConstantScalarType && $phpParserNodeType->isSuperTypeOf($phpStanDocType)->yes();
|
2022-01-14 15:04:58 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isThisTypeInFinalClass(Type $phpStanDocType, Type $phpParserNodeType, Node $node) : bool
|
2022-01-14 15:04:58 +00:00
|
|
|
{
|
2022-01-17 11:11:59 +00:00
|
|
|
/**
|
|
|
|
* Special case for $this/(self|static) compare
|
|
|
|
*
|
|
|
|
* $this refers to the exact object identity, not just the same type. Therefore, it's valid and should not be removed
|
|
|
|
* @see https://wiki.php.net/rfc/this_return_type for more context
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($phpStanDocType instanceof ThisType && $phpParserNodeType instanceof StaticType) {
|
2022-05-11 05:14:30 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$isStaticReturnDocTypeWithThisType = $phpStanDocType instanceof StaticType && $phpParserNodeType instanceof ThisType;
|
2022-05-11 05:14:30 +00:00
|
|
|
if (!$isStaticReturnDocTypeWithThisType) {
|
|
|
|
return \true;
|
|
|
|
}
|
2023-06-11 08:57:05 +00:00
|
|
|
$classReflection = $this->reflectionResolver->resolveClassReflection($node);
|
|
|
|
if (!$classReflection instanceof ClassReflection || !$classReflection->isClass()) {
|
2022-05-11 05:14:30 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2023-06-11 08:57:05 +00:00
|
|
|
return $classReflection->isFinalByKeyword();
|
2022-01-14 15:04:58 +00:00
|
|
|
}
|
2020-01-31 08:18:45 +00:00
|
|
|
}
|