Skip to content

Commit 9c06f13

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents 3863033 + d0e7aca commit 9c06f13

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

src/Type/Php/ArrayMapFunctionReturnTypeExtension.php

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use PHPStan\Type\TypeCombinator;
2020
use PHPStan\Type\TypeUtils;
2121
use function array_map;
22+
use function array_reduce;
2223
use function array_slice;
2324
use function count;
2425

@@ -32,7 +33,8 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
3233

3334
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
3435
{
35-
if (count($functionCall->getArgs()) < 2) {
36+
$numArgs = count($functionCall->getArgs());
37+
if ($numArgs < 2) {
3638
return null;
3739
}
3840

@@ -54,10 +56,58 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
5456
)->getReturnType();
5557
} elseif ($callableIsNull) {
5658
$arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
59+
$argTypes = [];
60+
$areAllSameSize = true;
61+
$expectedSize = null;
5762
foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) {
63+
$argTypes[$index] = $argType = $scope->getType($arg->value);
64+
if (!$areAllSameSize || $numArgs === 2) {
65+
continue;
66+
}
67+
68+
$arraySizes = $argType->getArraySize()->getConstantScalarValues();
69+
if ($arraySizes === []) {
70+
$areAllSameSize = false;
71+
continue;
72+
}
73+
74+
foreach ($arraySizes as $size) {
75+
$expectedSize ??= $size;
76+
if ($expectedSize === $size) {
77+
continue;
78+
}
79+
80+
$areAllSameSize = false;
81+
continue 2;
82+
}
83+
}
84+
85+
if (!$areAllSameSize) {
86+
$firstArr = $functionCall->getArgs()[1]->value;
87+
$identities = [];
88+
foreach (array_slice($functionCall->getArgs(), 2) as $arg) {
89+
$identities[] = new Node\Expr\BinaryOp\Identical($firstArr, $arg->value);
90+
}
91+
92+
$and = array_reduce(
93+
$identities,
94+
static fn (Node\Expr $a, Node\Expr $b) => new Node\Expr\BinaryOp\BooleanAnd($a, $b),
95+
new Node\Expr\ConstFetch(new Node\Name('true')),
96+
);
97+
$areAllSameSize = $scope->getType($and)->isTrue()->yes();
98+
}
99+
100+
$addNull = !$areAllSameSize;
101+
102+
foreach ($argTypes as $index => $argType) {
103+
$offsetValueType = $argType->getIterableValueType();
104+
if ($addNull) {
105+
$offsetValueType = TypeCombinator::addNull($offsetValueType);
106+
}
107+
58108
$arrayBuilder->setOffsetValueType(
59109
new ConstantIntegerType($index),
60-
$scope->getType($arg->value)->getIterableValueType(),
110+
$offsetValueType,
61111
);
62112
}
63113
$valueType = $arrayBuilder->getArray();

tests/PHPStan/Analyser/nsrt/array_map_multiple.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ public function arrayMapNull(array $array, array $other): void
3030

3131
assertType('non-empty-array<string, int>', array_map(null, $array));
3232
assertType('non-empty-array<int, array{int, int}>', array_map(null, $array, $array));
33-
assertType('non-empty-array<int, array{int, bool}>', array_map(null, $array, $other));
33+
assertType('non-empty-array<int, array{int, int, int}>', array_map(null, $array, $array, $array));
34+
assertType('non-empty-array<int, array{int|null, bool|null}>', array_map(null, $array, $other));
35+
36+
assertType('array{1}|array{true}', array_map(null, rand() ? [1] : [true]));
37+
assertType('array{1}|array{true, false}', array_map(null, rand() ? [1] : [true, false]));
3438
}
3539

3640
}

0 commit comments

Comments
 (0)