Skip to content

Commit b242fc5

Browse files
committed
Fix "array_rand() - offset might not exists"
1 parent 47364df commit b242fc5

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,32 @@ public function specifyTypesInCondition(
687687
if ($context->null()) {
688688
$specifiedTypes = $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->expr, $context)->setRootExpr($expr);
689689

690+
// infer $arr[$key] after $arr[array_rand($arr)]
691+
if (
692+
$expr->expr instanceof FuncCall
693+
&& $expr->expr->name instanceof Name
694+
&& in_array($expr->expr->name->toLowerString(), ['array_rand'], true)
695+
&& count($expr->expr->getArgs()) >= 1
696+
) {
697+
$arrayArg = $expr->expr->getArgs()[0]->value;
698+
$numArg = $expr->expr->getArgs()[1]->value;
699+
$one = new ConstantIntegerType(1);
700+
$arrayType = $scope->getType($arrayArg);
701+
702+
if (
703+
$arrayType->isArray()->yes()
704+
&& $arrayType->isIterableAtLeastOnce()->yes()
705+
&& ($numArg === null || $one->isSuperTypeOf($scope->getType($numArg))->yes())
706+
) {
707+
$dimFetch = new ArrayDimFetch($arrayArg, $expr->var);
708+
$iterableValueType = $arrayType->getFirstIterableValueType();
709+
710+
return $specifiedTypes->unionWith(
711+
$this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope),
712+
);
713+
}
714+
}
715+
690716
// infer $arr[$key] after $key = array_key_first/last($arr)
691717
if (
692718
$expr->expr instanceof FuncCall

src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Rules\RuleErrorBuilder;
1515
use PHPStan\Rules\RuleLevelHelper;
1616
use PHPStan\TrinaryLogic;
17+
use PHPStan\Type\Constant\ConstantIntegerType;
1718
use PHPStan\Type\ErrorType;
1819
use PHPStan\Type\Type;
1920
use PHPStan\Type\VerbosityLevel;
@@ -129,6 +130,30 @@ public function processNode(Node $node, Scope $scope): array
129130
}
130131
}
131132

133+
if (
134+
$node->dim instanceof Node\Expr\FuncCall
135+
&& $node->dim->name instanceof Node\Name
136+
&& $node->dim->name->toLowerString() === 'array_rand'
137+
&& count($node->dim->getArgs()) >= 1
138+
) {
139+
$arrayArg = $node->dim->getArgs()[0]->value;
140+
$numArg = $node->dim->getArgs()[1]->value;
141+
142+
$one = new ConstantIntegerType(1);
143+
$arrayType = $scope->getType($arrayArg);
144+
if (
145+
$arrayArg instanceof Node\Expr\Variable
146+
&& $node->var instanceof Node\Expr\Variable
147+
&& is_string($arrayArg->name)
148+
&& $arrayArg->name === $node->var->name
149+
&& $arrayType->isArray()->yes()
150+
&& $arrayType->isIterableAtLeastOnce()->yes()
151+
&& ($numArg === null || $one->isSuperTypeOf($scope->getType($numArg))->yes())
152+
) {
153+
return [];
154+
}
155+
}
156+
132157
if (
133158
$node->dim instanceof Node\Expr\BinaryOp\Minus
134159
&& $node->dim->left instanceof Node\Expr\FuncCall

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,23 @@ public function testBug12593(): void
942942
$this->analyse([__DIR__ . '/data/bug-12593.php'], []);
943943
}
944944

945+
public function testBug12981(): void
946+
{
947+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
948+
949+
$this->analyse([__DIR__ . '/data/bug-12981.php'], [
950+
[
951+
'Offset array<int, int|string> might not exist on non-empty-array<bool|float|int|string>.',
952+
31
953+
],
954+
[
955+
'Offset array<int, int|string> might not exist on non-empty-array<bool|float|int|string>.',
956+
33
957+
],
958+
]);
959+
}
960+
961+
945962
public function testBugObject(): void
946963
{
947964
$this->analyse([__DIR__ . '/data/bug-object.php'], [
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12981;
4+
5+
use function PHPStan\dumpType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class HelloWorld
9+
{
10+
/** @param non-empty-array<scalar> $arr */
11+
public function sayHello(array $arr): void
12+
{
13+
echo $arr[array_rand($arr)];
14+
$randIndex = array_rand($arr);
15+
echo $arr[$randIndex];
16+
}
17+
18+
/** @param non-empty-array<scalar> $arr */
19+
public function sayHello1(array $arr): void
20+
{
21+
$num = 1;
22+
echo $arr[array_rand($arr, $num)];
23+
$randIndex = array_rand($arr, $num);
24+
echo $arr[$randIndex];
25+
}
26+
27+
/** @param non-empty-array<scalar> $arr */
28+
public function sayHello2(array $arr): void
29+
{
30+
$num = 5;
31+
echo $arr[array_rand($arr, $num)];
32+
$randIndex = array_rand($arr, $num);
33+
echo $arr[$randIndex];
34+
}
35+
}

0 commit comments

Comments
 (0)