From 8d91f5db7170f3e60c5f05db55e1d9e6976beed8 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 26 Aug 2026 08:48:38 +0100 Subject: [PATCH] [TypeDeclarationDocblocks] Skip contradicting @param array when local call type conflicts with default value --- .../default_array_with_known_type.php.inc | 38 +++++++++++++++++++ ..._empty_array_contradicting_default.php.inc | 21 ++++++++++ .../NodeDocblockTypeDecorator.php | 3 +- ...ArrayDocblockParamFromLocalCallsRector.php | 9 +++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/default_array_with_known_type.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_empty_array_contradicting_default.php.inc diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/default_array_with_known_type.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/default_array_with_known_type.php.inc new file mode 100644 index 00000000000..83ad952e275 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/default_array_with_known_type.php.inc @@ -0,0 +1,38 @@ +run(['item1', 'item2']); + } + + private function run(array $items = []) + { + } +} + +?> +----- +run(['item1', 'item2']); + } + + /** + * @param string[] $items + */ + private function run(array $items = []) + { + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_empty_array_contradicting_default.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_empty_array_contradicting_default.php.inc new file mode 100644 index 00000000000..14f70991cf0 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector/Fixture/skip_non_empty_array_contradicting_default.php.inc @@ -0,0 +1,21 @@ +callee($row); + } + + public function caller2() + { + return $this->callee(['state' => 5, 'name' => 'x']); + } +} diff --git a/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php b/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php index cf30d0e0384..b11d5613e5c 100644 --- a/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php +++ b/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php @@ -149,6 +149,7 @@ private function isArrayMixed(Type $type): bool return false; } - return $type->getKeyType() instanceof IntegerType; + // both plain "mixed[]" (integer key) and a fully mixed-keyed array carry no useful value + return $type->getKeyType() instanceof IntegerType || $type->getKeyType() instanceof MixedType; } } diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index 3c3dd427ae7..d58f0c973cb 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -5,6 +5,7 @@ namespace Rector\TypeDeclarationDocblocks\Rector\Class_; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; @@ -130,6 +131,14 @@ 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 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 + if ($param->default instanceof Expr) { + $defaultType = $this->nodeTypeResolver->getType($param->default); + $resolvedParameterType = TypeCombinator::union($resolvedParameterType, $defaultType); + } + $hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableParamType( $resolvedParameterType, $classMethodPhpDocInfo,