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

Merge pull request #8594 from kkmuffme/callable-invalidargument-required-param-mismatch-missing-error

ensure callbacks have the required number of params
This commit is contained in:
orklah 2022-10-22 12:20:29 +02:00 committed by GitHub
commit b739b67080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 3 deletions

View File

@ -6,6 +6,7 @@ use Psalm\Codebase;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TClassConstant;
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TIntRange;
@ -21,6 +22,10 @@ use function array_merge;
use function array_pop;
use function array_push;
use function array_reverse;
use function count;
use function is_array;
use const PHP_INT_MAX;
/**
* @internal
@ -134,6 +139,48 @@ class UnionTypeComparator
continue;
}
// if params are specified
if ($container_type_part instanceof TCallable
&& is_array($container_type_part->params)
&& $input_type_part instanceof TCallable
) {
$container_all_param_count = count($container_type_part->params);
$container_required_param_count = 0;
foreach ($container_type_part->params as $index => $container_param) {
if ($container_param->is_optional === false) {
$container_required_param_count = $index + 1;
}
if ($container_param->is_variadic === true) {
$container_all_param_count = PHP_INT_MAX;
}
}
$input_required_param_count = 0;
if (!is_array($input_type_part->params)) {
// it's not declared, there can be an arbitrary number of params
$input_all_param_count = PHP_INT_MAX;
} else {
$input_all_param_count = count($input_type_part->params);
foreach ($input_type_part->params as $index => $input_param) {
if ($input_param->is_optional === false) {
$input_required_param_count = $index + 1;
}
if ($input_param->is_variadic === true) {
$input_all_param_count = PHP_INT_MAX;
}
}
}
// too few or too many non-optional params provided in callback
if ($container_required_param_count > $input_all_param_count
|| $container_all_param_count < $input_required_param_count
) {
return false;
}
}
if ($union_comparison_result) {
$atomic_comparison_result = new TypeComparisonResult();
} else {

View File

@ -312,6 +312,40 @@ class ArgTest extends TestCase
}
',
],
'variadicCallbackArgsCountMatch' => [
'<?php
/**
* @param callable(string, string):void $callback
* @return void
*/
function caller($callback) {}
/**
* @param string ...$bar
* @return void
*/
function foo(...$bar) {}
caller("foo");',
],
'variadicCallableArgsCountMatch' => [
'<?php
/**
* @param callable(string, ...int):void $callback
* @return void
*/
function var_caller($callback) {}
/**
* @param string $a
* @param int $b
* @param int $c
* @return void
*/
function foo($a, $b, $c) {}
var_caller("foo");',
],
];
}
@ -733,6 +767,41 @@ class ArgTest extends TestCase
',
'error_message' => 'TooFewArguments',
],
'callbackArgsCountMismatch' => [
'<?php
/**
* @param callable(string, string):void $callback
* @return void
*/
function caller($callback) {}
/**
* @param string $a
* @return void
*/
function foo($a) {}
caller("foo");',
'error_message' => 'InvalidScalarArgument',
],
'callableArgsCountMismatch' => [
'<?php
/**
* @param callable(string):void $callback
* @return void
*/
function caller($callback) {}
/**
* @param string $a
* @param string $b
* @return void
*/
function foo($a, $b) {}
caller("foo");',
'error_message' => 'InvalidScalarArgument',
],
];
}
}

View File

@ -1560,13 +1560,13 @@ class FunctionTemplateTest extends TestCase
* @template TNewKey of array-key
* @template TNewValue
* @psalm-param iterable<TKey, TValue> $iterable
* @psalm-param callable(TKey, TValue): iterable<TNewKey, TNewValue> $mapper
* @psalm-param callable(TKey): iterable<TNewKey, TNewValue> $mapper
* @psalm-return \Generator<TNewKey, TNewValue>
*/
function map(iterable $iterable, callable $mapper): Generator
{
foreach ($iterable as $key => $value) {
yield from $mapper($key, $value);
foreach ($iterable as $key => $_) {
yield from $mapper($key);
}
}