Skip to content

Commit 08a6ce5

Browse files
Fix
1 parent c87456e commit 08a6ce5

File tree

5 files changed

+118
-5
lines changed

5 files changed

+118
-5
lines changed

src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use PHPStan\Rules\RuleLevelHelper;
1313
use PHPStan\Type\BenevolentUnionType;
1414
use PHPStan\Type\ErrorType;
15+
use PHPStan\Type\NeverType;
1516
use PHPStan\Type\Type;
17+
use PHPStan\Type\TypeCombinator;
1618
use PHPStan\Type\TypeUtils;
1719
use PHPStan\Type\VerbosityLevel;
1820
use function count;
@@ -60,6 +62,15 @@ public function check(
6062
}
6163

6264
if ($type->hasOffsetValueType($dimType)->no()) {
65+
if ($type->isArray()->yes()) {
66+
$validArrayDimType = TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType);
67+
if ($validArrayDimType instanceof NeverType) {
68+
// Already reported by InvalidKeyInArrayDimFetchRule
69+
return [];
70+
}
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,32 @@ public function check(
7687
$flattenedTypes = TypeUtils::flattenTypes($type);
7788
}
7889

90+
$validArrayDimType = TypeCombinator::intersect(AllowedArrayKeysTypes::getType(), $dimType);
91+
7992
foreach ($flattenedTypes as $innerType) {
93+
$dimTypeToCheck = $innerType->isArray()->yes() ? $validArrayDimType : $dimType;
94+
8095
if (
8196
$this->reportPossiblyNonexistentGeneralArrayOffset
8297
&& $innerType->isArray()->yes()
8398
&& !$innerType->isConstantArray()->yes()
84-
&& !$innerType->hasOffsetValueType($dimType)->yes()
99+
&& !$innerType->hasOffsetValueType($dimTypeToCheck)->yes()
85100
) {
86101
$report = true;
87102
break;
88103
}
89104
if (
90105
$this->reportPossiblyNonexistentConstantArrayOffset
91106
&& $innerType->isConstantArray()->yes()
92-
&& !$innerType->hasOffsetValueType($dimType)->yes()
107+
&& !$innerType->hasOffsetValueType($dimTypeToCheck)->yes()
93108
) {
94109
$report = true;
95110
break;
96111
}
97-
if ($dimType instanceof BenevolentUnionType) {
98-
$flattenedInnerTypes = [$dimType];
112+
if ($dimTypeToCheck instanceof BenevolentUnionType) {
113+
$flattenedInnerTypes = [$dimTypeToCheck];
99114
} else {
100-
$flattenedInnerTypes = TypeUtils::flattenTypes($dimType);
115+
$flattenedInnerTypes = TypeUtils::flattenTypes($dimTypeToCheck);
101116
}
102117
foreach ($flattenedInnerTypes as $innerDimType) {
103118
if (

src/Type/ArrayType.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
272272
if ($offsetArrayKeyType instanceof ErrorType) {
273273
$allowedArrayKeys = AllowedArrayKeysTypes::getType();
274274
$offsetArrayKeyType = TypeCombinator::intersect($allowedArrayKeys, $offsetType)->toArrayKey();
275+
if ($offsetArrayKeyType instanceof NeverType) {
276+
return TrinaryLogic::createNo();
277+
}
275278
}
276279
$offsetType = $offsetArrayKeyType;
277280

src/Type/Constant/ConstantArrayType.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,9 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
586586
if ($offsetArrayKeyType instanceof ErrorType) {
587587
$allowedArrayKeys = AllowedArrayKeysTypes::getType();
588588
$offsetArrayKeyType = TypeCombinator::intersect($allowedArrayKeys, $offsetType)->toArrayKey();
589+
if ($offsetArrayKeyType instanceof NeverType) {
590+
return TrinaryLogic::createNo();
591+
}
589592
}
590593

591594
return $this->recursiveHasOffsetValueType($offsetArrayKeyType);

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,55 @@ public function testBug13538(): void
10241024
]);
10251025
}
10261026

1027+
public function testPR4385(): void
1028+
{
1029+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
1030+
$this->reportPossiblyNonexistentConstantArrayOffset = true;
1031+
1032+
$this->analyse([__DIR__ . '/data/pr-4385.php'], [
1033+
[
1034+
'Offset int might not exist on array<int>.',
1035+
24,
1036+
],
1037+
[
1038+
'Offset string might not exist on array<int>.',
1039+
25,
1040+
],
1041+
[
1042+
'Offset array<int>|int might not exist on array<int>.',
1043+
28,
1044+
],
1045+
[
1046+
'Offset array<int>|string might not exist on array<int>.',
1047+
29,
1048+
],
1049+
[
1050+
'Offset 0|array<int> might not exist on array<int>.',
1051+
30,
1052+
],
1053+
[
1054+
'Offset int might not exist on array{string}.',
1055+
33,
1056+
],
1057+
[
1058+
'Offset string might not exist on array{string}.',
1059+
34,
1060+
],
1061+
[
1062+
'Offset array<int>|int might not exist on array{string}.',
1063+
37,
1064+
],
1065+
[
1066+
'Offset array<int>|string might not exist on array{string}.',
1067+
38,
1068+
],
1069+
[
1070+
'Offset array<int>|int might not exist on array<int>|string.',
1071+
41,
1072+
],
1073+
]);
1074+
}
1075+
10271076
public function testBug12805(): void
10281077
{
10291078
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
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];
27+
$array[$arrayOrObject];
28+
$array[$arrayOrInt];
29+
$array[$arrayOrString];
30+
$array[$arrayOrZero];
31+
32+
$constantArray[$array];
33+
$constantArray[$int];
34+
$constantArray[$string];
35+
$constantArray[$object];
36+
$constantArray[$arrayOrObject];
37+
$constantArray[$arrayOrInt];
38+
$constantArray[$arrayOrString];
39+
$constantArray[$arrayOrZero];
40+
41+
$arrayOrString[$arrayOrInt];
42+
}
43+
}

0 commit comments

Comments
 (0)