Skip to content

Commit

Permalink
Some sort functions do not preserve a list
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 23, 2024
1 parent fbc6bca commit 034f731
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 5 deletions.
40 changes: 37 additions & 3 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2192,8 +2192,21 @@ static function (): void {
$arrayArg = $expr->getArgs()[0]->value;
$scope = $scope->assignExpression(
$arrayArg,
$this->getArraySortFunctionType($scope->getType($arrayArg)),
$this->getArraySortFunctionType($scope->getNativeType($arrayArg)),
$this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)),
$this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg)),
);
}

if (
$functionReflection !== null
&& in_array($functionReflection->getName(), ['natcasesort', 'natsort', 'arsort', 'asort', 'ksort', 'krsort', 'uasort', 'uksort'], true)
&& count($expr->getArgs()) >= 1
) {
$arrayArg = $expr->getArgs()[0]->value;
$scope = $scope->assignExpression(
$arrayArg,
$this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)),
$this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg)),
);
}

Expand Down Expand Up @@ -3157,7 +3170,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
return $arrayType;
}

private function getArraySortFunctionType(Type $type): Type
private function getArraySortPreserveListFunctionType(Type $type): Type
{
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
if ($isIterableAtLeastOnce->no()) {
Expand All @@ -3182,6 +3195,27 @@ private function getArraySortFunctionType(Type $type): Type
});
}

private function getArraySortDoNotPreserveListFunctionType(Type $type): Type
{
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
if ($isIterableAtLeastOnce->no()) {
return $type;
}

return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type {
if ($type instanceof UnionType) {
return $traverse($type);
}

$newArrayType = new ArrayType($type->getIterableKeyType(), $type->getIterableValueType());
if ($isIterableAtLeastOnce->yes()) {
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
}

return $newArrayType;
});
}

private function getFunctionThrowPoint(
FunctionReflection $functionReflection,
?ParametersAcceptor $parametersAcceptor,
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ public function dataFileAsserts(): iterable
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/never.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10627.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/native-intersection.php');

Expand Down
78 changes: 78 additions & 0 deletions tests/PHPStan/Analyser/data/bug-10627.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Bug10627;

use function PHPStan\Testing\assertType;

class HelloWorld
{
public function sayHello(): void
{
$list = ['A', 'C', 'B'];
natcasesort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello2(): void
{
$list = ['A', 'C', 'B'];
natsort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello3(): void
{
$list = ['A', 'C', 'B'];
arsort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello4(): void
{
$list = ['A', 'C', 'B'];
asort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello5(): void
{
$list = ['A', 'C', 'B'];
ksort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello6(): void
{
$list = ['A', 'C', 'B'];
uasort($list, function () {

});
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello7(): void
{
$list = ['A', 'C', 'B'];
uksort($list, function () {

});
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

public function sayHello8(): void
{
$list = ['A', 'C', 'B'];
krsort($list);
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
}

/**
* @param list<string> $list
* @return void
*/
public function sayHello9(array $list): void
{
krsort($list);
assertType("array<int<0, max>, string>", $list);
}
}
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/data/param-out.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ function foo15() {
$manifest,
"fooCompare"
);
assertType('array{1, 2, 3}', $manifest);
assertType('non-empty-array<0|1|2, 1|2|3>', $manifest);
}

function fooSpaceship (string $a, string $b): int {
Expand All @@ -234,7 +234,7 @@ function foo16() {
$array,
"fooSpaceship"
);
assertType('array{1, 2}', $array);
assertType('non-empty-array<0|1, 1|2>', $array);
}

function fooShuffle() {
Expand Down

0 comments on commit 034f731

Please sign in to comment.