mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Test with invalid first-class-callable
This commit is contained in:
parent
f26c16d2ab
commit
62a79eeff3
@ -425,37 +425,44 @@ class ArgumentsAnalyzer
|
||||
private static function handleFirstClassCallableCallArg(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
TemplateResult $inferred_template_result,
|
||||
FunctionLikeStorage $storage,
|
||||
FunctionLikeParameter $actual_func_param
|
||||
FunctionLikeStorage $first_class_callable_storage,
|
||||
FunctionLikeParameter $expected_callable_param
|
||||
): ?Union {
|
||||
$expected_func_type = $actual_func_param->type ?? Type::getMixed();
|
||||
if (!$expected_func_type->isSingle()) {
|
||||
$expected_callable_type = $expected_callable_param->type ?? Type::getMixed();
|
||||
|
||||
if (!$expected_callable_type->isSingle()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expected_func_atomic = $expected_func_type->getSingleAtomic();
|
||||
if (!$expected_func_atomic instanceof TClosure && !$expected_func_atomic instanceof TCallable) {
|
||||
$expected_callable_atomic = $expected_callable_type->getSingleAtomic();
|
||||
|
||||
if (!$expected_callable_atomic instanceof TClosure && !$expected_callable_atomic instanceof TCallable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
$remapped_lower_bounds = [];
|
||||
|
||||
foreach ($expected_func_atomic->params ?? [] as $offset => $expected_param) {
|
||||
if (!isset($storage->params[$offset])) {
|
||||
foreach ($expected_callable_atomic->params ?? [] as $offset => $expected_callable_param) {
|
||||
if (!isset($first_class_callable_storage->params[$offset])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$actual_param = $storage->params[$offset];
|
||||
$actual_callable_storage_param = $first_class_callable_storage->params[$offset];
|
||||
|
||||
$expected_param_type = $expected_param->type ?? Type::getMixed();
|
||||
$actual_param_type = $actual_param->type ?? Type::getMixed();
|
||||
$expected_callable_param_type = $expected_callable_param->type ?? Type::getMixed();
|
||||
$actual_callable_param_type = $actual_callable_storage_param->type ?? Type::getMixed();
|
||||
|
||||
if (!$expected_param_type->isSingle() || !$actual_param_type->isSingle()) {
|
||||
if (!$codebase->isTypeContainedByType($actual_callable_param_type, $expected_callable_param_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected_atomic = $expected_param_type->getSingleAtomic();
|
||||
$actual_atomic = $actual_param_type->getSingleAtomic();
|
||||
if (!$expected_callable_param_type->isSingle() || !$actual_callable_param_type->isSingle()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected_atomic = $expected_callable_param_type->getSingleAtomic();
|
||||
$actual_atomic = $actual_callable_param_type->getSingleAtomic();
|
||||
|
||||
if (!$expected_atomic instanceof TTemplateParam || !$actual_atomic instanceof TTemplateParam) {
|
||||
continue;
|
||||
@ -466,23 +473,21 @@ class ArgumentsAnalyzer
|
||||
]);
|
||||
}
|
||||
|
||||
$replaced_container_hof_atomic = new Union([
|
||||
new TClosure(
|
||||
'Closure',
|
||||
$storage->params,
|
||||
$storage->return_type,
|
||||
$storage->pure,
|
||||
),
|
||||
]);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
$remapped_first_class_callable = TemplateInferredTypeReplacer::replace(
|
||||
new Union([
|
||||
new TClosure(
|
||||
'Closure',
|
||||
$first_class_callable_storage->params,
|
||||
$first_class_callable_storage->return_type,
|
||||
$first_class_callable_storage->pure,
|
||||
),
|
||||
]),
|
||||
new TemplateResult($inferred_template_result->template_types, $remapped_lower_bounds),
|
||||
$codebase,
|
||||
);
|
||||
|
||||
return TemplateInferredTypeReplacer::replace(
|
||||
TemplateInferredTypeReplacer::replace(
|
||||
$replaced_container_hof_atomic,
|
||||
new TemplateResult($inferred_template_result->template_types, $remapped_lower_bounds),
|
||||
$codebase,
|
||||
),
|
||||
$remapped_first_class_callable,
|
||||
$inferred_template_result,
|
||||
$codebase,
|
||||
);
|
||||
|
@ -701,14 +701,14 @@ class CallableTest extends TestCase
|
||||
* @param C $value3
|
||||
* @return list{A, B, C}
|
||||
*/
|
||||
function doubleId(mixed $value1, mixed $value2, mixed $value3): array
|
||||
function tripleId(mixed $value1, mixed $value2, mixed $value3): array
|
||||
{
|
||||
return [$value1, $value2, $value3];
|
||||
}
|
||||
|
||||
$processor = new Processor(a: 1, b: 2, c: 3);
|
||||
|
||||
$test = $processor->process(doubleId(...));
|
||||
$test = $processor->process(tripleId(...));
|
||||
',
|
||||
'assertions' => [
|
||||
'$test' => 'list{int, int, int}',
|
||||
@ -1929,6 +1929,47 @@ class CallableTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'invalidFirstClassCallableCannotBeInferred' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @template T1
|
||||
*/
|
||||
final class App
|
||||
{
|
||||
/**
|
||||
* @param T1 $param1
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly mixed $param1,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @template T2
|
||||
* @param callable(T1): T2 $callback
|
||||
* @return T2
|
||||
*/
|
||||
public function run(callable $callback): mixed
|
||||
{
|
||||
return $callback($this->param1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template P1 of int|float
|
||||
* @param P1 $param1
|
||||
* @return array{param1: P1}
|
||||
*/
|
||||
function appHandler(mixed $param1): array
|
||||
{
|
||||
return ["param1" => $param1];
|
||||
}
|
||||
|
||||
$result = (new App(param1: 42))->run(appHandler(...));
|
||||
',
|
||||
'error_message' => 'InvalidArgument',
|
||||
'ignored_issues' => [],
|
||||
'php_version' => '8.1',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user