1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Fix #5107 - treat function-bound templated parameters the same

Previously they were treated differently depending on whether or not they were inside a method
This commit is contained in:
Matt Brown 2021-01-26 22:43:42 -05:00 committed by Daniil Gentili
parent cdc431e940
commit 5a0e9d0965
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
5 changed files with 70 additions and 22 deletions

View File

@ -978,18 +978,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
$check_stmts = false;
}
}
if (!$storage instanceof MethodStorage
&& $param_type->hasTemplate()
&& $param_type->isSingle()
) {
/** @var Type\Atomic\TTemplateParam */
$template_type = \array_values($param_type->getAtomicTypes())[0];
if ($template_type->as->getTemplateTypes()) {
$param_type = $template_type->as;
}
}
} else {
$param_type = Type::getMixed();
}

View File

@ -554,7 +554,16 @@ class FunctionCallAnalyzer extends CallAnalyzer
$invalid_function_call_types = [];
$has_valid_function_call_type = false;
foreach ($stmt_name_type->getAtomicTypes() as $var_type_part) {
$var_atomic_types = $stmt_name_type->getAtomicTypes();
while ($var_atomic_types) {
$var_type_part = \array_shift($var_atomic_types);
if ($var_type_part instanceof TTemplateParam) {
$var_atomic_types = \array_merge($var_atomic_types, $var_type_part->as->getAtomicTypes());
continue;
}
if ($var_type_part instanceof Type\Atomic\TClosure || $var_type_part instanceof TCallable) {
if (!$var_type_part->is_pure && $context->pure) {
if (IssueBuffer::accepts(
@ -593,8 +602,6 @@ class FunctionCallAnalyzer extends CallAnalyzer
$function_call_info->function_exists = true;
$has_valid_function_call_type = true;
} elseif ($var_type_part instanceof TTemplateParam && $var_type_part->as->hasCallableType()) {
$has_valid_function_call_type = true;
} elseif ($var_type_part instanceof TMixed || $var_type_part instanceof TTemplateParam) {
$has_valid_function_call_type = true;

View File

@ -569,7 +569,16 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
$new_type = null;
foreach ($stmt_class_type->getAtomicTypes() as $lhs_type_part) {
$stmt_class_types = $stmt_class_type->getAtomicTypes();
while ($stmt_class_types) {
$lhs_type_part = \array_shift($stmt_class_types);
if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) {
$stmt_class_types = \array_merge($stmt_class_types, $lhs_type_part->as->getAtomicTypes());
continue;
}
if ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) {
if (!$statements_analyzer->node_data->getType($stmt)) {
$new_type_part = new Type\Atomic\TTemplateParam(
@ -735,9 +744,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
)) {
// fall through
}
} elseif ($lhs_type_part instanceof Type\Atomic\TMixed
|| $lhs_type_part instanceof Type\Atomic\TTemplateParam
) {
} elseif ($lhs_type_part instanceof Type\Atomic\TMixed) {
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot call constructor on an unknown class',

View File

@ -1995,12 +1995,24 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
$callable_types[] = $type;
$did_remove_type = true;
} elseif ($type instanceof TTemplateParam) {
if ($type->as->isMixed()) {
if ($type->as->hasCallableType() || $type->as->hasMixed()) {
$type = clone $type;
$type->as = new Type\Union([new Type\Atomic\TCallable]);
$type->as = self::reconcileCallable(
$codebase,
$type->as,
null,
$negated,
null,
$suppressed_issues,
$failed_reconciliation,
$is_equality
);
}
$callable_types[] = $type;
$did_remove_type = true;
$callable_types[] = $type;
} else {
$did_remove_type = true;
}

View File

@ -711,6 +711,40 @@ class ConditionalReturnTypeTest extends TestCase
return new NullObject();
}'
],
'reconcileCallableFunctionTemplateParam' => [
'<?php
/**
* @template T
* @template TOptionalClosure as (callable():T)|null
* @param TOptionalClosure $cb
* @return (TOptionalClosure is null ? int : T)
*/
function f($cb) {
if (is_callable($cb)) {
return $cb();
}
return 1;
}'
],
'reconcileCallableClassTemplateParam' => [
'<?php
class C {
/**
* @template T
* @template TOptionalClosure as (callable():T)|null
* @param TOptionalClosure $cb
* @return (TOptionalClosure is null ? int : T)
*/
public static function f($cb) {
if (is_callable($cb)) {
return $cb();
}
return 1;
}
}'
],
];
}
}