2020-01-15 13:51:15 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Rector\PHPStanStaticTypeMapper\TypeMapper;
|
|
|
|
|
|
|
|
use PhpParser\Node;
|
2020-05-10 21:02:46 +00:00
|
|
|
use PhpParser\Node\Name;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
2020-08-11 11:58:03 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
|
|
|
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
|
|
|
use PHPStan\Type\ArrayType;
|
2020-08-11 11:58:03 +00:00
|
|
|
use PHPStan\Type\Constant\ConstantArrayType;
|
|
|
|
use PHPStan\Type\Constant\ConstantIntegerType;
|
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\NeverType;
|
2020-01-15 13:51:15 +00:00
|
|
|
use PHPStan\Type\Type;
|
|
|
|
use PHPStan\Type\UnionType;
|
2020-08-11 11:58:03 +00:00
|
|
|
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareGenericTypeNode;
|
|
|
|
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
|
2020-01-15 13:51:15 +00:00
|
|
|
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface;
|
|
|
|
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;
|
2020-05-05 15:22:54 +00:00
|
|
|
use Rector\TypeDeclaration\TypeNormalizer;
|
2020-01-15 13:51:15 +00:00
|
|
|
|
|
|
|
final class ArrayTypeMapper implements TypeMapperInterface
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var PHPStanStaticTypeMapper
|
|
|
|
*/
|
|
|
|
private $phpStanStaticTypeMapper;
|
|
|
|
|
2020-05-05 15:22:54 +00:00
|
|
|
/**
|
|
|
|
* @var TypeNormalizer
|
|
|
|
*/
|
|
|
|
private $typeNormalizer;
|
|
|
|
|
2020-01-28 16:39:05 +00:00
|
|
|
/**
|
|
|
|
* @required
|
|
|
|
*/
|
2020-05-05 15:22:54 +00:00
|
|
|
public function autowireArrayTypeMapper(
|
|
|
|
PHPStanStaticTypeMapper $phpStanStaticTypeMapper,
|
|
|
|
TypeNormalizer $typeNormalizer
|
|
|
|
): void {
|
2020-01-28 16:39:05 +00:00
|
|
|
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
|
2020-05-05 15:22:54 +00:00
|
|
|
$this->typeNormalizer = $typeNormalizer;
|
2020-01-28 16:39:05 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 13:51:15 +00:00
|
|
|
public function getNodeClass(): string
|
|
|
|
{
|
|
|
|
return ArrayType::class;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ArrayType $type
|
|
|
|
*/
|
|
|
|
public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode
|
|
|
|
{
|
|
|
|
$itemTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($type->getItemType());
|
|
|
|
|
|
|
|
if ($itemTypeNode instanceof UnionTypeNode) {
|
|
|
|
return $this->convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes($itemTypeNode);
|
|
|
|
}
|
|
|
|
|
2020-08-11 11:58:03 +00:00
|
|
|
if ($this->isGenericArrayCandidate($type)) {
|
|
|
|
return $this->createGenericArrayType($type->getKeyType(), $itemTypeNode);
|
|
|
|
}
|
|
|
|
|
2020-01-15 13:51:15 +00:00
|
|
|
return new ArrayTypeNode($itemTypeNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ArrayType $type
|
|
|
|
*/
|
|
|
|
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
|
|
|
|
{
|
2020-05-10 21:02:46 +00:00
|
|
|
return new Name('array');
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ArrayType $type
|
|
|
|
*/
|
|
|
|
public function mapToDocString(Type $type, ?Type $parentType = null): string
|
|
|
|
{
|
|
|
|
$itemType = $type->getItemType();
|
|
|
|
|
2020-05-05 15:22:54 +00:00
|
|
|
$normalizedType = $this->typeNormalizer->normalizeArrayOfUnionToUnionArray($type);
|
|
|
|
if ($normalizedType instanceof UnionType) {
|
|
|
|
return $this->mapArrayUnionTypeToDocString($type, $normalizedType);
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 15:22:54 +00:00
|
|
|
return $this->phpStanStaticTypeMapper->mapToDocString($itemType, $parentType) . '[]';
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes(
|
|
|
|
UnionTypeNode $unionTypeNode
|
|
|
|
): AttributeAwareUnionTypeNode {
|
|
|
|
$unionedArrayType = [];
|
|
|
|
foreach ($unionTypeNode->types as $unionedType) {
|
|
|
|
if ($unionedType instanceof UnionTypeNode) {
|
|
|
|
foreach ($unionedType->types as $key => $subUnionedType) {
|
|
|
|
$unionedType->types[$key] = new ArrayTypeNode($subUnionedType);
|
|
|
|
}
|
|
|
|
|
|
|
|
$unionedArrayType[] = $unionedType;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$unionedArrayType[] = new ArrayTypeNode($unionedType);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new AttributeAwareUnionTypeNode($unionedArrayType);
|
|
|
|
}
|
2020-02-01 16:04:38 +00:00
|
|
|
|
2020-08-11 11:58:03 +00:00
|
|
|
private function isGenericArrayCandidate(ArrayType $arrayType): bool
|
|
|
|
{
|
|
|
|
if ($arrayType->getKeyType() instanceof MixedType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($arrayType->getKeyType() instanceof NeverType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the integer key type is not natural/implicit array int keys
|
|
|
|
$keysArrayType = $arrayType->getKeysArray();
|
|
|
|
if (! $keysArrayType instanceof ConstantArrayType) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($keysArrayType->getValueTypes() as $key => $keyType) {
|
|
|
|
if (! $keyType instanceof ConstantIntegerType) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($key !== $keyType->getValue()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function createGenericArrayType(Type $keyType, TypeNode $itemTypeNode): AttributeAwareGenericTypeNode
|
|
|
|
{
|
|
|
|
/** @var IdentifierTypeNode $keyTypeNode */
|
|
|
|
$keyTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($keyType);
|
|
|
|
|
|
|
|
$attributeAwareIdentifierTypeNode = new AttributeAwareIdentifierTypeNode('array');
|
|
|
|
|
|
|
|
// @see https://github.com/phpstan/phpdoc-parser/blob/98a088b17966bdf6ee25c8a4b634df313d8aa531/tests/PHPStan/Parser/PhpDocParserTest.php#L2692-L2696
|
|
|
|
$genericTypes = [$keyTypeNode, $itemTypeNode];
|
|
|
|
return new AttributeAwareGenericTypeNode($attributeAwareIdentifierTypeNode, $genericTypes);
|
|
|
|
}
|
|
|
|
|
2020-02-01 16:04:38 +00:00
|
|
|
private function mapArrayUnionTypeToDocString(ArrayType $arrayType, UnionType $unionType): string
|
|
|
|
{
|
|
|
|
$unionedTypesAsString = [];
|
|
|
|
|
|
|
|
foreach ($unionType->getTypes() as $unionedArrayItemType) {
|
|
|
|
$unionedTypesAsString[] = $this->phpStanStaticTypeMapper->mapToDocString(
|
|
|
|
$unionedArrayItemType,
|
|
|
|
$arrayType
|
2020-05-05 15:22:54 +00:00
|
|
|
);
|
2020-02-01 16:04:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$unionedTypesAsString = array_values($unionedTypesAsString);
|
|
|
|
$unionedTypesAsString = array_unique($unionedTypesAsString);
|
|
|
|
|
|
|
|
return implode('|', $unionedTypesAsString);
|
|
|
|
}
|
2020-01-15 13:51:15 +00:00
|
|
|
}
|