From 31c69c596998c708cdcde7ae1e860bcb6abb5c81 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:32:16 +0000 Subject: [PATCH] Preserve list type in `ConstantArrayType::spliceArray` when all keys are integers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - When `array_splice` is called on a constant array with only integer keys and a non-constant replacement, the builder degradation caused `isList` to be incorrectly set to `No` (via non-null key in `setOffsetValueType`). - After building the result, check whether all keys in the original constant array were integers. If so, intersect the result with `AccessoryArrayListType` since `array_splice` always re-indexes integer keys. - The fix is applied per-replacement-variant before the final union, so it correctly handles cases where the replacement is a union of arrays. - Added regression test covering constant arrays with integer keys (the reported bug), list replacements, and string-key arrays (already correct). - Probed `sliceArray`, `IntersectionType::spliceArray`, `AccessoryArrayListType::spliceArray`, sort functions, and `ConstantTypeHelper` for analogous issues — none found in the same pattern. --- src/Type/Constant/ConstantArrayType.php | 8 +++- tests/PHPStan/Analyser/nsrt/bug-14472.php | 55 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-14472.php diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index c5cf80b83c..b2ec74678a 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1194,6 +1194,8 @@ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacemen ->spliceArray($offsetType, $lengthType, $replacementType); } + $allKeysInteger = $this->getIterableKeyType()->isInteger()->yes(); + if ($keyTypesCount + $offset <= 0) { // A negative offset cannot reach left outside the array twice $offset = 0; @@ -1271,7 +1273,11 @@ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacemen ); } - $types[] = $builder->getArray(); + $builtType = $builder->getArray(); + if ($allKeysInteger && !$builtType->isList()->yes()) { + $builtType = TypeCombinator::intersect($builtType, new AccessoryArrayListType()); + } + $types[] = $builtType; } return TypeCombinator::union(...$types); diff --git a/tests/PHPStan/Analyser/nsrt/bug-14472.php b/tests/PHPStan/Analyser/nsrt/bug-14472.php new file mode 100644 index 0000000000..7351103442 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14472.php @@ -0,0 +1,55 @@ +", $headers); + } + + /** + * @param list $replacement + */ + public function arraySpliceOnConstantArrayWithIntKeysListReplacement(array $replacement): void + { + $headers = ['a', 'b', 'c']; + array_splice($headers, 1, 0, $replacement); + assertType("non-empty-list", $headers); + } + + public function arraySpliceOnConstantArrayWithStringKeys(): void + { + $headers = ['a' => 'x', 'b' => 'y', 'c' => 'z']; + array_splice($headers, 1, 1, ['replacement']); + assertType("array{a: 'x', 0: 'replacement', c: 'z'}", $headers); + } +}