Skip to content

Commit 5d45463

Browse files
committed
Extract Type::sortArray() for sort / rsort / usort
Replaces `FuncCallHandler::getArraySortPreserveListFunctionType()`'s hand-rolled `TypeTraverser::map` callback with a polymorphic `Type` method. The call site shrinks to `$type->sortArray()`. The semantics — values reordered, array re-indexed as a list — are similar to `shuffleArray()` but differ in two ways that ruled out direct reuse: - Empty arrays stay empty (`array{}` does not degrade to `list<never>`). The check moves into `ArrayTypeTrait::sortArray()` via `isIterableAtLeastOnce()->no()`, so per-leaf decisions also preserve emptiness inside unions (precision improvement vs. the closure-captured outer flag in the original). - Non-array leaves pass through unchanged (rather than `ErrorType` like `shuffleArray()`), because `sort($x)` where `$x` is a `mixed` / template / non-array variable should not erase the variable's type — the arg-type rule is responsible for the diagnostic. Implementation: - `ArrayTypeTrait::sortArray()`: shared body for `ArrayType` and `ConstantArrayType`. Returns the input unchanged if `isIterableAtLeastOnce()->no()`; otherwise builds `IntersectionType[ArrayType<int<0,max>, valueType>, AccessoryArrayListType]`, intersected with `NonEmptyArrayType` when the leaf's own `isIterableAtLeastOnce()` is `Yes`. - `NonArrayTypeTrait`, `MaybeArrayTypeTrait`, `MixedType`, `StrictMixedType`, `StaticType`, `NeverType`, and the array accessory types (`AccessoryArrayListType`, `NonEmptyArrayType`, `OversizedArrayType`, `HasOffsetType`, `HasOffsetValueType`): return `$this`. - `UnionType` / `IntersectionType`: distribute via `unionTypes` / `intersectTypes`. - `LateResolvableTypeTrait`: delegates to `resolve()`. Pure refactor: full test suite + phpstan + cs pass.
1 parent de99d53 commit 5d45463

16 files changed

Lines changed: 93 additions & 27 deletions

src/Analyser/ExprHandler/FuncCallHandler.php

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
use PHPStan\Type\StringType;
6161
use PHPStan\Type\Type;
6262
use PHPStan\Type\TypeCombinator;
63-
use PHPStan\Type\TypeTraverser;
6463
use PHPStan\Type\UnionType;
6564
use Throwable;
6665
use function array_filter;
@@ -462,7 +461,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
462461
$storage,
463462
$stmt,
464463
$arrayArg,
465-
new NativeTypeExpr($this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg))),
464+
new NativeTypeExpr($scope->getType($arrayArg)->sortArray(), $scope->getNativeType($arrayArg)->sortArray()),
466465
$nodeCallback,
467466
)->getScope();
468467
}
@@ -722,31 +721,6 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
722721
return $arrayType;
723722
}
724723

725-
private function getArraySortPreserveListFunctionType(Type $type): Type
726-
{
727-
$isIterableAtLeastOnce = $type->isIterableAtLeastOnce();
728-
if ($isIterableAtLeastOnce->no()) {
729-
return $type;
730-
}
731-
732-
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($isIterableAtLeastOnce): Type {
733-
if ($type instanceof UnionType || $type instanceof IntersectionType) {
734-
return $traverse($type);
735-
}
736-
737-
if (!$type instanceof ArrayType && !$type instanceof ConstantArrayType) {
738-
return $type;
739-
}
740-
741-
$newArrayType = new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $type->getIterableValueType()), new AccessoryArrayListType()]);
742-
if ($isIterableAtLeastOnce->yes()) {
743-
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
744-
}
745-
746-
return $newArrayType;
747-
});
748-
}
749-
750724
private function getArraySortDoNotPreserveListFunctionType(Type $type): Type
751725
{
752726
return $type->makeListMaybe();

src/Type/Accessory/AccessoryArrayListType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ public function shuffleArray(): Type
242242
return $this;
243243
}
244244

245+
public function sortArray(): Type
246+
{
247+
return $this;
248+
}
249+
245250
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
246251
{
247252
if ($preserveKeys->no()) {

src/Type/Accessory/HasOffsetType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ public function shuffleArray(): Type
202202
return new NonEmptyArrayType();
203203
}
204204

205+
public function sortArray(): Type
206+
{
207+
return $this;
208+
}
209+
205210
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
206211
{
207212
if (

src/Type/Accessory/HasOffsetValueType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ public function shuffleArray(): Type
291291
return new NonEmptyArrayType();
292292
}
293293

294+
public function sortArray(): Type
295+
{
296+
return $this;
297+
}
298+
294299
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
295300
{
296301
if (

src/Type/Accessory/NonEmptyArrayType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ public function shuffleArray(): Type
226226
return $this;
227227
}
228228

229+
public function sortArray(): Type
230+
{
231+
return $this;
232+
}
233+
229234
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
230235
{
231236
if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes() && $lengthType->isNull()->yes()) {

src/Type/Accessory/OversizedArrayType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ public function shuffleArray(): Type
214214
return $this;
215215
}
216216

217+
public function sortArray(): Type
218+
{
219+
return $this;
220+
}
221+
217222
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
218223
{
219224
return $this;

src/Type/IntersectionType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,11 @@ public function shuffleArray(): Type
11641164
return $this->intersectTypes(static fn (Type $type): Type => $type->shuffleArray());
11651165
}
11661166

1167+
public function sortArray(): Type
1168+
{
1169+
return $this->intersectTypes(static fn (Type $type): Type => $type->sortArray());
1170+
}
1171+
11671172
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
11681173
{
11691174
$result = $this->intersectTypes(static fn (Type $type): Type => $type->sliceArray($offsetType, $lengthType, $preserveKeys));

src/Type/MixedType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ public function shuffleArray(): Type
288288
return new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType()]);
289289
}
290290

291+
public function sortArray(): Type
292+
{
293+
return $this;
294+
}
295+
291296
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
292297
{
293298
if ($this->isArray()->no()) {

src/Type/NeverType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ public function shuffleArray(): Type
375375
return new NeverType();
376376
}
377377

378+
public function sortArray(): Type
379+
{
380+
return $this;
381+
}
382+
378383
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
379384
{
380385
return new NeverType();

src/Type/StaticType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,11 @@ public function shuffleArray(): Type
550550
return $this->getStaticObjectType()->shuffleArray();
551551
}
552552

553+
public function sortArray(): Type
554+
{
555+
return $this;
556+
}
557+
553558
public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type
554559
{
555560
return $this->getStaticObjectType()->sliceArray($offsetType, $lengthType, $preserveKeys);

0 commit comments

Comments
 (0)