Skip to content

Commit

Permalink
Fix false positive non-existing-offset after array_search()
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Jan 5, 2025
1 parent 5908fe9 commit 6a187b6
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 1 deletion.
26 changes: 25 additions & 1 deletion src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ public function specifyTypesInCondition(
if (!$scope instanceof MutatingScope) {
throw new ShouldNotHappenException();
}

if ($context->null()) {
$specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr);

Expand Down Expand Up @@ -715,7 +716,30 @@ public function specifyTypesInCondition(
return $specifiedTypes;
}

return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr);
$specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context)->setRootExpr($expr);

if ($context->true()) {
// infer $arr[$key] after $key = array_search($needle, $arr)
if (
$expr->expr instanceof FuncCall
&& $expr->expr->name instanceof Name
&& in_array($expr->expr->name->toLowerString(), ['array_search'], true)
&& count($expr->expr->getArgs()) >= 2
) {
$arrayArg = $expr->expr->getArgs()[1]->value;
$arrayType = $scope->getType($arrayArg);

if ($arrayType->isArray()->yes()) {
$dimFetch = new ArrayDimFetch($arrayArg, $expr->var);
$iterableValueType = $arrayType->getIterableValueType();

return $specifiedTypes->unionWith(
$this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope),
);
}
}
}
return $specifiedTypes;
} elseif (
$expr instanceof Expr\Isset_
&& count($expr->vars) > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,4 +812,16 @@ public function testArrayDimFetchAfterCount(): void
]);
}

public function testArrayDimFetchAfterArraySearch(): void
{
$this->reportPossiblyNonexistentGeneralArrayOffset = true;

$this->analyse([__DIR__ . '/data/array-dim-after-array-search.php'], [
[
'Offset int|string might not exist on array.',
20,
],
]);
}

}
37 changes: 37 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/array-dim-after-array-search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace ArrayDimAfterArraySeach;

class HelloWorld
{
public function doFoo(array $arr, string $needle): string
{
if (($key = array_search($needle, $arr, true)) !== false) {
echo $arr[$key];
}
}

public function doBar(array $arr, string $needle): string
{
$key = array_search($needle, $arr, true);
if ($key !== false) {
echo $arr[$key];
}
}

public function doFooBar(array $arr, string $needle): string
{
if (($key = array_search($needle, $arr, false)) !== false) {
echo $arr[$key];
}
}

public function doBaz(array $arr, string $needle): string
{
if (($key = array_search($needle, $arr)) !== false) {
echo $arr[$key];
}
}
}

0 comments on commit 6a187b6

Please sign in to comment.