Skip to content

Commit ce9e0fc

Browse files
committed
Fix false positive non-existing-offset after array_search()
1 parent 32d84e0 commit ce9e0fc

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

Diff for: src/Analyser/TypeSpecifier.php

+25-1
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ public function specifyTypesInCondition(
661661
if (!$scope instanceof MutatingScope) {
662662
throw new ShouldNotHappenException();
663663
}
664+
664665
if ($context->null()) {
665666
$specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr);
666667

@@ -715,7 +716,30 @@ public function specifyTypesInCondition(
715716
return $specifiedTypes;
716717
}
717718

718-
return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr);
719+
$specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr);
720+
721+
if ($context->true()) {
722+
// infer $arr[$key] after $key = array_search($needle, $arr)
723+
if (
724+
$expr->expr instanceof FuncCall
725+
&& $expr->expr->name instanceof Name
726+
&& in_array($expr->expr->name->toLowerString(), ['array_search'], true)
727+
&& count($expr->expr->getArgs()) >= 2
728+
) {
729+
$arrayArg = $expr->expr->getArgs()[1]->value;
730+
$arrayType = $scope->getType($arrayArg);
731+
732+
if ($arrayType->isArray()->yes()) {
733+
$dimFetch = new ArrayDimFetch($arrayArg, $expr->var);
734+
$iterableValueType = $arrayType->getIterableValueType();
735+
736+
return $specifiedTypes->unionWith(
737+
$this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope),
738+
);
739+
}
740+
}
741+
}
742+
return $specifiedTypes;
719743
} elseif (
720744
$expr instanceof Expr\Isset_
721745
&& count($expr->vars) > 0

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

+12
Original file line numberDiff line numberDiff line change
@@ -812,4 +812,16 @@ public function testArrayDimFetchAfterCount(): void
812812
]);
813813
}
814814

815+
public function testArrayDimFetchAfterArraySearch(): void
816+
{
817+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
818+
819+
$this->analyse([__DIR__ . '/data/array-dim-after-array-search.php'], [
820+
[
821+
'Offset int|string might not exist on array.',
822+
20,
823+
],
824+
]);
825+
}
826+
815827
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace ArrayDimAfterArraySeach;
6+
7+
class HelloWorld
8+
{
9+
public function doFoo(array $arr, string $needle): string
10+
{
11+
if (($key = array_search($needle, $arr, true)) !== false) {
12+
echo $arr[$key];
13+
}
14+
}
15+
16+
public function doBar(array $arr, string $needle): string
17+
{
18+
$key = array_search($needle, $arr, true);
19+
if ($key !== false) {
20+
echo $arr[$key];
21+
}
22+
}
23+
24+
public function doFooBar(array $arr, string $needle): string
25+
{
26+
if (($key = array_search($needle, $arr, false)) !== false) {
27+
echo $arr[$key];
28+
}
29+
}
30+
31+
public function doBaz(array $arr, string $needle): string
32+
{
33+
if (($key = array_search($needle, $arr)) !== false) {
34+
echo $arr[$key];
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)