diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 10b1f135eb..1baaa08905 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -938,7 +938,7 @@ private function isImplicitArrayCreation(array $dimFetchStack, Scope $scope): Tr /** * @param list $dimFetchStack - * @param list $offsetTypes + * @param non-empty-list $offsetTypes * * @return array{Type, list} */ @@ -947,10 +947,11 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar $originalValueToWrite = $valueToWrite; $offsetValueTypeStack = [$offsetValueType]; + $generalizeOnWrite = $offsetTypes[array_key_last($offsetTypes)][0] !== null; foreach (array_slice($offsetTypes, 0, -1) as [$offsetType, $dimFetch]) { if ($offsetType === null) { $offsetValueType = new ConstantArrayType([], []); - + $generalizeOnWrite = false; } else { $has = $offsetValueType->hasOffsetValueType($offsetType); if ($has->yes()) { @@ -959,9 +960,11 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar if (!$scope->hasExpressionType($dimFetch)->yes()) { $offsetValueType = TypeCombinator::union($offsetValueType->getOffsetValueType($offsetType), new ConstantArrayType([], [])); } else { + $generalizeOnWrite = false; $offsetValueType = $offsetValueType->getOffsetValueType($offsetType); } } else { + $generalizeOnWrite = false; $offsetValueType = new ConstantArrayType([], []); } } @@ -1010,7 +1013,24 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar } } else { - $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0); + // when $unionValues=false the array item-type will be replaced with $valueToWrite + // when $unionValues=true the existing array item-type will be union'ed with $valueToWrite -> type gets wider + $unionValues = false; + if ($i === 0) { + $unionValues = true; + } elseif ( + $generalizeOnWrite + && $i === count($offsetTypes) - 1 + && + ( + $originalValueToWrite->isConstantScalarValue()->yes() + || !$offsetValueType->getIterableValueType()->isSuperTypeOf($valueToWrite)->yes() + ) + ) { + $unionValues = true; + } + + $valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $unionValues); } if ($arrayDimFetch === null || !$offsetValueType->isList()->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/assign-nested-arrays.php b/tests/PHPStan/Analyser/nsrt/assign-nested-arrays.php index 31cecaa4d1..3de574894a 100644 --- a/tests/PHPStan/Analyser/nsrt/assign-nested-arrays.php +++ b/tests/PHPStan/Analyser/nsrt/assign-nested-arrays.php @@ -12,8 +12,9 @@ public function doFoo(int $i) $array = []; $array[$i]['bar'] = 1; - $array[$i]['baz'] = 2; + assertType('non-empty-array', $array); + $array[$i]['baz'] = 2; assertType('non-empty-array', $array); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10089.php b/tests/PHPStan/Analyser/nsrt/bug-10089.php new file mode 100644 index 0000000000..21122cdfc3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10089.php @@ -0,0 +1,44 @@ +, non-empty-array> + assertType('list>', $matrix); + + $matrix[$size - 1][8] = 3; + + // non-empty-array&hasOffsetValue(8, 3)> + assertType('non-empty-list, 0|3>>', $matrix); + + for ($i = 0; $i <= $size; $i++) { + if ($matrix[$i][8] === 0) { + // ... + } + if ($matrix[8][$i] === 0) { + // ... + } + if ($matrix[$size - 1 - $i][8] === 0) { + // ... + } + } + + return $matrix; + } + +} + + diff --git a/tests/PHPStan/Analyser/nsrt/bug-13857.php b/tests/PHPStan/Analyser/nsrt/bug-13857.php new file mode 100644 index 0000000000..737f17763e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13857.php @@ -0,0 +1,67 @@ + $array + */ +function test(array $array, int $id): void { + $array[$id]['state'] = 'foo'; + // only one element was set to 'foo', not all of them. + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testMaybe(array $array, int $id): void { + $array[$id]['state'] = 'foo'; + // only one element was set to 'foo', not all of them. + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testUnionValue(array $array, int $id): void { + $array[$id]['state'] = 'foo'; + // only one element was set to 'foo', not all of them. + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testUnionArray(array $array, int $id): void { + $array[$id]['state'] = 'foo'; + // only one element was set to 'foo', not all of them. + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testUnionArrayDifferentType(array $array, int $id): void { + $array[$id]['state'] = true; + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testConstantArray(array $array, int $id): void { + $array[$id]['state'] = 'bar'; + assertType("non-empty-array", $array); +} + +/** + * @param array $array + */ +function testConstantArrayNonScalarAssign(array $array, int $id, bool $b): void { + $array[$id]['state'] = $b; + assertType("non-empty-array", $array); +} diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11679.php b/tests/PHPStan/Rules/Arrays/data/bug-11679.php index 463362516a..46374b8cb2 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-11679.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-11679.php @@ -31,7 +31,8 @@ public function sayHello(int $index): bool assertType('array', $this->arr); if (!isset($this->arr[$index]['foo'])) { $this->arr[$index]['foo'] = true; - assertType('non-empty-array', $this->arr); + assertType('non-empty-array', $this->arr); + assertType('true', $this->arr[$index]['foo']); } assertType('array', $this->arr); return $this->arr[$index]['foo']; // PHPStan does not realize 'foo' is set diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 2450cb19b3..d5e7f59fd8 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1046,6 +1046,11 @@ public function testBug13282(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-13282.php'], []); } + public function testBug10089(): void + { + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10089.php'], []); + } + public function testBug11609(): void { $this->analyse([__DIR__ . '/data/bug-11609.php'], [