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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;

final class AvoidExtremeNesting
{
public function run(): array
{
return [
'data' => [
'data' => [
'data' => [
'data' => [
'data' => [
'name' => 'John',
],
],
],
],
],
];
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockReturnArrayFromDirectArrayInstanceRector\Fixture;

final class AvoidExtremeNesting
{
/**
* @return array<string, array<string, array<string, array<string, mixed>>>>
*/
public function run(): array
{
return [
'data' => [
'data' => [
'data' => [
'data' => [
'data' => [
'name' => 'John',
],
],
],
],
],
];
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
) {
}

Expand Down Expand Up @@ -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);
Expand All @@ -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]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclarationDocblocks\TypeResolver;

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\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\StaticTypeMapper\StaticTypeMapper;

final class ConstantArrayTypeGeneralizer
{
/**
* Using 10-level array @return docblocks makes code very hard to read,
* lets limit it to reasonable level
*/
private const MAX_NESTING = 3;

private int $currentNesting = 0;

public function __construct(
private readonly TypeFactory $typeFactory,
private readonly StaticTypeMapper $staticTypeMapper,
) {
}

public function generalize(ConstantArrayType $constantArrayType, bool $isFresh = true): GenericTypeNode
{
if ($isFresh) {
$this->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();
}
}
Loading