Skip to content

Fix phpstan/phpstan#7280: array_reduce could infer the precise type of the $initial argument within the callable and in the return type#5168

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-28evfgd
Open

Fix phpstan/phpstan#7280: array_reduce could infer the precise type of the $initial argument within the callable and in the return type#5168
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-28evfgd

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

array_reduce now infers the precise type of the $initial argument as the $carry parameter type within the callback. Previously, $carry was typed as mixed (from the function signature), which when intersected with the closure's native type hint gave only broad types like array or non-empty-array. Now, constant array shapes like array{starts: array{}, ends: array{}} are preserved inside the callback.

Changes

  • Added src/Type/Php/ArrayReduceCallbackClosureTypeExtension.php — a new FunctionParameterClosureTypeExtension that overrides the callback parameter types for array_reduce:
    • The carry parameter gets the generalized initial argument type (preserves array shapes, generalizes scalar literals like 0int)
    • The value parameter gets the array's iterable value type
  • Added tests/PHPStan/Analyser/nsrt/bug-7280.php — regression test covering:
    • Constant array shape initial values (the primary use case from the issue)
    • Scalar initial values (int, string)
    • No initial value (defaults to null)
    • Arrow functions

Root cause

The function signature for array_reduce in functionMap.php declares the callback as callable(mixed,mixed):mixed. This meant the carry parameter had no type information beyond mixed, so PHPStan could only use the closure's native type hints. By implementing a FunctionParameterClosureTypeExtension, the initial argument's type is now propagated to the carry parameter, which is then intersected with the native type hint to produce precise types inside the callback body.

Test

The regression test verifies that:

  • $carry inside a callback with initial: ['starts' => [], 'ends' => []] is typed as array{starts: array{}, ends: array{}} (was non-empty-array)
  • Scalar initial values like 0 and '' are generalized to int and string (no over-narrowing)
  • Missing initial value correctly types carry as null

Fixes phpstan/phpstan#7280

- Added ArrayReduceCallbackClosureTypeExtension implementing FunctionParameterClosureTypeExtension
- The extension provides the initial argument type (generalized to remove literals) as the carry parameter type
- The array's value type is provided as the value parameter type
- New regression test in tests/PHPStan/Analyser/nsrt/bug-7280.php

Closes phpstan/phpstan#7280
$result1 = array_reduce(
['test1', 'test2'],
static function (array $carry, string $value): array {
assertType("array{starts: array{}, ends: array{}}", $carry);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this type is wrong. it only is correct for the first invocation when initial is used.

in this example, I think we need to aim for `assertType("array{starts: array, ends: array}", $carry);

$result3 = array_reduce(
[1, 2, 3],
static function (?int $carry, int $value): int {
assertType('null', $carry);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assertType('null', $carry);
assertType('int|null', $carry);

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.

3 participants