From 2ff172e707b9bab0fc68adc7191c1cd8973b88f8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 15 Sep 2025 11:42:21 +0200 Subject: [PATCH 1/2] fixtures --- .../avoid_extreme_nesting.php.inc | 4 +- .../{ => Nesting}/complex_array.php.inc | 4 +- .../{ => Nesting}/two_nested_levels.php.inc | 4 +- .../Fixture/Nesting/union_nest.php.inc | 198 ++++++++++++++++++ .../TypeManipulator/TypeNormalizer.php | 1 + .../ConstantArrayTypeGeneralizer.php | 2 +- 6 files changed, 206 insertions(+), 7 deletions(-) rename rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/{ => Nesting}/avoid_extreme_nesting.php.inc (89%) rename rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/{ => Nesting}/complex_array.php.inc (88%) rename rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/{ => Nesting}/two_nested_levels.php.inc (85%) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/union_nest.php.inc diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/avoid_extreme_nesting.php.inc similarity index 89% rename from rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc rename to rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/avoid_extreme_nesting.php.inc index 9b0de5cd182..e44bfce0de7 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/avoid_extreme_nesting.php.inc @@ -1,6 +1,6 @@ 1111, + 'field2' => 22, + 'field3' => [ + 'field1' => 'value1', + ], + 'field4' => 9, + 'field5' => '50/50', + 'field6' => true, + 'field7' => 'value2', + 'field8' => [ + 0 => [ + 'field1' => [ + 'field1' => 123.4, + 'field2' => 200.0, + 'field3' => 200.0, + 'field4' => 10.0, + 'field5' => 9.0, + 'field6' => 210.0, + 'field7' => 209.0, + 'field8' => 3.0, + 'field9' => 0.0, + 'field10' => 123.4, + ], + 'field2' => [ + 0 => [ + 'field1' => 3.0, + ], + ], + 'field3' => [ + 0 => [ + 'field1' => 0.0, + ], + ], + 'field4' => false, + 'field5' => false, + 'field6' => [ + 0 => [ + 'field1' => 'value3', + ], + ], + 'field7' => [ + 'field1' => 'value4', + ], + 'field8' => [], + 'field9' => [ + 0 => [ + 'field1' => 123.4, + 'field2' => 200.0, + 'field3' => 200.0, + 'field4' => 10.0, + 'field5' => 9.0, + 'field6' => 210.0, + 'field7' => 209.0, + 'field8' => 3.0, + 'field9' => 0.0, + 'field10' => 123.4, + ], + ], + 'field10' => [], + 'field11' => [ + 'field1' => '2000-01-01T00:00:00+0000', + 'field2' => '2000-01-01T00:00:00+0000', + 'field3' => '2000-01-01T00:00:00+0000', + 'field4' => '2000-01-01T00:00:00+0000', + 'field5' => '2000-01-01T00:00:00+0000', + 'field6' => '2000-01-01T00:00:00+0000', + 'field7' => '2000-01-01T00:00:00+0000', + 'field8' => '2000-01-01T00:00:00+0000', + ], + 'field12' => 'value5', + 'field13' => [], + ], + ], + 'field9' => [ + 'value6', + 'value7', + ], + 'field10' => [ + 'value8', + 'value9', + ], + 'field11' => true, + 'field12' => 'value10', + ]; + } +} + +?> +----- +, field2: array<0, array{field1: float}>, field3: array<0, array{field1: float}>, field4: bool, field5: bool, field6: array<0, array{field1: string}>, field7: array<'field1', string>, field8: array, field9: array<0, array{field1: float, field2: float, field3: float, field4: float, field5: float, field6: float, field7: float, field8: float, field9: float, field10: float}>, field10: array, field11: array, field12: string, field13: array}[]> + */ + public function getData(): array + { + return [ + 'field1' => 1111, + 'field2' => 22, + 'field3' => [ + 'field1' => 'value1', + ], + 'field4' => 9, + 'field5' => '50/50', + 'field6' => true, + 'field7' => 'value2', + 'field8' => [ + 0 => [ + 'field1' => [ + 'field1' => 123.4, + 'field2' => 200.0, + 'field3' => 200.0, + 'field4' => 10.0, + 'field5' => 9.0, + 'field6' => 210.0, + 'field7' => 209.0, + 'field8' => 3.0, + 'field9' => 0.0, + 'field10' => 123.4, + ], + 'field2' => [ + 0 => [ + 'field1' => 3.0, + ], + ], + 'field3' => [ + 0 => [ + 'field1' => 0.0, + ], + ], + 'field4' => false, + 'field5' => false, + 'field6' => [ + 0 => [ + 'field1' => 'value3', + ], + ], + 'field7' => [ + 'field1' => 'value4', + ], + 'field8' => [], + 'field9' => [ + 0 => [ + 'field1' => 123.4, + 'field2' => 200.0, + 'field3' => 200.0, + 'field4' => 10.0, + 'field5' => 9.0, + 'field6' => 210.0, + 'field7' => 209.0, + 'field8' => 3.0, + 'field9' => 0.0, + 'field10' => 123.4, + ], + ], + 'field10' => [], + 'field11' => [ + 'field1' => '2000-01-01T00:00:00+0000', + 'field2' => '2000-01-01T00:00:00+0000', + 'field3' => '2000-01-01T00:00:00+0000', + 'field4' => '2000-01-01T00:00:00+0000', + 'field5' => '2000-01-01T00:00:00+0000', + 'field6' => '2000-01-01T00:00:00+0000', + 'field7' => '2000-01-01T00:00:00+0000', + 'field8' => '2000-01-01T00:00:00+0000', + ], + 'field12' => 'value5', + 'field13' => [], + ], + ], + 'field9' => [ + 'value6', + 'value7', + ], + 'field10' => [ + 'value8', + 'value9', + ], + 'field11' => true, + 'field12' => 'value10', + ]; + } +} + +?> diff --git a/rules/Privatization/TypeManipulator/TypeNormalizer.php b/rules/Privatization/TypeManipulator/TypeNormalizer.php index 04b4e1b08c6..9533d95eca5 100644 --- a/rules/Privatization/TypeManipulator/TypeNormalizer.php +++ b/rules/Privatization/TypeManipulator/TypeNormalizer.php @@ -14,6 +14,7 @@ use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; +use PHPStan\Type\NeverType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; diff --git a/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php index e9e998e717a..4e52fe717d5 100644 --- a/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php +++ b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php @@ -56,7 +56,7 @@ public function generalize(ConstantArrayType $constantArrayType, bool $isFresh = $genericItemType = $this->typeNormalizer->generalizeConstantTypes($itemType); } - // correct + // correction if ($genericItemType instanceof NeverType) { $genericItemType = new MixedType(); } From 4fb5db0c14fc94f27b3fedba8b521e203fe62007 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 15 Sep 2025 11:51:32 +0200 Subject: [PATCH 2/2] generalize unioned array item --- .../Fixture/complex_case.php.inc | 2 +- .../Fixture/Nesting/complex_array.php.inc | 2 +- .../Fixture/Nesting/union_nest.php.inc | 2 +- .../Fixture/covert-implicit-key.php.inc | 2 +- .../Fixture/cover_mixex_and_known.php.inc | 42 +++++++++++++++ .../Fixture/skip_mixed_mixed.php.inc | 17 ------ .../TypeManipulator/TypeNormalizer.php | 53 ++++++++++++++++++- 7 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/cover_mixex_and_known.php.inc delete mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_mixed_mixed.php.inc diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDataProviderRector/Fixture/complex_case.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDataProviderRector/Fixture/complex_case.php.inc index b2a41f1ebce..b02fd83874b 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDataProviderRector/Fixture/complex_case.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDataProviderRector/Fixture/complex_case.php.inc @@ -62,7 +62,7 @@ use PHPUnit\Framework\TestCase; final class ComplexCase extends TestCase { /** - * @param array> $first + * @param array> $first * @param array $second */ #[DataProvider('getData')] diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/complex_array.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/complex_array.php.inc index 687fb320306..6fa70b2cf4e 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/complex_array.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/complex_array.php.inc @@ -31,7 +31,7 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockRetur class ComplexArray { /** - * @return array>> + * @return array */ public function run(): array { diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/union_nest.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/union_nest.php.inc index 236c01f83cd..681dde218a0 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/union_nest.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/Nesting/union_nest.php.inc @@ -103,7 +103,7 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockRetur final class UnionNest { /** - * @return array, field2: array<0, array{field1: float}>, field3: array<0, array{field1: float}>, field4: bool, field5: bool, field6: array<0, array{field1: string}>, field7: array<'field1', string>, field8: array, field9: array<0, array{field1: float, field2: float, field3: float, field4: float, field5: float, field6: float, field7: float, field8: float, field9: float, field10: float}>, field10: array, field11: array, field12: string, field13: array}[]> + * @return array */ public function getData(): array { diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/covert-implicit-key.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/covert-implicit-key.php.inc index 6efd75479a4..d8194433e41 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/covert-implicit-key.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/covert-implicit-key.php.inc @@ -28,7 +28,7 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockRetur final class CoverImplicitKey { /** - * @return array> + * @return array> */ private static function getExpectedAllOwners(): array { diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/cover_mixex_and_known.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/cover_mixex_and_known.php.inc new file mode 100644 index 00000000000..01833c54588 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/cover_mixex_and_known.php.inc @@ -0,0 +1,42 @@ +run(['item1', 'item2']); + + $this->run([$mixed, $mixed]); + } + + private function run(array $items) + { + } +} + +?> +----- +run(['item1', 'item2']); + + $this->run([$mixed, $mixed]); + } + + /** + * @param string[]|mixed[] $items + */ + private function run(array $items) + { + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_mixed_mixed.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_mixed_mixed.php.inc deleted file mode 100644 index 6dc6d75a169..00000000000 --- a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_mixed_mixed.php.inc +++ /dev/null @@ -1,17 +0,0 @@ -run(['item1', 'item2']); - - $this->run([$mixed, $mixed]); - } - - private function run(array $items) - { - } -} diff --git a/rules/Privatization/TypeManipulator/TypeNormalizer.php b/rules/Privatization/TypeManipulator/TypeNormalizer.php index 9533d95eca5..94bc02e5451 100644 --- a/rules/Privatization/TypeManipulator/TypeNormalizer.php +++ b/rules/Privatization/TypeManipulator/TypeNormalizer.php @@ -19,9 +19,23 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; +use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; +use Rector\StaticTypeMapper\StaticTypeMapper; final class TypeNormalizer { + /** + * @var int + */ + private const MAX_PRINTED_UNION_DOC_LENGHT = 50; + + public function __construct( + private TypeFactory $typeFactory, + private StaticTypeMapper $staticTypeMapper + ) { + + } + /** * @deprecated This method is deprecated and will be removed in the next major release. * Use @see generalizeConstantTypes() instead. @@ -59,7 +73,7 @@ public function generalizeConstantTypes(Type $type): Type if ($this->isImplicitNumberedListKeyType($type)) { $keyType = new MixedType(); } else { - $keyType = $traverseCallback($type->getKeyType(), $traverseCallback); + $keyType = $this->generalizeConstantTypes($type->getKeyType()); } // should be string[] @@ -68,10 +82,45 @@ public function generalizeConstantTypes(Type $type): Type $itemType = new StringType(); } + if ($itemType instanceof ConstantArrayType) { + $itemType = $this->generalizeConstantTypes($itemType); + } + return new ArrayType($keyType, $itemType); } - return $traverseCallback($type, $traverseCallback); + if ($type instanceof UnionType) { + $generalizedUnionedTypes = []; + foreach ($type->getTypes() as $unionedType) { + $generalizedUnionedTypes[] = $this->generalizeConstantTypes($unionedType); + } + + $uniqueGeneralizedUnionTypes = $this->typeFactory->uniquateTypes($generalizedUnionedTypes); + + if (count($uniqueGeneralizedUnionTypes) > 1) { + $generalizedUnionType = new UnionType($generalizedUnionedTypes); + // avoid too huge print in docblock + $unionedDocType = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode( + $generalizedUnionType + ); + + // too long + if (strlen((string) $unionedDocType) > self::MAX_PRINTED_UNION_DOC_LENGHT) { + return new MixedType(); + } + + return $generalizedUnionType; + } + + return $uniqueGeneralizedUnionTypes[0]; + } + + $convertedType = $traverseCallback($type, $traverseCallback); + if ($convertedType instanceof NeverType) { + return new MixedType(); + } + + return $convertedType; }); }