Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions packages/NodeTypeResolver/src/StaticTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use PHPStan\Type\BooleanType;
use PHPStan\Type\CallableType;
use PHPStan\Type\ClosureType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
Expand Down Expand Up @@ -480,17 +481,15 @@ public function createTypeHash(Type $type): string
return $type->getClassName();
}

return $this->mapPHPStanTypeToDocString($type);
}
if ($type instanceof ConstantType) {
if (method_exists($type, 'getValue')) {
return get_class($type) . $type->getValue();
}

public function mapStringToPHPStanType(string $newSimpleType): Type
{
$phpParserNode = $this->mapStringToPhpParserNode($newSimpleType);
if ($phpParserNode === null) {
return new MixedType();
throw new ShouldNotHappenException();
}

return $this->mapPhpParserNodePHPStanType($phpParserNode);
return $this->mapPHPStanTypeToDocString($type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,14 @@ private function shouldSkip(ClassMethod $classMethod): bool

private function shouldSkipType(Type $newType, ClassMethod $classMethod): bool
{
if (! $newType instanceof ArrayType && ! $newType instanceof UnionType) {
return true;
}

if ($newType instanceof ArrayType) {
if ($this->isNewAndCurrentTypeBothCallable($newType, $classMethod)) {
if ($this->shouldSkipArrayType($newType, $classMethod)) {
return true;
}
}

if ($this->isMixedOfSpecificOverride($newType, $classMethod)) {
if ($newType instanceof UnionType) {
if (count($newType->getTypes()) > self::MAX_NUMBER_OF_TYPES) {
return true;
}
}
Expand Down Expand Up @@ -191,4 +189,17 @@ private function isMixedOfSpecificOverride(ArrayType $arrayType, ClassMethod $cl

return true;
}

private function shouldSkipArrayType(ArrayType $arrayType, ClassMethod $classMethod): bool
{
if ($this->isNewAndCurrentTypeBothCallable($arrayType, $classMethod)) {
return true;
}

if ($this->isMixedOfSpecificOverride($arrayType, $classMethod)) {
return true;
}

return false;
}
}
43 changes: 9 additions & 34 deletions packages/TypeDeclaration/src/TypeInferer/ReturnTypeInferer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@
namespace Rector\TypeDeclaration\TypeInferer;

use PhpParser\Node\FunctionLike;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
use Rector\TypeDeclaration\TypeNormalizer;

final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
{
Expand All @@ -22,17 +19,17 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
private $returnTypeInferers = [];

/**
* @var TypeFactory
* @var TypeNormalizer
*/
private $typeFactory;
private $typeNormalizer;

/**
* @param ReturnTypeInfererInterface[] $returnTypeInferers
*/
public function __construct(TypeFactory $typeFactory, array $returnTypeInferers)
public function __construct(array $returnTypeInferers, TypeNormalizer $typeNormalizer)
{
$this->returnTypeInferers = $this->sortTypeInferersByPriority($returnTypeInferers);
$this->typeFactory = $typeFactory;
$this->typeNormalizer = $typeNormalizer;
}

public function inferFunctionLike(FunctionLike $functionLike): Type
Expand All @@ -51,7 +48,10 @@ public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike
}

$type = $returnTypeInferer->inferFunctionLike($functionLike);
$type = $this->normalizeArrayTypeAndArrayNever($type);

$type = $this->typeNormalizer->normalizeArrayTypeAndArrayNever($type);
$type = $this->typeNormalizer->uniqueateConstantArrayType($type);
$type = $this->typeNormalizer->normalizeArrayOfUnionToUnionArray($type);

if (! $type instanceof MixedType) {
return $type;
Expand Down Expand Up @@ -87,29 +87,4 @@ private function ensureIsTypeInferer(string $excludedInferer): void

throw new ShouldNotHappenException();
}

/**
* From "string[]|mixed[]" based on empty array to to "string[]"
*/
private function normalizeArrayTypeAndArrayNever(Type $type): Type
{
if (! $type instanceof UnionType) {
return $type;
}

$nonNeverTypes = [];
foreach ($type->getTypes() as $unionedType) {
if (! $unionedType instanceof ArrayType) {
return $type;
}

if ($unionedType->getItemType() instanceof NeverType) {
continue;
}

$nonNeverTypes[] = $unionedType;
}

return $this->typeFactory->createMixedPassedOrUnionType($nonNeverTypes);
}
}
174 changes: 174 additions & 0 deletions packages/TypeDeclaration/src/TypeNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclaration;

use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\NodeTypeResolver\StaticTypeMapper;
use Rector\PHPStan\TypeFactoryStaticHelper;
use Rector\TypeDeclaration\ValueObject\NestedArrayTypeValueObject;

final class TypeNormalizer
{
/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;

/**
* @var TypeFactory
*/
private $typeFactory;

/**
* @var NestedArrayTypeValueObject[]
*/
private $collectedNestedArrayTypes = [];

public function __construct(StaticTypeMapper $staticTypeMapper, TypeFactory $typeFactory)
{
$this->staticTypeMapper = $staticTypeMapper;
$this->typeFactory = $typeFactory;
}

/**
* Turn nested array union types to unique ones:
* e.g. int[]|string[][]|bool[][]|string[][]
* ↓
* int[]|string[][]|bool[][]
*/
public function normalizeArrayOfUnionToUnionArray(Type $type, int $arrayNesting = 1): Type
{
if (! $type instanceof ArrayType) {
return $type;
}

// first collection of types
if ($arrayNesting === 1) {
$this->collectedNestedArrayTypes = [];
}

if ($type->getItemType() instanceof ArrayType) {
++$arrayNesting;
$this->normalizeArrayOfUnionToUnionArray($type->getItemType(), $arrayNesting);
} elseif ($type->getItemType() instanceof UnionType) {
$this->collectNestedArrayTypeFromUnionType($type->getItemType(), $arrayNesting);
} else {
$this->collectedNestedArrayTypes[] = new NestedArrayTypeValueObject(
$type->getItemType(),
$arrayNesting
);
}

return $this->createUnionedTypesFromArrayTypes($this->collectedNestedArrayTypes);
}

public function uniqueateConstantArrayType(Type $type): Type
{
if (! $type instanceof ConstantArrayType) {
return $type;
}

// nothing to normalize
if ($type->getValueTypes() === []) {
return $type;
}

$uniqueTypes = [];
$removedKeys = [];
foreach ($type->getValueTypes() as $key => $valueType) {
$typeHash = $this->staticTypeMapper->createTypeHash($valueType);

$valueType = $this->uniqueateConstantArrayType($valueType);
$valueType = $this->normalizeArrayOfUnionToUnionArray($valueType);

if (! isset($uniqueTypes[$typeHash])) {
$uniqueTypes[$typeHash] = $valueType;
} else {
$removedKeys[] = $key;
}
}

// re-index keys
$uniqueTypes = array_values($uniqueTypes);

$keyTypes = [];
foreach ($type->getKeyTypes() as $key => $keyType) {
if (in_array($key, $removedKeys, true)) {
// remove it
continue;
}

$keyTypes[$key] = $keyType;
}

return new ConstantArrayType($keyTypes, $uniqueTypes);
}

/**
* From "string[]|mixed[]" based on empty array to to "string[]"
*/
public function normalizeArrayTypeAndArrayNever(Type $type): Type
{
if (! $type instanceof UnionType) {
return $type;
}

$nonNeverTypes = [];
foreach ($type->getTypes() as $unionedType) {
if (! $unionedType instanceof ArrayType) {
return $type;
}

if ($unionedType->getItemType() instanceof NeverType) {
continue;
}

$nonNeverTypes[] = $unionedType;
}

return $this->typeFactory->createMixedPassedOrUnionType($nonNeverTypes);
}

/**
* @param NestedArrayTypeValueObject[] $collectedNestedArrayTypes
*/
private function createUnionedTypesFromArrayTypes(array $collectedNestedArrayTypes): Type
{
$unionedTypes = [];
foreach ($collectedNestedArrayTypes as $collectedNestedArrayType) {
$arrayType = $collectedNestedArrayType->getType();
for ($i = 0; $i < $collectedNestedArrayType->getArrayNestingLevel(); ++$i) {
$arrayType = new ArrayType(new MixedType(), $arrayType);
}

/** @var ArrayType $arrayType */
$unionedTypes[] = $arrayType;
}

if (count($unionedTypes) > 1) {
return TypeFactoryStaticHelper::createUnionObjectType($unionedTypes);
}

return $unionedTypes[0];
}

private function collectNestedArrayTypeFromUnionType(UnionType $unionType, int $arrayNesting): void
{
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof ArrayType) {
++$arrayNesting;
$this->normalizeArrayOfUnionToUnionArray($unionedType, $arrayNesting);
} else {
$this->collectedNestedArrayTypes[] = new NestedArrayTypeValueObject($unionedType, $arrayNesting);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclaration\ValueObject;

use PHPStan\Type\Type;

final class NestedArrayTypeValueObject
{
/**
* @var Type
*/
private $type;

/**
* @var int
*/
private $arrayNestingLevel;

public function __construct(Type $type, int $arrayNestingLevel)
{
$this->type = $type;
$this->arrayNestingLevel = $arrayNestingLevel;
}

public function getType(): Type
{
return $this->type;
}

public function getArrayNestingLevel(): int
{
return $this->arrayNestingLevel;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public function provideDataForTest(): Iterator

// skip
yield [__DIR__ . '/Fixture/skip_too_many.php.inc'];
yield [__DIR__ . '/Fixture/skip_too_many_2.php.inc'];
yield [__DIR__ . '/Fixture/skip_mixed_of_specific_override.php.inc'];
yield [__DIR__ . '/Fixture/skip_closure_callable_override.php.inc'];
yield [__DIR__ . '/Fixture/skip_shorten_class_name.php.inc'];
Expand Down
Loading