Skip to content

Commit 09a1688

Browse files
VincentLangletphpstan-bot
authored andcommitted
Report non existent offset on non empty array (phpstan#4399)
1 parent 658da46 commit 09a1688

File tree

9 files changed

+74
-76
lines changed

9 files changed

+74
-76
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,12 +1731,6 @@ parameters:
17311731
count: 1
17321732
path: src/Type/TypeCombinator.php
17331733

1734-
-
1735-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.'
1736-
identifier: phpstanApi.instanceofType
1737-
count: 2
1738-
path: src/Type/TypeUtils.php
1739-
17401734
-
17411735
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
17421736
identifier: phpstanApi.instanceofType

src/Internal/CombinationsHelper.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace PHPStan\Internal;
44

5+
use Traversable;
56
use function array_pop;
67

78
final class CombinationsHelper
89
{
910

1011
/**
1112
* @param array<iterable<mixed>> $arrays
12-
* @return iterable<list<mixed>>
13+
* @return Traversable<list<mixed>>
1314
*/
1415
public static function combinations(array $arrays): iterable
1516
{

src/Type/Constant/ConstantArrayType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ public function getAllArrays(): array
216216
} else {
217217
$optionalKeysCombinations = [
218218
[],
219+
array_slice($this->optionalKeys, 0, 1, true),
220+
array_slice($this->optionalKeys, -1, 1, true),
219221
$this->optionalKeys,
220222
];
221223
}

src/Type/TypeUtils.php

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
namespace PHPStan\Type;
44

5+
use PHPStan\Internal\CombinationsHelper;
56
use PHPStan\Type\Accessory\AccessoryType;
67
use PHPStan\Type\Accessory\HasPropertyType;
7-
use PHPStan\Type\Constant\ConstantArrayType;
88
use PHPStan\Type\Constant\ConstantIntegerType;
99
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
1010
use PHPStan\Type\Generic\TemplateType;
1111
use PHPStan\Type\Generic\TemplateUnionType;
1212
use PHPStan\Type\Traverser\LateResolvableTraverser;
13+
use function array_filter;
14+
use function array_map;
1315
use function array_merge;
16+
use function iterator_to_array;
1417

1518
/**
1619
* @api
@@ -130,26 +133,34 @@ public static function toStrictUnion(Type $type): Type
130133
*/
131134
public static function flattenTypes(Type $type): array
132135
{
133-
if ($type instanceof ConstantArrayType) {
134-
return $type->getAllArrays();
135-
}
136-
137136
if ($type instanceof UnionType) {
138137
$types = [];
139138
foreach ($type->getTypes() as $innerType) {
140-
if ($innerType instanceof ConstantArrayType) {
141-
foreach ($innerType->getAllArrays() as $array) {
142-
$types[] = $array;
143-
}
144-
continue;
139+
$flattenTypes = self::flattenTypes($innerType);
140+
foreach ($flattenTypes as $flattenType) {
141+
$types[] = $flattenType;
145142
}
146-
147-
$types[] = $innerType;
148143
}
149144

150145
return $types;
151146
}
152147

148+
$constantArrays = $type->getConstantArrays();
149+
if ($constantArrays !== []) {
150+
$newTypes = [];
151+
foreach ($constantArrays as $constantArray) {
152+
$newTypes[] = $constantArray->getAllArrays();
153+
}
154+
155+
return array_filter(
156+
array_map(
157+
static fn (array $types): Type => TypeCombinator::intersect(...$types),
158+
iterator_to_array(CombinationsHelper::combinations($newTypes)),
159+
),
160+
static fn (Type $type): bool => !$type instanceof NeverType,
161+
);
162+
}
163+
153164
return [$type];
154165
}
155166

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(24, $errors);
1001+
$this->assertCount(39, $errors);
10021002
}
10031003

10041004
public function testBug7901(): void

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,6 @@ public function testBug4747(): void
385385
$this->analyse([__DIR__ . '/data/bug-4747.php'], []);
386386
}
387387

388-
public function testBug6379(): void
389-
{
390-
$this->analyse([__DIR__ . '/data/bug-6379.php'], []);
391-
}
392-
393388
#[RequiresPhp('>= 8.0')]
394389
public function testBug4885(): void
395390
{
@@ -929,13 +924,6 @@ public function testBug4809(): void
929924
$this->analyse([__DIR__ . '/data/bug-4809.php'], []);
930925
}
931926

932-
public function testBug11602(): void
933-
{
934-
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
935-
936-
$this->analyse([__DIR__ . '/data/bug-11602.php'], []);
937-
}
938-
939927
public function testBug12593(): void
940928
{
941929
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
@@ -1123,6 +1111,28 @@ public function testPR4385Bis(): void
11231111
$this->analyse([__DIR__ . '/data/pr-4385-bis.php'], []);
11241112
}
11251113

1114+
public function testBug7143(): void
1115+
{
1116+
$this->analyse([__DIR__ . '/data/bug-7143.php'], [
1117+
[
1118+
"Offset 'foo' might not exist on non-empty-array{foo?: string, bar?: string}.",
1119+
12,
1120+
],
1121+
[
1122+
"Offset 'bar' might not exist on non-empty-array{foo?: string, bar?: string}.",
1123+
13,
1124+
],
1125+
[
1126+
"Offset 'foo' might not exist on non-empty-array{foo?: string, bar?: string, 1?: 1, 2?: 2, 3?: 3, 4?: 4, 5?: 5, 6?: 6, ...}.",
1127+
21,
1128+
],
1129+
[
1130+
"Offset 'bar' might not exist on non-empty-array{foo?: string, bar?: string, 1?: 1, 2?: 2, 3?: 3, 4?: 4, 5?: 5, 6?: 6, ...}.",
1131+
22,
1132+
],
1133+
]);
1134+
}
1135+
11261136
public function testBug12805(): void
11271137
{
11281138
$this->reportPossiblyNonexistentGeneralArrayOffset = true;

tests/PHPStan/Rules/Arrays/data/bug-11602.php

Lines changed: 0 additions & 23 deletions
This file was deleted.

tests/PHPStan/Rules/Arrays/data/bug-6379.php

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Bug7143;
4+
5+
class Foo
6+
{
7+
/**
8+
* @param array{foo?: string, bar?: string}&non-empty-array $arr
9+
*/
10+
public function test(array $arr): void
11+
{
12+
echo $arr['foo'];
13+
echo $arr['bar'];
14+
}
15+
16+
/**
17+
* @param array{foo?: string, bar?: string, 1?:1, 2?:2, 3?:3, 4?:4, 5?:5, 6?:6, 7?:7, 8?:8, 9?:9}&non-empty-array $arr
18+
*/
19+
public function test2(array $arr): void
20+
{
21+
echo $arr['foo'];
22+
echo $arr['bar'];
23+
}
24+
}

0 commit comments

Comments
 (0)