From 0977a7b0259cbe4055c6fb0200708b2ec137d770 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 23 Feb 2024 16:44:03 +0100 Subject: [PATCH] Constant arrays should be preserved after sorting, just not as certain lists --- src/Analyser/NodeScopeResolver.php | 16 +++++++++ .../Analyser/NodeScopeResolverTest.php | 5 ++- tests/PHPStan/Analyser/data/bug-10627.php | 33 ++++++++++++++----- tests/PHPStan/Analyser/data/param-out.php | 5 +-- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1e3baf610b..baeba549b3 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3207,6 +3207,22 @@ private function getArraySortDoNotPreserveListFunctionType(Type $type): Type return $traverse($type); } + $constantArrays = $type->getConstantArrays(); + if (count($constantArrays) > 0) { + $types = []; + foreach ($constantArrays as $constantArray) { + $types[] = new ConstantArrayType( + $constantArray->getKeyTypes(), + $constantArray->getValueTypes(), + $constantArray->getNextAutoIndexes(), + $constantArray->getOptionalKeys(), + TrinaryLogic::createMaybe(), + ); + } + + return TypeCombinator::union(...$types); + } + $newArrayType = new ArrayType($type->getIterableKeyType(), $type->getIterableValueType()); if ($isIterableAtLeastOnce->yes()) { $newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType()); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 4cc7acf4d2..3df5fed94c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -654,7 +654,10 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/never.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10627.php'); + + if (PHP_VERSION_ID >= 80100) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10627.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/data/native-intersection.php'); diff --git a/tests/PHPStan/Analyser/data/bug-10627.php b/tests/PHPStan/Analyser/data/bug-10627.php index f5c9f59cab..17579ec52c 100644 --- a/tests/PHPStan/Analyser/data/bug-10627.php +++ b/tests/PHPStan/Analyser/data/bug-10627.php @@ -2,6 +2,7 @@ namespace Bug10627; +use function array_is_list; use function PHPStan\Testing\assertType; class HelloWorld @@ -10,35 +11,40 @@ public function sayHello(): void { $list = ['A', 'C', 'B']; natcasesort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello2(): void { $list = ['A', 'C', 'B']; natsort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello3(): void { $list = ['A', 'C', 'B']; arsort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello4(): void { $list = ['A', 'C', 'B']; asort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello5(): void { $list = ['A', 'C', 'B']; ksort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello6(): void @@ -47,7 +53,8 @@ public function sayHello6(): void uasort($list, function () { }); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello7(): void @@ -56,14 +63,16 @@ public function sayHello7(): void uksort($list, function () { }); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } public function sayHello8(): void { $list = ['A', 'C', 'B']; krsort($list); - assertType("non-empty-array<0|1|2, 'A'|'B'|'C'>", $list); + assertType("array{'A', 'C', 'B'}", $list); + assertType('bool', array_is_list($list)); } /** @@ -75,4 +84,12 @@ public function sayHello9(array $list): void krsort($list); assertType("array, string>", $list); } + + public function sayHello10(): void + { + $list = ['a' => 'A', 'c' => 'C', 'b' => 'B']; + krsort($list); + assertType("array{a: 'A', c: 'C', b: 'B'}", $list); + assertType('false', array_is_list($list)); + } } diff --git a/tests/PHPStan/Analyser/data/param-out.php b/tests/PHPStan/Analyser/data/param-out.php index 4723f1d85f..62216290ac 100644 --- a/tests/PHPStan/Analyser/data/param-out.php +++ b/tests/PHPStan/Analyser/data/param-out.php @@ -221,7 +221,7 @@ function foo15() { $manifest, "fooCompare" ); - assertType('non-empty-array<0|1|2, 1|2|3>', $manifest); + assertType('array{1, 2, 3}', $manifest); } function fooSpaceship (string $a, string $b): int { @@ -234,7 +234,7 @@ function foo16() { $array, "fooSpaceship" ); - assertType('non-empty-array<0|1, 1|2>', $array); + assertType('array{1, 2}', $array); } function fooShuffle() { @@ -251,6 +251,7 @@ function fooSort() { $array = ["foo" => 123, "bar" => 456]; sort($array); assertType('non-empty-list<123|456>', $array); + assertType('true', array_is_list($array)); $emptyArray = []; sort($emptyArray);