Skip to content

Commit 034f731

Browse files
committed
Some sort functions do not preserve a list
1 parent fbc6bca commit 034f731

File tree

4 files changed

+118
-5
lines changed

4 files changed

+118
-5
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,8 +2192,21 @@ static function (): void {
21922192
$arrayArg = $expr->getArgs()[0]->value;
21932193
$scope = $scope->assignExpression(
21942194
$arrayArg,
2195-
$this->getArraySortFunctionType($scope->getType($arrayArg)),
2196-
$this->getArraySortFunctionType($scope->getNativeType($arrayArg)),
2195+
$this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)),
2196+
$this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg)),
2197+
);
2198+
}
2199+
2200+
if (
2201+
$functionReflection !== null
2202+
&& in_array($functionReflection->getName(), ['natcasesort', 'natsort', 'arsort', 'asort', 'ksort', 'krsort', 'uasort', 'uksort'], true)
2203+
&& count($expr->getArgs()) >= 1
2204+
) {
2205+
$arrayArg = $expr->getArgs()[0]->value;
2206+
$scope = $scope->assignExpression(
2207+
$arrayArg,
2208+
$this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)),
2209+
$this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg)),
21972210
);
21982211
}
21992212

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

3160-
private function getArraySortFunctionType(Type $type): Type
3173+
private function getArraySortPreserveListFunctionType(Type $type): Type
31613174
{
31623175
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
31633176
if ($isIterableAtLeastOnce->no()) {
@@ -3182,6 +3195,27 @@ private function getArraySortFunctionType(Type $type): Type
31823195
});
31833196
}
31843197

3198+
private function getArraySortDoNotPreserveListFunctionType(Type $type): Type
3199+
{
3200+
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
3201+
if ($isIterableAtLeastOnce->no()) {
3202+
return $type;
3203+
}
3204+
3205+
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type {
3206+
if ($type instanceof UnionType) {
3207+
return $traverse($type);
3208+
}
3209+
3210+
$newArrayType = new ArrayType($type->getIterableKeyType(), $type->getIterableValueType());
3211+
if ($isIterableAtLeastOnce->yes()) {
3212+
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
3213+
}
3214+
3215+
return $newArrayType;
3216+
});
3217+
}
3218+
31853219
private function getFunctionThrowPoint(
31863220
FunctionReflection $functionReflection,
31873221
?ParametersAcceptor $parametersAcceptor,

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ public function dataFileAsserts(): iterable
654654
}
655655

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

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Bug10627;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function sayHello(): void
10+
{
11+
$list = ['A', 'C', 'B'];
12+
natcasesort($list);
13+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
14+
}
15+
16+
public function sayHello2(): void
17+
{
18+
$list = ['A', 'C', 'B'];
19+
natsort($list);
20+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
21+
}
22+
23+
public function sayHello3(): void
24+
{
25+
$list = ['A', 'C', 'B'];
26+
arsort($list);
27+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
28+
}
29+
30+
public function sayHello4(): void
31+
{
32+
$list = ['A', 'C', 'B'];
33+
asort($list);
34+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
35+
}
36+
37+
public function sayHello5(): void
38+
{
39+
$list = ['A', 'C', 'B'];
40+
ksort($list);
41+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
42+
}
43+
44+
public function sayHello6(): void
45+
{
46+
$list = ['A', 'C', 'B'];
47+
uasort($list, function () {
48+
49+
});
50+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
51+
}
52+
53+
public function sayHello7(): void
54+
{
55+
$list = ['A', 'C', 'B'];
56+
uksort($list, function () {
57+
58+
});
59+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
60+
}
61+
62+
public function sayHello8(): void
63+
{
64+
$list = ['A', 'C', 'B'];
65+
krsort($list);
66+
assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list);
67+
}
68+
69+
/**
70+
* @param list<string> $list
71+
* @return void
72+
*/
73+
public function sayHello9(array $list): void
74+
{
75+
krsort($list);
76+
assertType("array<int<0, max>, string>", $list);
77+
}
78+
}

tests/PHPStan/Analyser/data/param-out.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ function foo15() {
221221
$manifest,
222222
"fooCompare"
223223
);
224-
assertType('array{1, 2, 3}', $manifest);
224+
assertType('non-empty-array<0|1|2, 1|2|3>', $manifest);
225225
}
226226

227227
function fooSpaceship (string $a, string $b): int {
@@ -234,7 +234,7 @@ function foo16() {
234234
$array,
235235
"fooSpaceship"
236236
);
237-
assertType('array{1, 2}', $array);
237+
assertType('non-empty-array<0|1, 1|2>', $array);
238238
}
239239

240240
function fooShuffle() {

0 commit comments

Comments
 (0)