Skip to content

Commit 40ff6dd

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix deeply nested array offset tracking losing required keys
- Added recursive handling in ArrayType::setExistingOffsetValueType() for non-constant array item types, so nested arrays at any depth correctly propagate key optionality updates - New regression test in tests/PHPStan/Analyser/nsrt/bug-13637.php - Updated bug-7903 expected error count (39→36) as fix resolves 3 false positives - Root cause: when itemType was a non-constant array (e.g. array<int, array{...}>), setExistingOffsetValueType fell through to TypeCombinator::union which re-merged the old optional keys with the updated required keys Closes phpstan/phpstan#13637
1 parent d32efcb commit 40ff6dd

3 files changed

Lines changed: 63 additions & 1 deletion

File tree

src/Type/ArrayType.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,23 @@ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): T
410410
}
411411
}
412412

413+
if (
414+
!$this->itemType->isConstantArray()->yes()
415+
&& $this->itemType->isArray()->yes()
416+
&& $valueType->isArray()->yes()
417+
) {
418+
$newItemType = $this->itemType->setExistingOffsetValueType(
419+
$valueType->getIterableKeyType(),
420+
$valueType->getIterableValueType(),
421+
);
422+
if (!$newItemType->equals($this->itemType)) {
423+
return new self(
424+
$this->keyType,
425+
$newItemType,
426+
);
427+
}
428+
}
429+
413430
return new self(
414431
$this->keyType,
415432
TypeCombinator::union($this->itemType, $valueType),

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ public function testBug7581(): void
998998
public function testBug7903(): void
999999
{
10001000
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7903.php');
1001-
$this->assertCount(39, $errors);
1001+
$this->assertCount(36, $errors);
10021002
}
10031003

10041004
public function testBug7901(): void
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13637;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @return array<int, array<int, array<int, array{abc: int, def: int, ghi: int}>>>
9+
*/
10+
function DoesNotWork() : array {
11+
$final = [];
12+
13+
for ($i = 0; $i < 5; $i++) {
14+
$j = $i * 2;
15+
$k = $j +1;
16+
$l = $i * 3;
17+
$final[$i][$j][$k]['abc'] = $i;
18+
$final[$i][$j][$k]['def'] = $i;
19+
$final[$i][$j][$k]['ghi'] = $i;
20+
21+
assertType("array{abc: int<0, 4>, def: int<0, 4>, ghi: int<0, 4>}", $final[$i][$j][$k]);
22+
}
23+
24+
return $final;
25+
}
26+
27+
/**
28+
* @return array<int, array<int, array{abc: int, def: int, ghi: int}>>
29+
*/
30+
function thisWorks() : array {
31+
$final = [];
32+
33+
for ($i = 0; $i < 5; $i++) {
34+
$j = $i * 2;
35+
$k = $j +1;
36+
$l = $i * 3;
37+
$final[$i][$j]['abc'] = $i;
38+
$final[$i][$j]['def'] = $i;
39+
$final[$i][$j]['ghi'] = $i;
40+
41+
assertType("array{abc: int<0, 4>, def: int<0, 4>, ghi: int<0, 4>}", $final[$i][$j]);
42+
}
43+
44+
return $final;
45+
}

0 commit comments

Comments
 (0)