2020-01-15 13:51:15 +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\PHPStanStaticTypeMapper\TypeMapper;
|
2020-01-15 13:51:15 +00:00
|
|
|
|
2022-06-06 17:12:56 +00:00
|
|
|
use PhpParser\Node;
|
2022-12-20 20:40:25 +00:00
|
|
|
use PhpParser\Node\Identifier;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
|
|
use PHPStan\Reflection\ReflectionProvider;
|
|
|
|
use PHPStan\Type\ArrayType;
|
|
|
|
use PHPStan\Type\ClassStringType;
|
|
|
|
use PHPStan\Type\Constant\ConstantArrayType;
|
|
|
|
use PHPStan\Type\Constant\ConstantIntegerType;
|
|
|
|
use PHPStan\Type\Generic\GenericClassStringType;
|
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\NeverType;
|
|
|
|
use PHPStan\Type\ObjectType;
|
|
|
|
use PHPStan\Type\Type;
|
|
|
|
use PHPStan\Type\UnionType;
|
|
|
|
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareUnionTypeNode;
|
|
|
|
use Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower;
|
|
|
|
use Rector\TypeDeclaration\NodeTypeAnalyzer\DetailedTypeAnalyzer;
|
|
|
|
use Rector\TypeDeclaration\TypeAnalyzer\GenericClassStringTypeNormalizer;
|
2023-06-01 08:56:46 +00:00
|
|
|
use RectorPrefix202306\Symfony\Contracts\Service\Attribute\Required;
|
2020-09-01 17:56:30 +00:00
|
|
|
/**
|
2021-04-22 21:36:50 +00:00
|
|
|
* @see \Rector\Tests\PHPStanStaticTypeMapper\TypeMapper\ArrayTypeMapperTest
|
2021-06-30 11:36:37 +00:00
|
|
|
*
|
|
|
|
* @implements TypeMapperInterface<ArrayType>
|
2020-09-01 17:56:30 +00:00
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
final class ArrayTypeMapper implements TypeMapperInterface
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2020-08-29 14:20:27 +00:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public const HAS_GENERIC_TYPE_PARENT = 'has_generic_type_parent';
|
2020-01-15 13:51:15 +00:00
|
|
|
/**
|
2021-05-12 13:15:45 +00:00
|
|
|
* @var \Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper
|
2020-01-15 13:51:15 +00:00
|
|
|
*/
|
|
|
|
private $phpStanStaticTypeMapper;
|
2021-02-20 00:14:58 +00:00
|
|
|
/**
|
2021-05-12 13:15:45 +00:00
|
|
|
* @var \Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower
|
2021-02-20 00:14:58 +00:00
|
|
|
*/
|
|
|
|
private $unionTypeCommonTypeNarrower;
|
|
|
|
/**
|
2021-08-23 00:20:32 +00:00
|
|
|
* @var \PHPStan\Reflection\ReflectionProvider
|
2021-02-20 00:14:58 +00:00
|
|
|
*/
|
2021-02-28 07:47:48 +00:00
|
|
|
private $reflectionProvider;
|
2021-09-17 19:38:24 +00:00
|
|
|
/**
|
|
|
|
* @var \Rector\TypeDeclaration\TypeAnalyzer\GenericClassStringTypeNormalizer
|
|
|
|
*/
|
|
|
|
private $genericClassStringTypeNormalizer;
|
|
|
|
/**
|
|
|
|
* @var \Rector\TypeDeclaration\NodeTypeAnalyzer\DetailedTypeAnalyzer
|
|
|
|
*/
|
|
|
|
private $detailedTypeAnalyzer;
|
2022-03-07 11:34:50 +00:00
|
|
|
/**
|
|
|
|
* @var \Rector\PHPStanStaticTypeMapper\TypeMapper\ArrayShapeTypeMapper
|
|
|
|
*/
|
|
|
|
private $arrayShapeTypeMapper;
|
2021-05-23 14:21:14 +00:00
|
|
|
// To avoid circular dependency
|
2020-01-28 16:39:05 +00:00
|
|
|
/**
|
|
|
|
* @required
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
public function autowire(PHPStanStaticTypeMapper $phpStanStaticTypeMapper, UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower, ReflectionProvider $reflectionProvider, GenericClassStringTypeNormalizer $genericClassStringTypeNormalizer, DetailedTypeAnalyzer $detailedTypeAnalyzer, \Rector\PHPStanStaticTypeMapper\TypeMapper\ArrayShapeTypeMapper $arrayShapeTypeMapper) : void
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2020-01-28 16:39:05 +00:00
|
|
|
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
|
2021-02-20 00:14:58 +00:00
|
|
|
$this->unionTypeCommonTypeNarrower = $unionTypeCommonTypeNarrower;
|
2021-02-28 07:47:48 +00:00
|
|
|
$this->reflectionProvider = $reflectionProvider;
|
2021-09-17 19:38:24 +00:00
|
|
|
$this->genericClassStringTypeNormalizer = $genericClassStringTypeNormalizer;
|
|
|
|
$this->detailedTypeAnalyzer = $detailedTypeAnalyzer;
|
2022-03-07 11:34:50 +00:00
|
|
|
$this->arrayShapeTypeMapper = $arrayShapeTypeMapper;
|
2020-01-28 16:39:05 +00:00
|
|
|
}
|
2021-03-06 21:16:18 +00:00
|
|
|
/**
|
|
|
|
* @return class-string<Type>
|
|
|
|
*/
|
2021-05-09 20:15:43 +00:00
|
|
|
public function getNodeClass() : string
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
return ArrayType::class;
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
/**
|
2022-05-29 22:41:07 +00:00
|
|
|
* @param TypeKind::* $typeKind
|
2021-12-10 10:22:23 +00:00
|
|
|
* @param ArrayType $type
|
2020-01-15 13:51:15 +00:00
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
public function mapToPHPStanPhpDocTypeNode(Type $type, string $typeKind) : TypeNode
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2020-08-29 08:41:19 +00:00
|
|
|
$itemType = $type->getItemType();
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($itemType instanceof UnionType && !$type instanceof ConstantArrayType) {
|
2021-07-04 13:13:54 +00:00
|
|
|
return $this->createArrayTypeNodeFromUnionType($itemType, $typeKind);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($type instanceof ConstantArrayType && $typeKind === TypeKind::RETURN) {
|
2022-03-07 11:34:50 +00:00
|
|
|
$arrayShapeNode = $this->arrayShapeTypeMapper->mapConstantArrayType($type);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayShapeNode instanceof TypeNode) {
|
2022-03-07 11:34:50 +00:00
|
|
|
return $arrayShapeNode;
|
|
|
|
}
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($itemType instanceof ArrayType && $this->isGenericArrayCandidate($itemType)) {
|
2021-07-04 13:13:54 +00:00
|
|
|
return $this->createGenericArrayType($type, $typeKind, \true);
|
2020-08-29 14:20:27 +00:00
|
|
|
}
|
2021-02-20 00:14:58 +00:00
|
|
|
if ($this->isGenericArrayCandidate($type)) {
|
2021-07-04 13:13:54 +00:00
|
|
|
return $this->createGenericArrayType($type, $typeKind, \true);
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2021-07-04 13:13:54 +00:00
|
|
|
$narrowedTypeNode = $this->narrowConstantArrayTypeOfUnionType($type, $itemType, $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($narrowedTypeNode instanceof TypeNode) {
|
2021-02-20 00:14:58 +00:00
|
|
|
return $narrowedTypeNode;
|
|
|
|
}
|
2021-07-04 13:13:54 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($itemType, $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new SpacingAwareArrayTypeNode($itemTypeNode);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
/**
|
2021-12-10 10:22:23 +00:00
|
|
|
* @param ArrayType $type
|
2020-01-15 13:51:15 +00:00
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
public function mapToPhpParserNode(Type $type, string $typeKind) : ?Node
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2022-12-20 20:40:25 +00:00
|
|
|
return new Identifier('array');
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2022-05-29 22:41:07 +00:00
|
|
|
/**
|
|
|
|
* @param TypeKind::* $typeKind
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
private function createArrayTypeNodeFromUnionType(UnionType $unionType, string $typeKind) : SpacingAwareArrayTypeNode
|
2020-08-29 08:41:19 +00:00
|
|
|
{
|
2020-01-15 13:51:15 +00:00
|
|
|
$unionedArrayType = [];
|
2020-08-29 08:41:19 +00:00
|
|
|
foreach ($unionType->getTypes() as $unionedType) {
|
2021-07-04 13:13:54 +00:00
|
|
|
$typeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($unionedType, $typeKind);
|
2020-08-29 08:41:19 +00:00
|
|
|
$unionedArrayType[(string) $typeNode] = $typeNode;
|
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
if (\count($unionedArrayType) > 1) {
|
2022-06-07 08:22:29 +00:00
|
|
|
return new SpacingAwareArrayTypeNode(new BracketsAwareUnionTypeNode($unionedArrayType));
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2020-08-29 08:41:19 +00:00
|
|
|
/** @var TypeNode $arrayType */
|
2021-05-09 20:15:43 +00:00
|
|
|
$arrayType = \array_shift($unionedArrayType);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new SpacingAwareArrayTypeNode($arrayType);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isGenericArrayCandidate(ArrayType $arrayType) : bool
|
2020-08-11 11:58:03 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof MixedType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2021-03-09 23:00:45 +00:00
|
|
|
if ($this->isClassStringArrayType($arrayType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2020-08-28 05:00:03 +00:00
|
|
|
// skip simple arrays, like "string[]", from converting to obvious "array<int, string>"
|
|
|
|
if ($this->isIntegerKeyAndNonNestedArray($arrayType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-28 05:00:03 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof NeverType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
|
|
|
// make sure the integer key type is not natural/implicit array int keys
|
|
|
|
$keysArrayType = $arrayType->getKeysArray();
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$keysArrayType instanceof ConstantArrayType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
|
|
|
foreach ($keysArrayType->getValueTypes() as $key => $keyType) {
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$keyType instanceof ConstantIntegerType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
|
|
|
if ($key !== $keyType->getValue()) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \true;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2022-05-29 22:41:07 +00:00
|
|
|
/**
|
|
|
|
* @param TypeKind::* $typeKind
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
private function createGenericArrayType(ArrayType $arrayType, string $typeKind, bool $withKey = \false) : GenericTypeNode
|
2020-08-11 11:58:03 +00:00
|
|
|
{
|
2021-09-17 19:38:24 +00:00
|
|
|
$itemType = $arrayType->getItemType();
|
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($itemType, $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
$identifierTypeNode = new IdentifierTypeNode('array');
|
2021-03-09 23:00:45 +00:00
|
|
|
// is class-string[] list only
|
|
|
|
if ($this->isClassStringArrayType($arrayType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
$withKey = \false;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2020-08-29 08:41:19 +00:00
|
|
|
if ($withKey) {
|
2021-07-04 13:13:54 +00:00
|
|
|
$keyTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType->getKeyType(), $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($itemTypeNode instanceof BracketsAwareUnionTypeNode && $this->isPairClassTooDetailed($itemType)) {
|
|
|
|
$genericTypes = [$keyTypeNode, $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode(new ClassStringType(), $typeKind)];
|
2021-09-17 19:38:24 +00:00
|
|
|
} else {
|
|
|
|
$genericTypes = [$keyTypeNode, $itemTypeNode];
|
|
|
|
}
|
2020-08-29 08:41:19 +00:00
|
|
|
} else {
|
|
|
|
$genericTypes = [$itemTypeNode];
|
|
|
|
}
|
2020-08-11 11:58:03 +00:00
|
|
|
// @see https://github.com/phpstan/phpdoc-parser/blob/98a088b17966bdf6ee25c8a4b634df313d8aa531/tests/PHPStan/Parser/PhpDocParserTest.php#L2692-L2696
|
2020-08-29 14:20:27 +00:00
|
|
|
foreach ($genericTypes as $genericType) {
|
2021-08-23 00:20:32 +00:00
|
|
|
/** @var \PHPStan\PhpDocParser\Ast\Node $genericType */
|
2020-08-29 14:20:27 +00:00
|
|
|
$genericType->setAttribute(self::HAS_GENERIC_TYPE_PARENT, $withKey);
|
|
|
|
}
|
2021-03-19 14:33:58 +00:00
|
|
|
$identifierTypeNode->setAttribute(self::HAS_GENERIC_TYPE_PARENT, $withKey);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new GenericTypeNode($identifierTypeNode, $genericTypes);
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isPairClassTooDetailed(Type $itemType) : bool
|
2021-09-17 19:38:24 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$itemType instanceof UnionType) {
|
2021-09-17 19:38:24 +00:00
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
if (!$this->genericClassStringTypeNormalizer->isAllGenericClassStringType($itemType)) {
|
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
return $this->detailedTypeAnalyzer->isTooDetailed($itemType);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isIntegerKeyAndNonNestedArray(ArrayType $arrayType) : bool
|
2020-08-28 05:00:03 +00:00
|
|
|
{
|
2023-04-09 00:51:38 +00:00
|
|
|
if (!$arrayType->getKeyType()->isInteger()->yes()) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-28 05:00:03 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return !$arrayType->getItemType() instanceof ArrayType;
|
2020-08-28 05:00:03 +00:00
|
|
|
}
|
2022-05-29 22:41:07 +00:00
|
|
|
/**
|
|
|
|
* @param TypeKind::* $typeKind
|
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
private function narrowConstantArrayTypeOfUnionType(ArrayType $arrayType, Type $itemType, string $typeKind) : ?TypeNode
|
2021-02-20 00:14:58 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayType instanceof ConstantArrayType && $itemType instanceof UnionType) {
|
2021-02-20 00:14:58 +00:00
|
|
|
$narrowedItemType = $this->unionTypeCommonTypeNarrower->narrowToSharedObjectType($itemType);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($narrowedItemType instanceof ObjectType) {
|
2021-07-04 13:13:54 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($narrowedItemType, $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new SpacingAwareArrayTypeNode($itemTypeNode);
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
|
|
|
$narrowedItemType = $this->unionTypeCommonTypeNarrower->narrowToGenericClassStringType($itemType);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($narrowedItemType instanceof GenericClassStringType) {
|
2021-07-04 13:13:54 +00:00
|
|
|
return $this->createTypeNodeFromGenericClassStringType($narrowedItemType, $typeKind);
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2021-09-17 19:38:24 +00:00
|
|
|
/**
|
2022-05-29 22:41:07 +00:00
|
|
|
* @param TypeKind::* $typeKind
|
2022-04-26 08:13:18 +00:00
|
|
|
* @return \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode|\PHPStan\PhpDocParser\Ast\Type\GenericTypeNode
|
2021-09-17 19:38:24 +00:00
|
|
|
*/
|
2022-06-07 08:22:29 +00:00
|
|
|
private function createTypeNodeFromGenericClassStringType(GenericClassStringType $genericClassStringType, string $typeKind)
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2021-02-20 00:14:58 +00:00
|
|
|
$genericType = $genericClassStringType->getGenericType();
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($genericType instanceof ObjectType && !$this->reflectionProvider->hasClass($genericType->getClassName())) {
|
|
|
|
return new IdentifierTypeNode($genericType->getClassName());
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
2021-07-04 13:13:54 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($genericClassStringType, $typeKind);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new GenericTypeNode(new IdentifierTypeNode('array'), [$itemTypeNode]);
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
private function isClassStringArrayType(ArrayType $arrayType) : bool
|
2021-03-09 23:00:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof MixedType) {
|
|
|
|
return $arrayType->getItemType() instanceof GenericClassStringType;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof ConstantIntegerType) {
|
|
|
|
return $arrayType->getItemType() instanceof GenericClassStringType;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|