Skip to content

Commit 54ca85b

Browse files
authored
Fix false positives on non-existing-offsets
1 parent 1b58626 commit 54ca85b

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

Diff for: src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php

+45
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
use PHPStan\Type\Type;
1414
use PHPStan\Type\VerbosityLevel;
1515
use function count;
16+
use function in_array;
17+
use function is_string;
1618
use function sprintf;
1719

1820
/**
@@ -96,6 +98,49 @@ public function processNode(Node $node, Scope $scope): array
9698
return [];
9799
}
98100

101+
if (
102+
$node->dim instanceof Node\Expr\FuncCall
103+
&& $node->dim->name instanceof Node\Name
104+
&& in_array($node->dim->name->toLowerString(), ['array_key_first', 'array_key_last'], true)
105+
&& count($node->dim->getArgs()) >= 1
106+
) {
107+
$arrayArg = $node->dim->getArgs()[0]->value;
108+
$arrayType = $scope->getType($arrayArg);
109+
if (
110+
$arrayArg instanceof Node\Expr\Variable
111+
&& $node->var instanceof Node\Expr\Variable
112+
&& is_string($arrayArg->name)
113+
&& $arrayArg->name === $node->var->name
114+
&& $arrayType->isArray()->yes()
115+
&& $arrayType->isIterableAtLeastOnce()->yes()
116+
) {
117+
return [];
118+
}
119+
}
120+
121+
if (
122+
$node->dim instanceof Node\Expr\BinaryOp\Minus
123+
&& $node->dim->left instanceof Node\Expr\FuncCall
124+
&& $node->dim->left->name instanceof Node\Name
125+
&& in_array($node->dim->left->name->toLowerString(), ['count', 'sizeof'], true)
126+
&& count($node->dim->left->getArgs()) >= 1
127+
&& $node->dim->right instanceof Node\Scalar\Int_
128+
&& $node->dim->right->value === 1
129+
) {
130+
$arrayArg = $node->dim->left->getArgs()[0]->value;
131+
$arrayType = $scope->getType($arrayArg);
132+
if (
133+
$arrayArg instanceof Node\Expr\Variable
134+
&& $node->var instanceof Node\Expr\Variable
135+
&& is_string($arrayArg->name)
136+
&& $arrayArg->name === $node->var->name
137+
&& $arrayType->isList()->yes()
138+
&& $arrayType->isIterableAtLeastOnce()->yes()
139+
) {
140+
return [];
141+
}
142+
}
143+
99144
return $this->nonexistentOffsetInArrayDimFetchCheck->check(
100145
$scope,
101146
$node->var,

Diff for: tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

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

832+
public function testArrayDimFetchOnArrayKeyFirsOrLastOrCount(): void
833+
{
834+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
835+
836+
$this->analyse([__DIR__ . '/data/array-dim-fetch-on-array-key-first-last.php'], [
837+
[
838+
'Offset 0|null might not exist on list<string>.',
839+
12,
840+
],
841+
[
842+
'Offset (int|string) might not exist on non-empty-list<string>.',
843+
16,
844+
],
845+
[
846+
'Offset int<-1, max> might not exist on non-empty-list<string>.',
847+
45,
848+
],
849+
]);
850+
}
851+
832852
public function testBug8649(): void
833853
{
834854
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace ArrayDimFetchOnArrayKeyFirstOrLast;
4+
5+
class Hello {
6+
/**
7+
* @param list<string> $hellos
8+
*/
9+
public function first(array $hellos, array $anotherArray): string
10+
{
11+
if (rand(0,1)) {
12+
return $hellos[array_key_first($hellos)];
13+
}
14+
if ($hellos !== []) {
15+
if ($anotherArray !== []) {
16+
return $hellos[array_key_first($anotherArray)];
17+
}
18+
19+
return $hellos[array_key_first($hellos)];
20+
}
21+
return '';
22+
}
23+
24+
/**
25+
* @param array<string> $hellos
26+
*/
27+
public function last(array $hellos): string
28+
{
29+
if ($hellos !== []) {
30+
return $hellos[array_key_last($hellos)];
31+
}
32+
return '';
33+
}
34+
35+
/**
36+
* @param list<string> $hellos
37+
*/
38+
public function countOnArray(array $hellos, array $anotherArray): string
39+
{
40+
if ($hellos === []) {
41+
return 'nothing';
42+
}
43+
44+
if (rand(0,1)) {
45+
return $hellos[count($anotherArray) - 1];
46+
}
47+
48+
return $hellos[count($hellos) - 1];
49+
}
50+
}

0 commit comments

Comments
 (0)