Skip to content
Permalink
Browse files

Infer closure param type for array_filter/array_map

Fixes #664
  • Loading branch information...
muglug committed May 7, 2019
1 parent 44f0c64 commit d7ee952084e84416004a86b1bf6f9749b15ee270
Showing with 84 additions and 3 deletions.
  1. +62 −3 src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php
  2. +22 −0 tests/FunctionCallTest.php
@@ -349,6 +349,10 @@ protected static function checkFunctionArguments(
return;
}
if ($method_id === 'array_map' && count($args) === 2) {
$args = array_reverse($args, true);
}
foreach ($args as $argument_offset => $arg) {
if ($function_params !== null) {
$param = $argument_offset < count($function_params)
@@ -397,6 +401,8 @@ protected static function checkFunctionArguments(
$toggled_class_exists = true;
}
$codebase = $statements_analyzer->getCodebase();
if ($arg->value instanceof PhpParser\Node\Expr\Closure
&& $generic_params
&& $param
@@ -409,12 +415,33 @@ function (PhpParser\Node\Param $closure_param) : bool {
}
)
) {
$replaced_type = clone $param->type;
if (count($args) === 2
&& (($argument_offset === 1 && $method_id === 'array_filter')
|| ($argument_offset === 0 && $method_id === 'array_map'))
) {
$replaced_type = new Type\Union([
new Type\Atomic\TCallable(
'callable',
[
new \Psalm\Storage\FunctionLikeParameter(
'function',
false,
new Type\Union([
new Type\Atomic\TTemplateParam(
'ArrayValue',
Type::getMixed()
)
])
)
]
)
]);
} else {
$replaced_type = clone $param->type;
}
$empty_generic_params = [];
$codebase = $statements_analyzer->getCodebase();
$replaced_type->replaceTemplateTypesWithStandins(
$generic_params,
$empty_generic_params,
@@ -460,6 +487,38 @@ function (PhpParser\Node\Param $closure_param) : bool {
$context->inside_call = false;
}
if (count($args) === 2
&& (($argument_offset === 0 && $method_id === 'array_filter')
|| ($argument_offset === 1 || $method_id === 'array_map'))
) {
$generic_param_type = new Type\Union([
new Type\Atomic\TArray([
Type::getArrayKey(),
new Type\Union([
new Type\Atomic\TTemplateParam(
'ArrayValue',
Type::getMixed()
)
])
])
]);
$template_types = ['ArrayValue' => ['' => [Type::getMixed()]]];
if ($generic_params === null) {
$generic_params = [];
}
$generic_param_type->replaceTemplateTypesWithStandins(
$template_types,
$generic_params,
$codebase,
isset($arg->value->inferredType)
? $arg->value->inferredType
: null
);
}
if ($context->collect_references
&& ($arg->value instanceof PhpParser\Node\Expr\AssignOp
|| $arg->value instanceof PhpParser\Node\Expr\PreInc
@@ -1627,6 +1627,18 @@ function getOrderings(array $arr): int {
'$seconds' => 'string|int|float',
],
],
'inferArrayMapReturnType' => [
'<?php
/** @return array<string> */
function Foo(DateTime ...$dateTimes) : array {
return array_map(
function ($dateTime) {
return (string) ($dateTime->format("c"));
},
$dateTimes
);
}',
],
];
}
@@ -1647,6 +1659,16 @@ function(?int $i) {
'error_message' => 'MixedArgumentTypeCoercion',
'error_levels' => ['MissingClosureParamType', 'MissingClosureReturnType'],
],
'arrayFilterUseMethodOnInferrableInt' => [
'<?php
$a = array_filter([1, 2, 3, 4], function ($i) { return $i->foo(); });',
'error_message' => 'InvalidMethodCall',
],
'arrayMapUseMethodOnInferrableInt' => [
'<?php
$a = array_map(function ($i) { return $i->foo(); }, [1, 2, 3, 4]);',
'error_message' => 'InvalidMethodCall',
],
'invalidScalarArgument' => [
'<?php
function fooFoo(int $a): void {}

0 comments on commit d7ee952

Please sign in to comment.
You can’t perform that action at this time.