diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 2723205dd8..049fc77497 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -5,12 +5,14 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\IntersectionType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; +use PHPStan\Type\TypeTraverser; +use PHPStan\Type\UnionType; class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -20,21 +22,29 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo return $functionReflection->getName() === 'array_reverse'; } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { if (!isset($functionCall->getArgs()[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } $type = $scope->getType($functionCall->getArgs()[0]->value); $preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new NeverType(); $preserveKeys = $preserveKeysType instanceof ConstantBooleanType ? $preserveKeysType->getValue() : false; - if ($type instanceof ConstantArrayType) { - return $type->reverse($preserveKeys); + if (!$type->isIterable()->yes()) { + return null; } - return $type; + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($preserveKeys): Type { + if ($type instanceof UnionType || $type instanceof IntersectionType) { + return $traverse($type); + } + if ($type instanceof ConstantArrayType) { + return $type->reverse($preserveKeys); + } + return $type; + }); } } diff --git a/tests/PHPStan/Analyser/data/array-reverse.php b/tests/PHPStan/Analyser/data/array-reverse.php index dc1eb8fd40..e5a205ac0a 100644 --- a/tests/PHPStan/Analyser/data/array-reverse.php +++ b/tests/PHPStan/Analyser/data/array-reverse.php @@ -19,9 +19,15 @@ public function normalArrays(array $a, array $b): void assertType('array', array_reverse($b, true)); } - /** @param array{a: 'foo', b: 'bar', c?: 'baz'} $a */ - public function constantArrays(array $a): void + /** + * @param array{a: 'foo', b: 'bar', c?: 'baz'} $a + * @param array{17: 'foo', 19: 'bar'}|array{foo: 17, bar: 19} $b + */ + public function constantArrays(array $a, array $b): void { + assertType('array{}', array_reverse([])); + assertType('array{}', array_reverse([], true)); + assertType('array{1337, null, 42}', array_reverse([42, null, 1337])); assertType('array{2: 1337, 1: null, 0: 42}', array_reverse([42, null, 1337], true)); @@ -36,5 +42,8 @@ public function constantArrays(array $a): void assertType('array{c?: \'baz\', b: \'bar\', a: \'foo\'}', array_reverse($a)); assertType('array{c?: \'baz\', b: \'bar\', a: \'foo\'}', array_reverse($a, true)); + + assertType('array{\'bar\', \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b)); + assertType('array{19: \'bar\', 17: \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b, true)); } }