Skip to content

Commit 3147a35

Browse files
Do not report non existent offset for invalid ones
1 parent 9d7cddb commit 3147a35

File tree

7 files changed

+131
-44
lines changed

7 files changed

+131
-44
lines changed

src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
use PHPStan\Rules\RuleLevelHelper;
1313
use PHPStan\Type\BenevolentUnionType;
1414
use PHPStan\Type\ErrorType;
15+
use PHPStan\Type\MixedType;
16+
use PHPStan\Type\NeverType;
1517
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeCombinator;
1619
use PHPStan\Type\TypeUtils;
1720
use PHPStan\Type\VerbosityLevel;
1821
use function count;
@@ -60,6 +63,14 @@ public function check(
6063
}
6164

6265
if ($type->hasOffsetValueType($dimType)->no()) {
66+
if ($type->isArray()->yes()) {
67+
$validArrayDimType = TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType);
68+
if ($validArrayDimType instanceof NeverType) {
69+
// Already reported by InvalidKeyInArrayDimFetchRule
70+
return [];
71+
}
72+
}
73+
6374
return [
6475
RuleErrorBuilder::message(sprintf('Offset %s does not exist on %s.', $dimType->describe(count($dimType->getConstantStrings()) > 0 ? VerbosityLevel::precise() : VerbosityLevel::value()), $type->describe(VerbosityLevel::value())))
6576
->identifier('offsetAccess.notFound')
@@ -76,28 +87,34 @@ public function check(
7687
$flattenedTypes = TypeUtils::flattenTypes($type);
7788
}
7889

90+
$validArrayDimType = $dimType instanceof MixedType
91+
? $dimType
92+
: TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType);
93+
7994
foreach ($flattenedTypes as $innerType) {
95+
$dimTypeToCheck = $innerType->isArray()->yes() ? $validArrayDimType : $dimType;
96+
8097
if (
8198
$this->reportPossiblyNonexistentGeneralArrayOffset
8299
&& $innerType->isArray()->yes()
83100
&& !$innerType->isConstantArray()->yes()
84-
&& !$innerType->hasOffsetValueType($dimType)->yes()
101+
&& !$innerType->hasOffsetValueType($dimTypeToCheck)->yes()
85102
) {
86103
$report = true;
87104
break;
88105
}
89106
if (
90107
$this->reportPossiblyNonexistentConstantArrayOffset
91108
&& $innerType->isConstantArray()->yes()
92-
&& !$innerType->hasOffsetValueType($dimType)->yes()
109+
&& !$innerType->hasOffsetValueType($dimTypeToCheck)->yes()
93110
) {
94111
$report = true;
95112
break;
96113
}
97-
if ($dimType instanceof BenevolentUnionType) {
98-
$flattenedInnerTypes = [$dimType];
114+
if ($dimTypeToCheck instanceof BenevolentUnionType) {
115+
$flattenedInnerTypes = [$dimTypeToCheck];
99116
} else {
100-
$flattenedInnerTypes = TypeUtils::flattenTypes($dimType);
117+
$flattenedInnerTypes = TypeUtils::flattenTypes($dimTypeToCheck);
101118
}
102119
foreach ($flattenedInnerTypes as $innerDimType) {
103120
if (

tests/PHPStan/Levels/data/arrayOffsetAccess-3.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,5 @@
33
"message": "Invalid array key type DateTimeImmutable.",
44
"line": 17,
55
"ignorable": true
6-
},
7-
{
8-
"message": "Offset DateTimeImmutable does not exist on array.",
9-
"line": 17,
10-
"ignorable": true
116
}
127
]

tests/PHPStan/Levels/data/arrayOffsetAccess-7.json

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,24 @@
11
[
2-
{
3-
"message": "Offset int|object might not exist on array.",
4-
"line": 19,
5-
"ignorable": true
6-
},
72
{
83
"message": "Possibly invalid array key type int|object.",
94
"line": 19,
105
"ignorable": true
116
},
12-
{
13-
"message": "Offset object|null might not exist on array.",
14-
"line": 20,
15-
"ignorable": true
16-
},
177
{
188
"message": "Possibly invalid array key type object|null.",
199
"line": 20,
2010
"ignorable": true
2111
},
22-
{
23-
"message": "Offset DateTimeImmutable might not exist on array|ArrayAccess.",
24-
"line": 26,
25-
"ignorable": true
26-
},
2712
{
2813
"message": "Possibly invalid array key type DateTimeImmutable.",
2914
"line": 26,
3015
"ignorable": true
3116
},
32-
{
33-
"message": "Offset int|object might not exist on array|ArrayAccess.",
34-
"line": 28,
35-
"ignorable": true
36-
},
3717
{
3818
"message": "Possibly invalid array key type int|object.",
3919
"line": 28,
4020
"ignorable": true
4121
},
42-
{
43-
"message": "Offset object|null might not exist on array|ArrayAccess.",
44-
"line": 29,
45-
"ignorable": true
46-
},
4722
{
4823
"message": "Possibly invalid array key type object|null.",
4924
"line": 29,

tests/PHPStan/Levels/data/stringOffsetAccess-3.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,5 @@
1818
"message": "Invalid array key type stdClass.",
1919
"line": 59,
2020
"ignorable": true
21-
},
22-
{
23-
"message": "Offset stdClass does not exist on array{baz: 21}|array{foo: 17, bar: 19}.",
24-
"line": 59,
25-
"ignorable": true
2621
}
2722
]

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -940,10 +940,6 @@ public function testBugObject(): void
940940
'Offset int|object does not exist on array{baz: 21}|array{foo: 17, bar: 19}.',
941941
12,
942942
],
943-
[
944-
'Offset object does not exist on array<string, int>.',
945-
21,
946-
],
947943
]);
948944
}
949945

