Skip to content

Commit

Permalink
Make array_map() accept null as first argument
Browse files Browse the repository at this point in the history
  • Loading branch information
jlherren authored and ondrejmirtes committed Oct 6, 2021
1 parent 0d025e7 commit 84bafe7
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 12 deletions.
7 changes: 6 additions & 1 deletion src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;

/** @api */
class ParametersAcceptorSelector
Expand Down Expand Up @@ -80,7 +82,10 @@ public static function selectFromArgs(
$parameters[0] = new NativeParameterReflection(
$parameters[0]->getName(),
$parameters[0]->isOptional(),
new CallableType($callbackParameters, new MixedType(), false),
new UnionType([
new CallableType($callbackParameters, new MixedType(), false),
new NullType(),
]),
$parameters[0]->passedByReference(),
$parameters[0]->isVariadic(),
$parameters[0]->getDefaultValue()
Expand Down
33 changes: 26 additions & 7 deletions src/Type/Php/ArrayMapFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
Expand All @@ -30,23 +32,35 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

$valueType = new MixedType();
$singleArrayArgument = !isset($functionCall->getArgs()[2]);
$callableType = $scope->getType($functionCall->getArgs()[0]->value);
$callableIsNull = (new NullType())->isSuperTypeOf($callableType)->yes();

if ($callableType->isCallable()->yes()) {
$valueType = new NeverType();
foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) {
$valueType = TypeCombinator::union($valueType, $parametersAcceptor->getReturnType());
}
} elseif ($callableIsNull) {
$arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) {
$arrayBuilder->setOffsetValueType(
new ConstantIntegerType($index),
$scope->getType($arg->value)->getIterableValueType()
);
}
$valueType = $arrayBuilder->getArray();
} else {
$valueType = new MixedType();
}

$mappedArrayType = new ArrayType(
new MixedType(),
$valueType
);
$arrayType = $scope->getType($functionCall->getArgs()[1]->value);
$constantArrays = TypeUtils::getConstantArrays($arrayType);

if (!isset($functionCall->getArgs()[2])) {
if ($singleArrayArgument) {
if ($callableIsNull) {
return $arrayType;
}
$constantArrays = TypeUtils::getConstantArrays($arrayType);
if (count($constantArrays) > 0) {
$arrayTypes = [];
foreach ($constantArrays as $constantArray) {
Expand All @@ -66,6 +80,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
$arrayType->getIterableKeyType(),
$valueType
), ...TypeUtils::getAccessoryTypes($arrayType));
} else {
$mappedArrayType = new ArrayType(
new MixedType(),
$valueType
);
}
} else {
$mappedArrayType = TypeCombinator::intersect(new ArrayType(
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/array_map_multiple.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,19 @@ public function doFoo(int $i, string $s): void
assertType('array<int, int|string>&nonEmpty', $result);
}

/**
* @param non-empty-array<string, int> $array
* @param non-empty-array<int, bool> $other
*/
public function arrayMapNull(array $array, array $other): void
{
assertType('array()', array_map(null, []));
assertType('array(\'foo\' => true)', array_map(null, ['foo' => true]));
assertType('array<int, array(1|2|3, 4|5|6)>&nonEmpty', array_map(null, [1, 2, 3], [4, 5, 6]));

assertType('array<string, int>&nonEmpty', array_map(null, $array));
assertType('array<int, array(int, int)>&nonEmpty', array_map(null, $array, $array));
assertType('array<int, array(int, bool)>&nonEmpty', array_map(null, $array, $other));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void
$this->checkExplicitMixed = $checkExplicitMixed;
$this->analyse([__DIR__ . '/data/array_map_multiple.php'], [
[
'Parameter #1 $callback of function array_map expects callable(1|2, \'bar\'|\'foo\'): mixed, Closure(int, int): void given.',
'Parameter #1 $callback of function array_map expects (callable(1|2, \'bar\'|\'foo\'): mixed)|null, Closure(int, int): void given.',
58,
],
]);
Expand Down Expand Up @@ -840,11 +840,11 @@ public function testBug5356(): void

$this->analyse([__DIR__ . '/data/bug-5356.php'], [
[
'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.',
'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.',
13,
],
[
'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.',
'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.',
21,
],
]);
Expand All @@ -854,7 +854,7 @@ public function testBug1954(): void
{
$this->analyse([__DIR__ . '/data/bug-1954.php'], [
[
'Parameter #1 $callback of function array_map expects callable(1|stdClass): mixed, Closure(string): string given.',
'Parameter #1 $callback of function array_map expects (callable(1|stdClass): mixed)|null, Closure(string): string given.',
7,
],
]);
Expand Down
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Functions/data/array_map_multiple.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,9 @@ public function doFoo(): void
}, [1, 2], ['foo', 'bar']);
}

public function arrayMapNull(): void
{
array_map(null, [1, 2], [3, 4]);
}

}

0 comments on commit 84bafe7

Please sign in to comment.