diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictNewArrayRector/Fixture/union_array_shapes_no_doc.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictNewArrayRector/Fixture/union_array_shapes_no_doc.php.inc new file mode 100644 index 00000000000..3edea32cc27 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictNewArrayRector/Fixture/union_array_shapes_no_doc.php.inc @@ -0,0 +1,45 @@ + 1, 'cash_register_id' => 2, 'text' => 't']; + } else { + $data[] = ['amount' => 1, 'date' => 'd', 'text' => 't']; + } + } + + return $data; + } +} + +?> +----- + 1, 'cash_register_id' => 2, 'text' => 't']; + } else { + $data[] = ['amount' => 1, 'date' => 'd', 'text' => 't']; + } + } + + return $data; + } +} + +?> diff --git a/rules/TypeDeclaration/NodeAnalyzer/StrictReturnNewArrayResolver.php b/rules/TypeDeclaration/NodeAnalyzer/StrictReturnNewArrayResolver.php index 7ad68b09308..796271f4d42 100644 --- a/rules/TypeDeclaration/NodeAnalyzer/StrictReturnNewArrayResolver.php +++ b/rules/TypeDeclaration/NodeAnalyzer/StrictReturnNewArrayResolver.php @@ -20,6 +20,8 @@ use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; +use PHPStan\Type\UnionType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\NodeNameResolver\NodeNameResolver; @@ -202,6 +204,11 @@ private function matchArrayAssignedVariable(array $stmts): array private function shouldAddReturnArrayDocType(Type $arrayType): bool { + // a union of multiple distinct array shapes produces a noisy doc type, skip it + if ($this->hasNoisyArrayShapeUnion($arrayType)) { + return false; + } + if ($arrayType instanceof ConstantArrayType) { if ($arrayType->getIterableValueType() instanceof NeverType) { return false; @@ -215,4 +222,37 @@ private function shouldAddReturnArrayDocType(Type $arrayType): bool return true; } + + private function hasNoisyArrayShapeUnion(Type $type): bool + { + $isNoisy = false; + TypeTraverser::map($type, function (Type $currentType, callable $traverse) use (&$isNoisy): Type { + if ($currentType instanceof UnionType && $this->hasMultipleArrayVariants($currentType)) { + $isNoisy = true; + } + + return $traverse($currentType); + }); + + return $isNoisy; + } + + private function hasMultipleArrayVariants(UnionType $unionType): bool + { + $arrayVariantCount = 0; + foreach ($unionType->getTypes() as $type) { + if (! $type->isArray()->yes()) { + continue; + } + + // an empty array [] collapses into the sibling variant, ignore it + if ($type->getIterableValueType() instanceof NeverType) { + continue; + } + + ++$arrayVariantCount; + } + + return $arrayVariantCount >= 2; + } }