diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index fa3b5328974..2999480dd36 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -29,6 +29,7 @@ use Psalm\Type\Union; use UnexpectedValueException; +use function array_slice; use function end; use function strtolower; use function substr; @@ -65,6 +66,8 @@ public static function isContainedBy( return false; } + $input_variadic_param_idx = null; + if ($input_type_part->params !== null && $container_type_part->params !== null) { foreach ($input_type_part->params as $i => $input_param) { $container_param = null; @@ -79,7 +82,15 @@ public static function isContainedBy( } } + if ($input_param->is_variadic) { + $input_variadic_param_idx = $i; + } + if (!$container_param) { + if ($input_param->is_variadic) { + break; + } + if ($input_param->is_optional) { break; } @@ -103,6 +114,26 @@ public static function isContainedBy( } } + if ($input_variadic_param_idx && isset($input_type_part->params[$input_variadic_param_idx])) { + $input_param = $input_type_part->params[$input_variadic_param_idx]; + + foreach (array_slice($container_type_part->params ?? [], $input_variadic_param_idx) as $container_param) { + if ($container_param->type + && !$container_param->type->hasMixed() + && !UnionTypeComparator::isContainedBy( + $codebase, + $container_param->type, + $input_param->type ?: Type::getMixed(), + false, + false, + $atomic_comparison_result, + ) + ) { + return false; + } + } + } + if (isset($container_type_part->return_type)) { if (!isset($input_type_part->return_type)) { if ($atomic_comparison_result) { diff --git a/tests/CallableTest.php b/tests/CallableTest.php index ff9acffaf65..5815222b1ad 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1821,6 +1821,29 @@ abstract class TestClass { use TestTrait; }', ], + 'variadicClosureAssignability' => [ + 'code' => ' [], + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } @@ -2262,6 +2285,29 @@ function appHandler(mixed $param1): array 'ignored_issues' => [], 'php_version' => '8.1', ], + 'variadicClosureAssignability' => [ + 'code' => ' 'InvalidScalarArgument', + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } }