Skip to content

Commit 61c9177

Browse files
committed
Preserve generics after usort() call
1 parent 813d15e commit 61c9177

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
use PHPStan\Type\Generic\TemplateTypeVariance;
147147
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
148148
use PHPStan\Type\IntegerType;
149+
use PHPStan\Type\IntersectionType;
149150
use PHPStan\Type\MixedType;
150151
use PHPStan\Type\NeverType;
151152
use PHPStan\Type\NullType;
@@ -3119,20 +3120,27 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
31193120

31203121
private function getArraySortFunctionType(Type $type): Type
31213122
{
3122-
if (!$type->isArray()->yes()) {
3123-
return $type;
3124-
}
3125-
31263123
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
31273124
if ($isIterableAtLeastOnce->no()) {
31283125
return $type;
31293126
}
31303127

3131-
$newArrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $type->getIterableValueType()));
3132-
if ($isIterableAtLeastOnce->yes()) {
3133-
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
3134-
}
3135-
return $newArrayType;
3128+
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type {
3129+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
3130+
return $traverse($type);
3131+
}
3132+
3133+
if (!$type instanceof ArrayType) {
3134+
return $type;
3135+
}
3136+
3137+
$newArrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $type->getIterableValueType()));
3138+
if ($isIterableAtLeastOnce->yes()) {
3139+
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
3140+
}
3141+
3142+
return $newArrayType;
3143+
});
31363144
}
31373145

31383146
private function getFunctionThrowPoint(

tests/PHPStan/Analyser/data/sort.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use function PHPStan\Testing\assertNativeType;
66
use function PHPStan\Testing\assertType;
77
use function sort;
8+
use function usort;
89

910
class Foo
1011
{
@@ -128,3 +129,25 @@ public function notArray(): void
128129
assertType("'foo'", $arr);
129130
}
130131
}
132+
133+
class Bar
134+
{
135+
136+
/**
137+
* @template T
138+
* @param T&list<array{a: scalar, b: true}> $array
139+
* @return list<T>
140+
*/
141+
public function doFoo(array $array)
142+
{
143+
assertType('list<array{a: bool|float|int|string, b: true}>&T (method Sort\Bar::doFoo(), argument)', $array);
144+
usort($array, function (array $a, array $b) {
145+
return $a['a'] <=> $b['a'];
146+
});
147+
148+
assertType('list<array{a: bool|float|int|string, b: true}>&T (method Sort\Bar::doFoo(), argument)', $array);
149+
150+
return $array;
151+
}
152+
153+
}

0 commit comments

Comments
 (0)