Skip to content

Commit 3372181

Browse files
committed
fix null coalesce false positive for multi-dimensional array in loop
1 parent 6958f86 commit 3372181

File tree

4 files changed

+51
-3
lines changed

4 files changed

+51
-3
lines changed

src/Type/ArrayType.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,33 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $uni
375375
public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
376376
{
377377
if ($this->itemType->isConstantArray()->yes() && $valueType->isConstantArray()->yes()) {
378-
$newItemType = $this->itemType;
378+
$newItemTypes = [];
379+
379380
foreach ($valueType->getConstantArrays() as $constArray) {
380-
foreach ($constArray->getKeyTypes() as $keyType) {
381+
$newItemType = $this->itemType;
382+
$optionalKeyTypes = [];
383+
foreach ($constArray->getKeyTypes() as $i => $keyType) {
381384
$newItemType = $newItemType->setExistingOffsetValueType($keyType, $constArray->getOffsetValueType($keyType));
385+
386+
if (!$constArray->isOptionalKey($i)) {
387+
continue;
388+
}
389+
390+
$optionalKeyTypes[] = $keyType;
391+
}
392+
$newItemTypes[] = $newItemType;
393+
394+
if ($optionalKeyTypes === []) {
395+
continue;
396+
}
397+
398+
foreach ($optionalKeyTypes as $keyType) {
399+
$newItemType = $newItemType->unsetOffset($keyType);
382400
}
401+
$newItemTypes[] = $newItemType;
383402
}
384403

404+
$newItemType = TypeCombinator::union(...$newItemTypes);
385405
if ($newItemType !== $this->itemType) {
386406
return new self(
387407
$this->keyType,

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ public function testBug7581(): void
983983
public function testBug7903(): void
984984
{
985985
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7903.php');
986-
$this->assertCount(23, $errors);
986+
$this->assertCount(24, $errors);
987987
}
988988

989989
public function testBug7901(): void

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,11 @@ public function testBug12553(): void
337337
$this->analyse([__DIR__ . '/data/bug-12553.php'], []);
338338
}
339339

340+
public function testBugMultiDimLoop(): void
341+
{
342+
$this->analyse([__DIR__ . '/data/bug-nullCoalesceMultiDimLoop.php'], []);
343+
}
344+
340345
public function testIssetAfterRememberedConstructor(): void
341346
{
342347
$this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace BugNullCoalesceMultiDimLoop;
4+
5+
/**
6+
* @param array<int, array{1: int, 2: int, 3: float}> $rows
7+
*/
8+
function foo(array $rows): mixed
9+
{
10+
$itemMap = [];
11+
12+
foreach ($rows as $row) {
13+
$x = $row[1];
14+
$month = $row[2];
15+
16+
$itemMap[$x][$month]['foo'] ??= 5;
17+
$itemMap[$x][$month]['bar'] ??= 5;
18+
19+
$itemMap[$x][$month]['amount'] ??= 0.0;
20+
}
21+
22+
return $itemMap;
23+
}

0 commit comments

Comments
 (0)