diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index de09752cac..b65b75b274 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -1196,7 +1196,13 @@ public function shiftArray(): Type public function shuffleArray(): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->shuffleArray()); + $isList = $this->isList()->yes(); + return $this->intersectTypes(static function (Type $type) use ($isList): Type { + if ($isList && $type instanceof TemplateType) { + return $type; + } + return $type->shuffleArray(); + }); } public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type diff --git a/tests/PHPStan/Analyser/nsrt/bug-14631.php b/tests/PHPStan/Analyser/nsrt/bug-14631.php new file mode 100644 index 0000000000..6034061bde --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14631.php @@ -0,0 +1,178 @@ + $items + * @return T&list + */ + public function sortList(array $items): array + { + assertType('list&T (method Bug14631\Foo::sortList(), argument)', $items); + usort($items, function (int $a, int $b) { + return $a <=> $b; + }); + + assertType('list&T (method Bug14631\Foo::sortList(), argument)', $items); + + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function sortArray(array $items): array + { + assertType('array&T (method Bug14631\Foo::sortArray(), argument)', $items); + usort($items, function (int $a, int $b) { + return $a <=> $b; + }); + + // T should be dropped because keys changed from string to int + assertType('list', $items); + + return $items; + } + + /** + * @template T + * @param T&list $items + * @return T&list + */ + public function sortListSort(array $items): array + { + sort($items); + assertType('list&T (method Bug14631\Foo::sortListSort(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&list $items + * @return T&list + */ + public function sortListRsort(array $items): array + { + rsort($items); + assertType('list&T (method Bug14631\Foo::sortListRsort(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&list $items + * @return T&list + */ + public function sortListShuffle(array $items): array + { + shuffle($items); + assertType('list&T (method Bug14631\Foo::sortListShuffle(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function sortArraySort(array $items): array + { + sort($items); + // T dropped: keys changed from string to int + assertType('list', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function sortArrayRsort(array $items): array + { + rsort($items); + // T dropped: keys changed from string to int + assertType('list', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function shuffleArray(array $items): array + { + shuffle($items); + // T dropped: keys changed from string to int + assertType('list', $items); + return $items; + } + + /** + * @template T + * @param T&list $items + * @return T&list + */ + public function uasortList(array $items): array + { + uasort($items, function (int $a, int $b) { + return $a <=> $b; + }); + + // T preserved, list-ness dropped (key-preserving sort may reorder) + assertType('array, int>&T (method Bug14631\Foo::uasortList(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function uasortArray(array $items): array + { + uasort($items, function (int $a, int $b) { + return $a <=> $b; + }); + + // T preserved: key-preserving sort doesn't change keys + assertType('array&T (method Bug14631\Foo::uasortArray(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function asortArray(array $items): array + { + asort($items); + // T preserved: key-preserving sort + assertType('array&T (method Bug14631\Foo::asortArray(), argument)', $items); + return $items; + } + + /** + * @template T + * @param T&array $items + * @return T&array + */ + public function ksortArray(array $items): array + { + ksort($items); + // T preserved: key-preserving sort + assertType('array&T (method Bug14631\Foo::ksortArray(), argument)', $items); + return $items; + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/sort.php b/tests/PHPStan/Analyser/nsrt/sort.php index 02879e3cd7..47f9b3f030 100644 --- a/tests/PHPStan/Analyser/nsrt/sort.php +++ b/tests/PHPStan/Analyser/nsrt/sort.php @@ -145,7 +145,7 @@ public function doFoo(array $array) return $a['a'] <=> $b['a']; }); - assertType('list', $array); + assertType('list&T (method Sort\Bar::doFoo(), argument)', $array); return $array; }