1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Fix treatment of closure params in array_map

This commit is contained in:
Brown 2020-08-29 11:10:09 -04:00
parent df0d426f61
commit 3ca4a576e7
5 changed files with 45 additions and 17 deletions

View File

@ -593,7 +593,9 @@ class NonDivArithmeticOpAnalyzer
} }
if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) {
$result_type = $always_positive ? Type::getPositiveInt() : Type::getInt(); $result_type = $always_positive
? new Type\Union([new Type\Atomic\TPositiveInt(), new TLiteralInt(0)])
: Type::getInt();
} elseif (!$result_type) { } elseif (!$result_type) {
$result_type = $always_positive ? Type::getPositiveInt(true) : Type::getInt(true); $result_type = $always_positive ? Type::getPositiveInt(true) : Type::getInt(true);
} else { } else {

View File

@ -251,22 +251,25 @@ class ArgumentsAnalyzer
); );
} }
if ($param_storage->type !== $param_storage->signature_type) { if (!$param_storage->type_inferred) {
continue; if ($param_storage->type !== $param_storage->signature_type) {
} continue;
}
$type_match_found = UnionTypeComparator::isContainedBy( $type_match_found = UnionTypeComparator::isContainedBy(
$codebase, $codebase,
$replaced_type_part->params[$closure_param_offset]->type, $replaced_type_part->params[$closure_param_offset]->type,
$param_storage->type $param_storage->type
); );
if (!$type_match_found) { if (!$type_match_found) {
continue; continue;
}
} }
} }
$param_storage->type = $replaced_type_part->params[$closure_param_offset]->type; $param_storage->type = $replaced_type_part->params[$closure_param_offset]->type;
$param_storage->type_inferred = true;
if ($method_id === 'array_map' || $method_id === 'array_filter') { if ($method_id === 'array_map' || $method_id === 'array_filter') {
ArrayFetchAnalyzer::taintArrayFetch( ArrayFetchAnalyzer::taintArrayFetch(
@ -318,12 +321,14 @@ class ArgumentsAnalyzer
[] []
); );
$existing_type = $statements_analyzer->node_data->getType($arg->value);
\Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins( \Psalm\Internal\Type\UnionTemplateHandler::replaceTemplateTypesWithStandins(
$generic_param_type, $generic_param_type,
$replace_template_result, $replace_template_result,
$codebase, $codebase,
$statements_analyzer, $statements_analyzer,
$statements_analyzer->node_data->getType($arg->value), $existing_type,
$argument_offset, $argument_offset,
'fn-' . ($context->calling_method_id ?: $context->calling_function_id) 'fn-' . ($context->calling_method_id ?: $context->calling_function_id)
); );

View File

@ -888,7 +888,7 @@ class ArrayFunctionArgumentsAnalyzer
if ($union_comparison_results->type_coerced_from_mixed) { if ($union_comparison_results->type_coerced_from_mixed) {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new MixedArgumentTypeCoercion( new MixedArgumentTypeCoercion(
'First parameter of closure passed to function ' . $method_id . ' expects ' . 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' .
$closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $closure_arg), new CodeLocation($statements_analyzer->getSource(), $closure_arg),
$method_id $method_id
@ -900,7 +900,7 @@ class ArrayFunctionArgumentsAnalyzer
} else { } else {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new ArgumentTypeCoercion( new ArgumentTypeCoercion(
'First parameter of closure passed to function ' . $method_id . ' expects ' . 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' .
$closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided', $closure_param_type->getId() . ', parent type ' . $input_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $closure_arg), new CodeLocation($statements_analyzer->getSource(), $closure_arg),
$method_id $method_id
@ -922,7 +922,7 @@ class ArrayFunctionArgumentsAnalyzer
if ($union_comparison_results->scalar_type_match_found) { if ($union_comparison_results->scalar_type_match_found) {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new InvalidScalarArgument( new InvalidScalarArgument(
'First parameter of closure passed to function ' . $method_id . ' expects ' . 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' .
$closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $closure_arg), new CodeLocation($statements_analyzer->getSource(), $closure_arg),
$method_id $method_id
@ -934,7 +934,7 @@ class ArrayFunctionArgumentsAnalyzer
} elseif ($types_can_be_identical) { } elseif ($types_can_be_identical) {
if (IssueBuffer::accepts( if (IssueBuffer::accepts(
new PossiblyInvalidArgument( new PossiblyInvalidArgument(
'First parameter of closure passed to function ' . $method_id . ' expects ' 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects '
. $closure_param_type->getId() . ', possibly different type ' . $closure_param_type->getId() . ', possibly different type '
. $input_type->getId() . ' provided', . $input_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $closure_arg), new CodeLocation($statements_analyzer->getSource(), $closure_arg),
@ -946,7 +946,7 @@ class ArrayFunctionArgumentsAnalyzer
} }
} elseif (IssueBuffer::accepts( } elseif (IssueBuffer::accepts(
new InvalidArgument( new InvalidArgument(
'First parameter of closure passed to function ' . $method_id . ' expects ' . 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' .
$closure_param_type->getId() . ', ' . $input_type->getId() . ' provided', $closure_param_type->getId() . ', ' . $input_type->getId() . ' provided',
new CodeLocation($statements_analyzer->getSource(), $closure_arg), new CodeLocation($statements_analyzer->getSource(), $closure_arg),
$method_id $method_id

View File

@ -83,6 +83,11 @@ class FunctionLikeParameter
*/ */
public $assert_untainted = false; public $assert_untainted = false;
/**
* @var bool
*/
public $type_inferred = false;
/** /**
* @param string $name * @param string $name
* @param bool $by_ref * @param bool $by_ref

View File

@ -1041,6 +1041,22 @@ class ForeachTest extends \Psalm\Tests\TestCase
echo $key . " " . $value; echo $key . " " . $value;
}' }'
], ],
'loopClosure' => [
'<?php
/**
* @param list<0> $currentIndexes
*/
function cartesianProduct(array $currentIndexes): void {
while (rand(0, 1)) {
array_map(
function ($index) { echo $index; },
$currentIndexes
);
$currentIndexes[0]++;
}
}'
],
]; ];
} }