Skip to content

Commit 7f711a0

Browse files
phpstan-botclaude
andcommitted
Account for callback return type in array_reduce carry parameter
The carry parameter type should be the union of the initial value type and the callback's return type, since after the first iteration $carry receives the callback's return value, not the initial value. Previously, only the initial type was used, which was incorrect for subsequent iterations (e.g., array{starts: array{}, ends: array{}} was wrong when the callback returns a modified array, and null was wrong when the callback returns int). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2bc277b commit 7f711a0

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

src/Type/Php/ArrayReduceCallbackClosureTypeExtension.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PHPStan\Type\Php;
44

5+
use PhpParser\Node\Expr\ArrowFunction;
6+
use PhpParser\Node\Expr\Closure;
57
use PhpParser\Node\Expr\FuncCall;
68
use PHPStan\Analyser\Scope;
79
use PHPStan\DependencyInjection\AutowiredService;
@@ -13,7 +15,9 @@
1315
use PHPStan\Type\GeneralizePrecision;
1416
use PHPStan\Type\MixedType;
1517
use PHPStan\Type\NullType;
18+
use PHPStan\Type\ParserNodeTypeToPHPStanType;
1619
use PHPStan\Type\Type;
20+
use PHPStan\Type\TypeCombinator;
1721

1822
#[AutowiredService]
1923
final class ArrayReduceCallbackClosureTypeExtension implements FunctionParameterClosureTypeExtension
@@ -43,6 +47,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4347

4448
$carryType = $initialType->generalize(GeneralizePrecision::templateArgument());
4549

50+
$callbackExpr = $args[1]->value ?? null;
51+
if ($callbackExpr instanceof Closure || $callbackExpr instanceof ArrowFunction) {
52+
$callbackReturnType = ParserNodeTypeToPHPStanType::resolve($callbackExpr->returnType, null);
53+
if (!$callbackReturnType instanceof MixedType) {
54+
$carryType = TypeCombinator::union($carryType, $callbackReturnType);
55+
}
56+
}
57+
4658
return new ClosureType(
4759
[
4860
new NativeParameterReflection('carry', false, $carryType, $parameter->passedByReference(), false, null),

tests/PHPStan/Analyser/nsrt/bug-7280.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
$result1 = array_reduce(
1111
['test1', 'test2'],
1212
static function (array $carry, string $value): array {
13-
assertType("array{starts: array{}, ends: array{}}", $carry);
13+
assertType("array", $carry);
1414
$carry['starts'][] = $value;
1515
$carry['ends'][] = $value;
1616

@@ -31,7 +31,7 @@ static function (array $carry, string $value): array {
3131
$result3 = array_reduce(
3232
[1, 2, 3],
3333
static function (?int $carry, int $value): int {
34-
assertType('null', $carry);
34+
assertType('int|null', $carry);
3535
return ($carry ?? 0) + $value;
3636
},
3737
);

0 commit comments

Comments
 (0)