mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
parent
44f0c64877
commit
d7ee952084
@ -349,6 +349,10 @@ class CallAnalyzer
|
||||
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 @@ class CallAnalyzer
|
||||
$toggled_class_exists = true;
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($arg->value instanceof PhpParser\Node\Expr\Closure
|
||||
&& $generic_params
|
||||
&& $param
|
||||
@ -409,12 +415,33 @@ class CallAnalyzer
|
||||
}
|
||||
)
|
||||
) {
|
||||
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 @@ class CallAnalyzer
|
||||
$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 @@ class FunctionCallTest extends TestCase
|
||||
'$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 @@ class FunctionCallTest extends TestCase
|
||||
'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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user