mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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:
parent
cdc431e940
commit
5a0e9d0965
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user