diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_nested_array_param.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_nested_array_param.php.inc new file mode 100644 index 00000000000..22ed37dce2b --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_nested_array_param.php.inc @@ -0,0 +1,15 @@ +run([['name' => 'John', 'age' => 30]]); + } + + private function run(array $rows): void + { + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected.php.inc new file mode 100644 index 00000000000..f59b0bb5de2 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected.php.inc @@ -0,0 +1,15 @@ +run(['item1', 'item2']); + } + + protected function run(array $items) + { + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected_with_parent.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected_with_parent.php.inc new file mode 100644 index 00000000000..a010431ec4f --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_final_protected_with_parent.php.inc @@ -0,0 +1,17 @@ +run(['item1', 'item2']); + } + + protected function run(array $items) + { + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index c04a1b36053..b6cb0633e23 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -8,8 +8,10 @@ use PhpParser\Node\Expr; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; +use PHPStan\Type\ArrayType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\PhpParser\NodeFinder\LocalMethodCallFinder; use Rector\Rector\AbstractRector; @@ -90,6 +92,7 @@ public function refactor(Node $node): ?Node } // only private methods have a closed set of local callers; public/protected can be called from outside + // with a wider type we cannot see here if (! $classMethod->isPrivate()) { continue; } @@ -121,6 +124,12 @@ public function refactor(Node $node): ?Node // in case of array type declaration, null cannot be passed or is already casted $resolvedParameterType = TypeCombinator::removeNull($resolvedParameterType); + // the generalization of a nested array (an array of arrays) is imprecise and can diverge from the type + // PHPStan itself infers at the call site, producing a @param that rejects the very call it was built from + if ($this->hasNestedArray($resolvedParameterType)) { + continue; + } + // the param default value must always be accepted; a locally inferred, flow-narrowed type such as // "non-empty-array" would otherwise contradict an "= []" default - unite with the default type so // the resulting @param never conflicts with the method signature @@ -150,6 +159,24 @@ public function refactor(Node $node): ?Node return $node; } + private function hasNestedArray(Type $type): bool + { + if ($type instanceof UnionType) { + return array_any($type->getTypes(), fn (Type $unionedType): bool => $this->hasNestedArray($unionedType)); + } + + if (! $type instanceof ArrayType) { + return false; + } + + $itemType = $type->getItemType(); + if ($itemType instanceof UnionType) { + return array_any($itemType->getTypes(), fn (Type $unionedItemType): bool => $unionedItemType instanceof ArrayType); + } + + return $itemType instanceof ArrayType; + } + private function hasParamArrayType(Param $param): bool { if (! $param->type instanceof Node) {