Skip to content

Commit 84bafe7

Browse files
jlherrenondrejmirtes
authored andcommitted
Make array_map() accept null as first argument
1 parent 0d025e7 commit 84bafe7

File tree

5 files changed

+56
-12
lines changed

5 files changed

+56
-12
lines changed

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use PHPStan\Type\Constant\ConstantIntegerType;
1313
use PHPStan\Type\Generic\TemplateTypeMap;
1414
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\NullType;
1516
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\UnionType;
1618

1719
/** @api */
1820
class ParametersAcceptorSelector
@@ -80,7 +82,10 @@ public static function selectFromArgs(
8082
$parameters[0] = new NativeParameterReflection(
8183
$parameters[0]->getName(),
8284
$parameters[0]->isOptional(),
83-
new CallableType($callbackParameters, new MixedType(), false),
85+
new UnionType([
86+
new CallableType($callbackParameters, new MixedType(), false),
87+
new NullType(),
88+
]),
8489
$parameters[0]->passedByReference(),
8590
$parameters[0]->isVariadic(),
8691
$parameters[0]->getDefaultValue()

src/Type/Php/ArrayMapFunctionReturnTypeExtension.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
use PHPStan\Type\Accessory\NonEmptyArrayType;
1010
use PHPStan\Type\ArrayType;
1111
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
12+
use PHPStan\Type\Constant\ConstantIntegerType;
1213
use PHPStan\Type\IntegerType;
1314
use PHPStan\Type\MixedType;
1415
use PHPStan\Type\NeverType;
16+
use PHPStan\Type\NullType;
1517
use PHPStan\Type\Type;
1618
use PHPStan\Type\TypeCombinator;
1719
use PHPStan\Type\TypeUtils;
@@ -30,23 +32,35 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3032
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
3133
}
3234

33-
$valueType = new MixedType();
35+
$singleArrayArgument = !isset($functionCall->getArgs()[2]);
3436
$callableType = $scope->getType($functionCall->getArgs()[0]->value);
37+
$callableIsNull = (new NullType())->isSuperTypeOf($callableType)->yes();
38+
3539
if ($callableType->isCallable()->yes()) {
3640
$valueType = new NeverType();
3741
foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) {
3842
$valueType = TypeCombinator::union($valueType, $parametersAcceptor->getReturnType());
3943
}
44+
} elseif ($callableIsNull) {
45+
$arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
46+
foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) {
47+
$arrayBuilder->setOffsetValueType(
48+
new ConstantIntegerType($index),
49+
$scope->getType($arg->value)->getIterableValueType()
50+
);
51+
}
52+
$valueType = $arrayBuilder->getArray();
53+
} else {
54+
$valueType = new MixedType();
4055
}
4156

42-
$mappedArrayType = new ArrayType(
43-
new MixedType(),
44-
$valueType
45-
);
4657
$arrayType = $scope->getType($functionCall->getArgs()[1]->value);
47-
$constantArrays = TypeUtils::getConstantArrays($arrayType);
4858

49-
if (!isset($functionCall->getArgs()[2])) {
59+
if ($singleArrayArgument) {
60+
if ($callableIsNull) {
61+
return $arrayType;
62+
}
63+
$constantArrays = TypeUtils::getConstantArrays($arrayType);
5064
if (count($constantArrays) > 0) {
5165
$arrayTypes = [];
5266
foreach ($constantArrays as $constantArray) {
@@ -66,6 +80,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
6680
$arrayType->getIterableKeyType(),
6781
$valueType
6882
), ...TypeUtils::getAccessoryTypes($arrayType));
83+
} else {
84+
$mappedArrayType = new ArrayType(
85+
new MixedType(),
86+
$valueType
87+
);
6988
}
7089
} else {
7190
$mappedArrayType = TypeCombinator::intersect(new ArrayType(

tests/PHPStan/Analyser/data/array_map_multiple.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,19 @@ public function doFoo(int $i, string $s): void
1818
assertType('array<int, int|string>&nonEmpty', $result);
1919
}
2020

21+
/**
22+
* @param non-empty-array<string, int> $array
23+
* @param non-empty-array<int, bool> $other
24+
*/
25+
public function arrayMapNull(array $array, array $other): void
26+
{
27+
assertType('array()', array_map(null, []));
28+
assertType('array(\'foo\' => true)', array_map(null, ['foo' => true]));
29+
assertType('array<int, array(1|2|3, 4|5|6)>&nonEmpty', array_map(null, [1, 2, 3], [4, 5, 6]));
30+
31+
assertType('array<string, int>&nonEmpty', array_map(null, $array));
32+
assertType('array<int, array(int, int)>&nonEmpty', array_map(null, $array, $array));
33+
assertType('array<int, array(int, bool)>&nonEmpty', array_map(null, $array, $other));
34+
}
35+
2136
}

tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void
796796
$this->checkExplicitMixed = $checkExplicitMixed;
797797
$this->analyse([__DIR__ . '/data/array_map_multiple.php'], [
798798
[
799-
'Parameter #1 $callback of function array_map expects callable(1|2, \'bar\'|\'foo\'): mixed, Closure(int, int): void given.',
799+
'Parameter #1 $callback of function array_map expects (callable(1|2, \'bar\'|\'foo\'): mixed)|null, Closure(int, int): void given.',
800800
58,
801801
],
802802
]);
@@ -840,11 +840,11 @@ public function testBug5356(): void
840840

841841
$this->analyse([__DIR__ . '/data/bug-5356.php'], [
842842
[
843-
'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.',
843+
'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.',
844844
13,
845845
],
846846
[
847-
'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.',
847+
'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.',
848848
21,
849849
],
850850
]);
@@ -854,7 +854,7 @@ public function testBug1954(): void
854854
{
855855
$this->analyse([__DIR__ . '/data/bug-1954.php'], [
856856
[
857-
'Parameter #1 $callback of function array_map expects callable(1|stdClass): mixed, Closure(string): string given.',
857+
'Parameter #1 $callback of function array_map expects (callable(1|stdClass): mixed)|null, Closure(string): string given.',
858858
7,
859859
],
860860
]);

tests/PHPStan/Rules/Functions/data/array_map_multiple.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,9 @@ public function doFoo(): void
6060
}, [1, 2], ['foo', 'bar']);
6161
}
6262

63+
public function arrayMapNull(): void
64+
{
65+
array_map(null, [1, 2], [3, 4]);
66+
}
67+
6368
}

0 commit comments

Comments
 (0)