Skip to content

Extract Type::sortArray() for sort / rsort / usort#5613

Merged
ondrejmirtes merged 2 commits into2.1.xfrom
refactor-sort-list
May 7, 2026
Merged

Extract Type::sortArray() for sort / rsort / usort#5613
ondrejmirtes merged 2 commits into2.1.xfrom
refactor-sort-list

Conversation

@ondrejmirtes
Copy link
Copy Markdown
Member

Summary

Same pattern as #5611 / #5612: extract a hand-rolled TypeTraverser::map callback into a polymorphic Type method. The call site (FuncCallHandler::getArraySortPreserveListFunctionType) shrinks to \$type->sortArray().

The semantics — values reordered, array re-indexed as a list — are similar to the existing shuffleArray(), but two differences prevent direct reuse:

  • Empty arrays stay empty: array{} does not degrade to list<never> like shuffleArray() does. The check moves into ArrayTypeTrait::sortArray() via isIterableAtLeastOnce()->no(), so per-leaf decisions also preserve emptiness inside unions (incidental precision improvement over the closure-captured outer flag in the original).
  • Non-array leaves pass through: sort(\$x) where \$x: mixed (or a template, or a non-array variable) should not erase the variable's type — the arg-type rule is responsible for the diagnostic. shuffleArray() returns ErrorType for non-arrays, which would propagate the wrong way through processVirtualAssign.

Implementation

  • ArrayTypeTrait::sortArray(): shared body for ArrayType and ConstantArrayType (their original behavior was identical, so consolidating into the trait drops the per-class duplication).
  • NonArrayTypeTrait, MaybeArrayTypeTrait, MixedType, StrictMixedType, StaticType, NeverType, and the five array accessory types: return \$this.
  • UnionType / IntersectionType: distribute.
  • LateResolvableTypeTrait: delegate to resolve().

Net diff: +93/-27 across 16 files.

Test plan

  • `make tests` passes (12066 tests, 79676 assertions).
  • `make phpstan` passes.
  • `make cs` passes.
  • CI green.

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.
Follow-up cleanup across #5611, #5612, #5613:

- `FuncCallHandler::getArrayWalkResultType()` and
  `getArraySortDoNotPreserveListFunctionType()` were single-statement
  wrappers around `Type::mapValueType()` / `Type::makeListMaybe()`.
  Both private, both fully replaced by their polymorphic call;
  inline at the two/one call sites and delete the helpers.
- `InstanceofHandler` and `TypeSpecifier` (the `instanceof <expr>`
  branch) used a throwaway `$classType = $scope->getType(...)` only
  to immediately overwrite it with `$result->type`. Drop the
  intermediate; chain the call.

Pure simplification: full test suite + phpstan + cs pass.
@ondrejmirtes ondrejmirtes merged commit 6a03691 into 2.1.x May 7, 2026
339 checks passed
@ondrejmirtes ondrejmirtes deleted the refactor-sort-list branch May 7, 2026 19:26
@VincentLanglet
Copy link
Copy Markdown
Contributor

VincentLanglet commented May 7, 2026

I'm not fully sure but

  • It's unclear to me how/why this is/should be different from shuffleArray. To me only one Type method is necessary
  • While trying to use sortArray instead of shuffleArray I discovered that this PR might introduce the following regression:

Take a look at https://phpstan.org/r/cacb0738-d70f-4c8b-83b5-931c989fd4b0 @ondrejmirtes

Edit: illustrated with

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants