2020-01-15 13:51:15 +00:00
|
|
|
<?php
|
|
|
|
|
2021-05-09 20:15:43 +00:00
|
|
|
declare (strict_types=1);
|
2020-01-15 13:51:15 +00:00
|
|
|
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
|
|
|
|
|
|
|
|
use PhpParser\Node;
|
2020-05-10 21:02:46 +00:00
|
|
|
use PhpParser\Node\Name;
|
2021-03-19 14:33:58 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
2021-02-28 07:47:48 +00:00
|
|
|
use PHPStan\Reflection\ReflectionProvider;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\Type\ArrayType;
|
2020-08-11 11:58:03 +00:00
|
|
|
use PHPStan\Type\Constant\ConstantArrayType;
|
|
|
|
use PHPStan\Type\Constant\ConstantIntegerType;
|
2021-02-20 00:14:58 +00:00
|
|
|
use PHPStan\Type\Generic\GenericClassStringType;
|
2020-08-28 05:00:03 +00:00
|
|
|
use PHPStan\Type\IntegerType;
|
2020-08-11 11:58:03 +00:00
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\NeverType;
|
2021-02-20 00:14:58 +00:00
|
|
|
use PHPStan\Type\ObjectType;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\Type\Type;
|
|
|
|
use PHPStan\Type\UnionType;
|
2021-03-20 15:27:18 +00:00
|
|
|
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareUnionTypeNode;
|
|
|
|
use Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode;
|
2020-01-15 13:51:15 +00:00
|
|
|
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
2021-02-20 00:14:58 +00:00
|
|
|
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower;
|
2020-09-01 17:56:30 +00:00
|
|
|
/**
|
2021-04-22 21:36:50 +00:00
|
|
|
* @see \Rector\Tests\PHPStanStaticTypeMapper\TypeMapper\ArrayTypeMapperTest
|
2020-09-01 17:56:30 +00:00
|
|
|
*/
|
2021-05-10 22:23:08 +00:00
|
|
|
final class ArrayTypeMapper implements \Rector\PHPStanStaticTypeMapper\Contract\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
|
|
|
/**
|
|
|
|
* @var PHPStanStaticTypeMapper
|
|
|
|
*/
|
|
|
|
private $phpStanStaticTypeMapper;
|
2021-02-20 00:14:58 +00:00
|
|
|
/**
|
|
|
|
* @var UnionTypeCommonTypeNarrower
|
|
|
|
*/
|
|
|
|
private $unionTypeCommonTypeNarrower;
|
|
|
|
/**
|
2021-02-28 07:47:48 +00:00
|
|
|
* @var ReflectionProvider
|
2021-02-20 00:14:58 +00:00
|
|
|
*/
|
2021-02-28 07:47:48 +00:00
|
|
|
private $reflectionProvider;
|
2020-01-28 16:39:05 +00:00
|
|
|
/**
|
2021-02-28 07:47:48 +00:00
|
|
|
* To avoid circular dependency
|
2020-01-28 16:39:05 +00:00
|
|
|
* @required
|
|
|
|
*/
|
2021-05-10 22:23:08 +00:00
|
|
|
public function autowireArrayTypeMapper(\Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper $phpStanStaticTypeMapper, \Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower, \PHPStan\Reflection\ReflectionProvider $reflectionProvider) : 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;
|
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
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
return \PHPStan\Type\ArrayType::class;
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param ArrayType $type
|
|
|
|
*/
|
2021-05-10 22:23:08 +00:00
|
|
|
public function mapToPHPStanPhpDocTypeNode(\PHPStan\Type\Type $type) : \PHPStan\PhpDocParser\Ast\Type\TypeNode
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2020-08-29 08:41:19 +00:00
|
|
|
$itemType = $type->getItemType();
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($itemType instanceof \PHPStan\Type\UnionType && !$type instanceof \PHPStan\Type\Constant\ConstantArrayType) {
|
2021-02-20 00:14:58 +00:00
|
|
|
return $this->createArrayTypeNodeFromUnionType($itemType);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($itemType instanceof \PHPStan\Type\ArrayType && $this->isGenericArrayCandidate($itemType)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return $this->createGenericArrayType($type, \true);
|
2020-08-29 14:20:27 +00:00
|
|
|
}
|
2021-02-20 00:14:58 +00:00
|
|
|
if ($this->isGenericArrayCandidate($type)) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return $this->createGenericArrayType($type, \true);
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2021-02-20 00:14:58 +00:00
|
|
|
$narrowedTypeNode = $this->narrowConstantArrayTypeOfUnionType($type, $itemType);
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($narrowedTypeNode instanceof \PHPStan\PhpDocParser\Ast\Type\TypeNode) {
|
2021-02-20 00:14:58 +00:00
|
|
|
return $narrowedTypeNode;
|
|
|
|
}
|
2020-08-29 08:41:19 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($itemType);
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode($itemTypeNode);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param ArrayType $type
|
|
|
|
*/
|
2021-05-10 22:23:08 +00:00
|
|
|
public function mapToPhpParserNode(\PHPStan\Type\Type $type, ?string $kind = null) : ?\PhpParser\Node
|
2020-01-15 13:51:15 +00:00
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \PhpParser\Node\Name('array');
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function createArrayTypeNodeFromUnionType(\PHPStan\Type\UnionType $unionType) : \Rector\BetterPhpDocParser\ValueObject\Type\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) {
|
|
|
|
$typeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($unionedType);
|
|
|
|
$unionedArrayType[(string) $typeNode] = $typeNode;
|
|
|
|
}
|
2021-05-09 20:15:43 +00:00
|
|
|
if (\count($unionedArrayType) > 1) {
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode(new \Rector\BetterPhpDocParser\ValueObject\Type\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);
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode($arrayType);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function isGenericArrayCandidate(\PHPStan\Type\ArrayType $arrayType) : bool
|
2020-08-11 11:58:03 +00:00
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof \PHPStan\Type\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
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof \PHPStan\Type\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();
|
2021-05-10 22:23:08 +00:00
|
|
|
if (!$keysArrayType instanceof \PHPStan\Type\Constant\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) {
|
2021-05-10 22:23:08 +00:00
|
|
|
if (!$keyType instanceof \PHPStan\Type\Constant\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
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function createGenericArrayType(\PHPStan\Type\ArrayType $arrayType, bool $withKey = \false) : \PHPStan\PhpDocParser\Ast\Type\GenericTypeNode
|
2020-08-11 11:58:03 +00:00
|
|
|
{
|
2020-08-29 08:41:19 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType->getItemType());
|
2021-05-10 22:23:08 +00:00
|
|
|
$identifierTypeNode = new \PHPStan\PhpDocParser\Ast\Type\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) {
|
|
|
|
$keyTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($arrayType->getKeyType());
|
|
|
|
$genericTypes = [$keyTypeNode, $itemTypeNode];
|
|
|
|
} 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-03-19 14:33:58 +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);
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \PHPStan\PhpDocParser\Ast\Type\GenericTypeNode($identifierTypeNode, $genericTypes);
|
2020-08-11 11:58:03 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function isIntegerKeyAndNonNestedArray(\PHPStan\Type\ArrayType $arrayType) : bool
|
2020-08-28 05:00:03 +00:00
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
if (!$arrayType->getKeyType() instanceof \PHPStan\Type\IntegerType) {
|
2021-05-09 20:15:43 +00:00
|
|
|
return \false;
|
2020-08-28 05:00:03 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
return !$arrayType->getItemType() instanceof \PHPStan\Type\ArrayType;
|
2020-08-28 05:00:03 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function narrowConstantArrayTypeOfUnionType(\PHPStan\Type\ArrayType $arrayType, \PHPStan\Type\Type $itemType) : ?\PHPStan\PhpDocParser\Ast\Type\TypeNode
|
2021-02-20 00:14:58 +00:00
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($arrayType instanceof \PHPStan\Type\Constant\ConstantArrayType && $itemType instanceof \PHPStan\Type\UnionType) {
|
2021-02-20 00:14:58 +00:00
|
|
|
$narrowedItemType = $this->unionTypeCommonTypeNarrower->narrowToSharedObjectType($itemType);
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($narrowedItemType instanceof \PHPStan\Type\ObjectType) {
|
2021-02-20 00:14:58 +00:00
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($narrowedItemType);
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \Rector\BetterPhpDocParser\ValueObject\Type\SpacingAwareArrayTypeNode($itemTypeNode);
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
|
|
|
$narrowedItemType = $this->unionTypeCommonTypeNarrower->narrowToGenericClassStringType($itemType);
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($narrowedItemType instanceof \PHPStan\Type\Generic\GenericClassStringType) {
|
2021-02-20 00:14:58 +00:00
|
|
|
return $this->createTypeNodeFromGenericClassStringType($narrowedItemType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function createTypeNodeFromGenericClassStringType(\PHPStan\Type\Generic\GenericClassStringType $genericClassStringType) : \PHPStan\PhpDocParser\Ast\Type\TypeNode
|
2021-05-09 20:15:43 +00:00
|
|
|
{
|
2021-02-20 00:14:58 +00:00
|
|
|
$genericType = $genericClassStringType->getGenericType();
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($genericType instanceof \PHPStan\Type\ObjectType && !$this->reflectionProvider->hasClass($genericType->getClassName())) {
|
|
|
|
return new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode($genericType->getClassName());
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($genericClassStringType);
|
2021-05-10 22:23:08 +00:00
|
|
|
return new \PHPStan\PhpDocParser\Ast\Type\GenericTypeNode(new \PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode('array'), [$itemTypeNode]);
|
2021-02-20 00:14:58 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
private function isClassStringArrayType(\PHPStan\Type\ArrayType $arrayType) : bool
|
2021-03-09 23:00:45 +00:00
|
|
|
{
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof \PHPStan\Type\MixedType) {
|
|
|
|
return $arrayType->getItemType() instanceof \PHPStan\Type\Generic\GenericClassStringType;
|
2021-03-09 23:00:45 +00:00
|
|
|
}
|
2021-05-10 22:23:08 +00:00
|
|
|
if ($arrayType->getKeyType() instanceof \PHPStan\Type\Constant\ConstantIntegerType) {
|
|
|
|
return $arrayType->getItemType() instanceof \PHPStan\Type\Generic\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
|
|
|
}
|