Skip to content

Commit

Permalink
Properly support constant arrays in array_reverse
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm authored and ondrejmirtes committed May 24, 2022
1 parent 9b7ef13 commit 00ec4af
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 8 deletions.
22 changes: 16 additions & 6 deletions src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
});
}

}
13 changes: 11 additions & 2 deletions tests/PHPStan/Analyser/data/array-reverse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ public function normalArrays(array $a, array $b): void
assertType('array<string, int>', 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));

Expand All @@ -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));
}
}

0 comments on commit 00ec4af

Please sign in to comment.