Skip to content

Commit d3e4cbb

Browse files
ondrejmirtesclaude
andcommitted
Carry unsealed slot through ConstantArrayType::spliceArray()
`array_splice` removes a slice at an explicit offset and inserts a replacement at that position. Real unsealed extras live at positions past the explicit keys, so they're not affected by either operation; the re-indexing of int keys also leaves a `<int, V>` unsealed range unchanged. Pass the source's unsealed tuple into the builder before producing the result for each replacement variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 97829f9 commit d3e4cbb

2 files changed

Lines changed: 27 additions & 0 deletions

File tree

src/Type/Constant/ConstantArrayType.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,16 @@ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacemen
18571857
);
18581858
}
18591859

1860+
// `array_splice` removes a slice at an explicit offset and
1861+
// inserts a replacement there. Real unsealed extras live at
1862+
// positions past the explicit keys, so they're unaffected
1863+
// by the operation (re-indexing of int keys keeps the
1864+
// `<int, V>` range intact). Carry the slot through.
1865+
if ($this->isUnsealed()->yes() && $this->unsealed !== null) {
1866+
[$unsealedKey, $unsealedValue] = $this->unsealed;
1867+
$builder->makeUnsealed($unsealedKey, $unsealedValue);
1868+
}
1869+
18601870
$builtType = $builder->getArray();
18611871
if ($allKeysInteger && !$builtType->isList()->yes()) {
18621872
$builtType = TypeCombinator::intersect($builtType, new AccessoryArrayListType());

tests/PHPStan/Analyser/nsrt/unsealed-derivations.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,23 @@ public function shufflePreservesUnsealedValues(array $arr): void
514514

515515
}
516516

517+
class SpliceArray
518+
{
519+
520+
/**
521+
* @param list{int, int, int, ...<float>} $arr
522+
*/
523+
public function splicePreservesUnsealed(array $arr): void
524+
{
525+
array_splice($arr, 1, 1);
526+
// `array_splice` removes a slice from an explicit position;
527+
// the unsealed extras at the tail are unaffected and survive
528+
// on the result.
529+
assertType('array{int, int, ...<float>}', $arr);
530+
}
531+
532+
}
533+
517534
class CountNarrowing
518535
{
519536

0 commit comments

Comments
 (0)