Skip to content

Commit 235f9a2

Browse files
committed
Try fixing assigning array offsets on possibly undefined nested array offsets
1 parent 08465ec commit 235f9a2

File tree

7 files changed

+160
-4
lines changed

7 files changed

+160
-4
lines changed

src/Analyser/NodeScopeResolver.php

+9-4
Original file line numberDiff line numberDiff line change
@@ -5756,24 +5756,26 @@ static function (): void {
57565756
*/
57575757
private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type $offsetValueType, Type $valueToWrite): Type
57585758
{
5759-
$offsetValueTypeStack = [$offsetValueType];
5759+
$offsetValueTypeStack = [[$offsetValueType, TrinaryLogic::createYes()]];
57605760
foreach (array_slice($offsetTypes, 0, -1) as $offsetType) {
57615761
if ($offsetType === null) {
5762+
$has = TrinaryLogic::createYes();
57625763
$offsetValueType = new ConstantArrayType([], []);
57635764

57645765
} else {
5766+
$has = $offsetValueType->hasOffsetValueType($offsetType);
57655767
$offsetValueType = $offsetValueType->getOffsetValueType($offsetType);
57665768
if ($offsetValueType instanceof ErrorType) {
57675769
$offsetValueType = new ConstantArrayType([], []);
57685770
}
57695771
}
57705772

5771-
$offsetValueTypeStack[] = $offsetValueType;
5773+
$offsetValueTypeStack[] = [$offsetValueType, $has];
57725774
}
57735775

57745776
foreach (array_reverse($offsetTypes) as $i => $offsetType) {
57755777
/** @var Type $offsetValueType */
5776-
$offsetValueType = array_pop($offsetValueTypeStack);
5778+
[$offsetValueType, $has] = array_pop($offsetValueTypeStack);
57775779
if (
57785780
!$offsetValueType instanceof MixedType
57795781
&& !$offsetValueType->isConstantArray()->yes()
@@ -5788,7 +5790,10 @@ private function produceArrayDimFetchAssignValueToWrite(array $offsetTypes, Type
57885790
}
57895791
$offsetValueType = TypeCombinator::intersect($offsetValueType, TypeCombinator::union(...$types));
57905792
}
5791-
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite, $i === 0);
5793+
if (!$has->yes()) {
5794+
$offsetValueType = TypeCombinator::union($offsetValueType, new ConstantArrayType([], []));
5795+
}
5796+
$valueToWrite = $offsetValueType->setOffsetValueType($offsetType, $valueToWrite);
57925797
}
57935798

57945799
return $valueToWrite;
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10025;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class MyClass {
8+
public int $groupId;
9+
}
10+
11+
/**
12+
* @param list<MyClass> $foos
13+
* @param list<MyClass> $bars
14+
*/
15+
function x(array $foos, array $bars): void {
16+
$arr = [];
17+
foreach ($foos as $foo) {
18+
$arr[$foo->groupId]['foo'][] = $foo;
19+
}
20+
foreach ($bars as $bar) {
21+
$arr[$bar->groupId]['bar'][] = $bar;
22+
}
23+
24+
assertType('array<int, non-empty-array{foo?: non-empty-list<Bug10025\MyClass>, bar?: non-empty-list<Bug10025\MyClass>}>', $arr);
25+
}
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Bug10089;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Test
8+
{
9+
10+
protected function create_matrix(int $size): array
11+
{
12+
$size = min(8, $size);
13+
$matrix = [];
14+
for ($i = 0; $i < $size; $i++) {
15+
$matrix[] = array_fill(0, $size, 0);
16+
}
17+
18+
assertType('list<non-empty-list<0>>', $matrix);
19+
20+
$matrix[$size - 1][8] = 3;
21+
22+
assertType('non-empty-array<int, non-empty-array<int<0, max>, 0|3>>', $matrix);
23+
24+
return $matrix;
25+
}
26+
27+
}
28+
29+
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Bug10640;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function (array $a, array $b): void {
8+
$changes = [];
9+
foreach ($a as $add) {
10+
$changes[$add['id']]['add'][] = 1;
11+
}
12+
foreach ($b as $del) {
13+
$changes[$del['id']]['del'][] = 2;
14+
}
15+
assertType('array<non-empty-array{add?: non-empty-list<1>, del?: non-empty-list<2>}>', $changes);
16+
};
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12078;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @return array <string,string>
9+
*/
10+
function returnsData6M(): array
11+
{
12+
return ["A"=>'data A',"B"=>'Data B'];
13+
}
14+
/**
15+
* @return array <string,string>
16+
*/
17+
function returnsData3M(): array
18+
{
19+
return ["A"=>'data A',"C"=>'Data C'];
20+
}
21+
22+
function main(){
23+
$arrDataByKey = [];
24+
25+
$arrData6M = returnsData6M();
26+
if([] === $arrData6M){
27+
echo "No data for 6M\n";
28+
}else{
29+
foreach($arrData6M as $key => $data){
30+
$arrDataByKey[$key]['6M'][] = $data;
31+
}
32+
}
33+
34+
$arrData3M = returnsData3M();
35+
if([] === $arrData3M){
36+
echo "No data for 3M\n";
37+
}else{
38+
foreach($arrData3M as $key => $data){
39+
$arrDataByKey[$key]['3M'][] = $data;
40+
}
41+
}
42+
43+
assertType('array<string, non-empty-array{6M?: non-empty-list<string>, 3M?: non-empty-list<string>}>', $arrDataByKey);
44+
}

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -829,4 +829,10 @@ public function testArrayDimFetchAfterArraySearch(): void
829829
]);
830830
}
831831

832+
public function testBug11679(): void
833+
{
834+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
835+
$this->analyse([__DIR__ . '/data/bug-11679.php'], []);
836+
}
837+
832838
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Bug11679;
4+
5+
class WorkingExample
6+
{
7+
/** @var array{foo?: bool} */
8+
private array $arr = [];
9+
10+
public function sayHello(): bool
11+
{
12+
if (!isset($this->arr['foo'])) {
13+
$this->arr['foo'] = true;
14+
}
15+
return $this->arr['foo'];
16+
}
17+
}
18+
19+
class NonworkingExample
20+
{
21+
/** @var array<int, array{foo?: bool}> */
22+
private array $arr = [];
23+
24+
public function sayHello(int $index): bool
25+
{
26+
if (!isset($this->arr[$index]['foo'])) {
27+
$this->arr[$index]['foo'] = true;
28+
}
29+
return $this->arr[$index]['foo'];
30+
}
31+
}

0 commit comments

Comments
 (0)