From 93fa96fd25d1528ad1df0e900bded06eb040984b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 15 Sep 2025 10:26:51 +0200 Subject: [PATCH] Avoid complex nesting in ConstantArrayTypeGeneralizer --- .../Fixture/avoid_extreme_nesting.php.inc | 54 ++++++++ ...turnArrayFromDirectArrayInstanceRector.php | 89 +------------ .../ConstantArrayTypeGeneralizer.php | 118 ++++++++++++++++++ 3 files changed, 175 insertions(+), 86 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc create mode 100644 rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc new file mode 100644 index 00000000000..9b0de5cd182 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/avoid_extreme_nesting.php.inc @@ -0,0 +1,54 @@ + [ + 'data' => [ + 'data' => [ + 'data' => [ + 'data' => [ + 'name' => 'John', + ], + ], + ], + ], + ], + ]; + } +} + +?> +----- +>>> + */ + public function run(): array + { + return [ + 'data' => [ + 'data' => [ + 'data' => [ + 'data' => [ + 'data' => [ + 'name' => 'John', + ], + ], + ], + ], + ], + ]; + } +} + +?> diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php index 3d9ad973c4f..10bc5e5a919 100644 --- a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php @@ -10,23 +10,11 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Return_; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; -use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; -use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\FloatType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; -use PHPStan\Type\MixedType; -use PHPStan\Type\NeverType; -use PHPStan\Type\StringType; -use PHPStan\Type\Type; -use PHPStan\Type\UnionType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\Rector\AbstractRector; -use Rector\StaticTypeMapper\StaticTypeMapper; +use Rector\TypeDeclarationDocblocks\TypeResolver\ConstantArrayTypeGeneralizer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -38,8 +26,7 @@ final class DocblockReturnArrayFromDirectArrayInstanceRector extends AbstractRec public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, private readonly DocBlockUpdater $docBlockUpdater, - private readonly StaticTypeMapper $staticTypeMapper, - private readonly TypeFactory $typeFactory + private readonly ConstantArrayTypeGeneralizer $constantArrayTypeGeneralizer, ) { } @@ -118,7 +105,7 @@ public function refactor(Node $node): ?Node return null; } - $genericTypeNode = $this->createGenericArrayTypeFromConstantArrayType($returnedType); + $genericTypeNode = $this->constantArrayTypeGeneralizer->generalize($returnedType); $returnTagValueNode = new ReturnTagValueNode($genericTypeNode, ''); $phpDocInfo->addTagValueNode($returnTagValueNode); @@ -127,74 +114,4 @@ public function refactor(Node $node): ?Node return $node; } - - /** - * covers constant types too and makes them more generic - */ - private function constantToGenericType(Type $type): Type - { - if ($type->isString()->yes()) { - return new StringType(); - } - - if ($type->isInteger()->yes()) { - return new IntegerType(); - } - - if ($type->isBoolean()->yes()) { - return new BooleanType(); - } - - if ($type->isFloat()->yes()) { - return new FloatType(); - } - - if ($type instanceof UnionType || $type instanceof IntersectionType) { - $genericComplexTypes = []; - foreach ($type->getTypes() as $splitType) { - $genericComplexTypes[] = $this->constantToGenericType($splitType); - } - - $genericComplexTypes = $this->typeFactory->uniquateTypes($genericComplexTypes); - if (count($genericComplexTypes) > 1) { - return new UnionType($genericComplexTypes); - } - - return $genericComplexTypes[0]; - } - - // unclear - return new MixedType(); - } - - private function createGenericArrayTypeFromConstantArrayType(ConstantArrayType $constantArrayType): GenericTypeNode - { - $genericKeyType = $this->constantToGenericType($constantArrayType->getKeyType()); - - if ($constantArrayType->getItemType() instanceof NeverType) { - $genericKeyType = new IntegerType(); - } - - $itemType = $constantArrayType->getItemType(); - if ($itemType instanceof ConstantArrayType) { - $genericItemType = $this->createGenericArrayTypeFromConstantArrayType($itemType); - } else { - $genericItemType = $this->constantToGenericType($itemType); - } - - return $this->createArrayGenericTypeNode($genericKeyType, $genericItemType); - } - - private function createArrayGenericTypeNode(Type $keyType, Type|GenericTypeNode $itemType): GenericTypeNode - { - $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); - - if ($itemType instanceof Type) { - $itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType); - } else { - $itemDocTypeNode = $itemType; - } - - return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]); - } } diff --git a/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php new file mode 100644 index 00000000000..73b7a944041 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/TypeResolver/ConstantArrayTypeGeneralizer.php @@ -0,0 +1,118 @@ +currentNesting = 0; + } else { + ++$this->currentNesting; + } + + $genericKeyType = $this->constantToGenericType($constantArrayType->getKeyType()); + + if ($constantArrayType->getItemType() instanceof NeverType) { + $genericKeyType = new IntegerType(); + } + + $itemType = $constantArrayType->getItemType(); + + if ($itemType instanceof ConstantArrayType) { + if ($this->currentNesting >= self::MAX_NESTING) { + $genericItemType = new MixedType(); + } else { + $genericItemType = $this->generalize($itemType, false); + } + } else { + $genericItemType = $this->constantToGenericType($itemType); + } + + return $this->createArrayGenericTypeNode($genericKeyType, $genericItemType); + } + + private function createArrayGenericTypeNode(Type $keyType, Type|GenericTypeNode $itemType): GenericTypeNode + { + $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); + + if ($itemType instanceof Type) { + $itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType); + } else { + $itemDocTypeNode = $itemType; + } + + return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]); + } + + /** + * Covers constant types too and makes them more generic + */ + private function constantToGenericType(Type $type): Type + { + if ($type->isString()->yes()) { + return new StringType(); + } + + if ($type->isInteger()->yes()) { + return new IntegerType(); + } + + if ($type->isBoolean()->yes()) { + return new BooleanType(); + } + + if ($type->isFloat()->yes()) { + return new FloatType(); + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + $genericComplexTypes = []; + foreach ($type->getTypes() as $splitType) { + $genericComplexTypes[] = $this->constantToGenericType($splitType); + } + + $genericComplexTypes = $this->typeFactory->uniquateTypes($genericComplexTypes); + if (count($genericComplexTypes) > 1) { + return new UnionType($genericComplexTypes); + } + + return $genericComplexTypes[0]; + } + + // unclear + return new MixedType(); + } +}