Skip to content

Commit f125b73

Browse files
staabmphpstan-bot
authored andcommitted
Fix multi-dimensional array shape wrongly inferred with hasOffsetValue
- When assigning to a nested array with a non-constant outer key like $matrix[$size - 1][8] = 3, the HasOffsetValueType(8, 3) from the inner dimension was incorrectly propagated to ALL entries of the outer array - Added logic in AssignHandler::produceArrayDimFetchAssignValueToWrite() to strip HasOffsetValueType from the inner value when the outer offset is non-constant and the inner dimension was not a tracked expression (i.e., not from a foreach binding) - New regression test in tests/PHPStan/Analyser/nsrt/bug-10089.php Fixes phpstan/phpstan#10089
1 parent 3c63f68 commit f125b73

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/Analyser/ExprHandler/AssignHandler.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
use PHPStan\Type\ConstantTypeHelper;
5656
use PHPStan\Type\ErrorType;
5757
use PHPStan\Type\IntegerRangeType;
58+
use PHPStan\Type\IntersectionType;
5859
use PHPStan\Type\MixedType;
5960
use PHPStan\Type\ObjectType;
6061
use PHPStan\Type\StaticTypeFactory;
@@ -969,6 +970,7 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
969970
$offsetValueTypeStack[] = $offsetValueType;
970971
}
971972

973+
$previousIterationUsedExistingBranch = false;
972974
foreach (array_reverse($offsetTypes) as $i => [$offsetType]) {
973975
/** @var Type $offsetValueType */
974976
$offsetValueType = array_pop($offsetValueTypeStack);
@@ -1009,8 +1011,31 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
10091011
}
10101012
}
10111013

1014+
$previousIterationUsedExistingBranch = true;
10121015
} else {
1013-
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0);
1016+
$innerValueToWrite = $valueToWrite;
1017+
if (
1018+
$i > 0
1019+
&& !$previousIterationUsedExistingBranch
1020+
&& $offsetType !== null
1021+
&& $offsetType->getConstantScalarValues() === []
1022+
&& $innerValueToWrite instanceof IntersectionType // @phpstan-ignore phpstanApi.instanceofType
1023+
) {
1024+
$filteredTypes = [];
1025+
$hasRemovedOffsetValue = false;
1026+
foreach ($innerValueToWrite->getTypes() as $innerType) {
1027+
if ($innerType instanceof HasOffsetValueType) {
1028+
$hasRemovedOffsetValue = true;
1029+
continue;
1030+
}
1031+
$filteredTypes[] = $innerType;
1032+
}
1033+
if ($hasRemovedOffsetValue && $filteredTypes !== []) {
1034+
$innerValueToWrite = TypeCombinator::intersect(...$filteredTypes);
1035+
}
1036+
}
1037+
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $innerValueToWrite, $i === 0);
1038+
$previousIterationUsedExistingBranch = false;
10141039
}
10151040

10161041
if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug10089;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Test
10+
{
11+
12+
protected function create_matrix(int $size): array
13+
{
14+
$size = min(8, $size);
15+
$matrix = [];
16+
for ($i = 0; $i < $size; $i++) {
17+
$matrix[] = array_fill(0, $size, 0);
18+
}
19+
20+
$matrix[$size - 1][8] = 3;
21+
22+
assertType('non-empty-list<non-empty-array<int<0, max>, 0|3>>', $matrix);
23+
24+
for ($i = 0; $i <= $size; $i++) {
25+
assertType('0|3', $matrix[$i][8]);
26+
}
27+
28+
return $matrix;
29+
}
30+
31+
}

0 commit comments

Comments
 (0)