@@ -1040,6 +1036,60 @@ public function testBug13538(): void
10401036
]);
10411037
}
10421038

1039+
public function testPR4385(): void
1040+
{
1041+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
1042+
$this->reportPossiblyNonexistentConstantArrayOffset = true;
1043+
1044+
$this->analyse([__DIR__ . '/data/pr-4385.php'], [
1045+
[
1046+
'Offset int might not exist on array<int>.',
1047+
24,
1048+
],
1049+
[
1050+
'Offset string might not exist on array<int>.',
1051+
25,
1052+
],
1053+
[
1054+
'Offset array<int>|int might not exist on array<int>.',
1055+
28,
1056+
],
1057+
[
1058+
'Offset array<int>|string might not exist on array<int>.',
1059+
29,
1060+
],
1061+
[
1062+
'Offset 0|array<int> might not exist on array<int>.',
1063+
30,
1064+
],
1065+
[
1066+
'Offset int might not exist on array{string}.',
1067+
33,
1068+
],
1069+
[
1070+
'Offset string might not exist on array{string}.',
1071+
34,
1072+
],
1073+
[
1074+
'Offset array<int>|int might not exist on array{string}.',
1075+
37,
1076+
],
1077+
[
1078+
'Offset array<int>|string might not exist on array{string}.',
1079+
38,
1080+
],
1081+
[
1082+
'Offset array<int>|int might not exist on array<int>|string.',
1083+
41,
1084+
],
1085+
]);
1086+
}
1087+
1088+
public function testPR4385Bis(): void
1089+
{
1090+
$this->analyse([__DIR__ . '/data/pr-4385-bis.php'], []);
1091+
}
1092+
10431093
public function testBug12805(): void
10441094
{
10451095
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pr4385Bis;
4+
5+
class HelloWorld
6+
{
7+
/** @param array<string, int> $a */
8+
public function sayHello(array $a, mixed $m): void
9+
{
10+
echo $a[$m];
11+
}
12+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Pr4385;
4+
5+
class Foo
6+
{
7+
/**
8+
* @param array<int> $array
9+
* @param int $int
10+
* @param string $string
11+
* @param object $object
12+
* @param array{0: string} $constantArray
13+
*
14+
* @return void
15+
*/
16+
public function test($array, $int, $string, $object, $constantArray)
17+
{
18+
$arrayOrObject = rand(0, 1) ? $array : $object;
19+
$arrayOrInt = rand(0, 1) ? $array : $int;
20+
$arrayOrString = rand(0, 1) ? $array : $string;
21+
$arrayOrZero = rand(0, 1) ? $array : 0;
22+
23+
$array[$array];
24+
$array[$int];
25+
$array[$string];
26+
$array[$object]; // Reported by InvalidKeyInArrayDimFetchRule
27+
$array[$arrayOrObject]; // Reported by InvalidKeyInArrayDimFetchRule
28+
$array[$arrayOrInt];
29+
$array[$arrayOrString];
30+
$array[$arrayOrZero];
31+
32+
$constantArray[$array];
33+
$constantArray[$int];
34+
$constantArray[$string];
35+
$constantArray[$object]; // Reported by InvalidKeyInArrayDimFetchRule
36+
$constantArray[$arrayOrObject]; // Reported by InvalidKeyInArrayDimFetchRule
37+
$constantArray[$arrayOrInt];
38+
$constantArray[$arrayOrString];
39+
$constantArray[$arrayOrZero]; // Reported by InvalidKeyInArrayDimFetchRule
40+
41+
$arrayOrString[$arrayOrInt];
42+
}
43+
}

0 commit comments

Comments
 (0)