diff --git a/conf/config.neon b/conf/config.neon index dab6e0fa0c..bad0ef3c04 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -823,6 +823,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayRandFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayReduceFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..00444e498f --- /dev/null +++ b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php @@ -0,0 +1,63 @@ +getName() === 'array_rand'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $argsCount = count($functionCall->args); + if (count($functionCall->args) < 1) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $firstArgType = $scope->getType($functionCall->args[0]->value); + $isInteger = (new IntegerType())->isSuperTypeOf($firstArgType->getIterableKeyType()); + $isString = (new StringType())->isSuperTypeOf($firstArgType->getIterableKeyType()); + + if ($isInteger->yes()) { + $valueType = new IntegerType(); + } elseif ($isString->yes()) { + $valueType = new StringType(); + } else { + $valueType = new UnionType([new IntegerType(), new StringType()]); + } + + if ($argsCount < 2) { + return $valueType; + } + + $secondArgType = $scope->getType($functionCall->args[1]->value); + + if ($secondArgType instanceof ConstantIntegerType) { + if ($secondArgType->getValue() === 1) { + return $valueType; + } + + if ($secondArgType->getValue() >= 2) { + return new ArrayType(new IntegerType(), $valueType); + } + } + + return TypeCombinator::union($valueType, new ArrayType(new IntegerType(), $valueType)); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 6d23a8725e..9b1e5cfc40 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5507,6 +5507,70 @@ public function dataArrayFunctions(): array '\'foo\'', '$poppedFoo', ], + [ + 'int', + 'array_rand([1 => 1, 2 => "2"])', + ], + [ + 'string', + 'array_rand(["a" => 1, "b" => "2"])', + ], + [ + 'int|string', + 'array_rand(["a" => 1, 2 => "b"])', + ], + [ + 'int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed])', + ], + [ + 'int', + 'array_rand([1 => 1, 2 => "b"], 1)', + ], + [ + 'string', + 'array_rand(["a" => 1, "b" => "b"], 1)', + ], + [ + 'int|string', + 'array_rand(["a" => 1, 2 => "b"], 1)', + ], + [ + 'int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], 1)', + ], + [ + 'array', + 'array_rand([1 => 1, 2 => "b"], 2)', + ], + [ + 'array', + 'array_rand(["a" => 1, "b" => "b"], 2)', + ], + [ + 'array', + 'array_rand(["a" => 1, 2 => "b"], 2)', + ], + [ + 'array', + 'array_rand([1 => 1, 2 => "2", $mixed => $mixed], 2)', + ], + [ + 'array|int', + 'array_rand([1 => 1, 2 => "b"], $mixed)', + ], + [ + 'array|string', + 'array_rand(["a" => 1, "b" => "b"], $mixed)', + ], + [ + 'array|int|string', + 'array_rand(["a" => 1, 2 => "b"], $mixed)', + ], + [ + 'array|int|string', + 'array_rand([1 => 1, 2 => "b", $mixed => $mixed], $mixed)', + ], ]; }