mirror of
https://github.com/danog/psalm.git
synced 2024-11-29 20:28:59 +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;
|
$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 {
|
} else {
|
||||||
$param_type = Type::getMixed();
|
$param_type = Type::getMixed();
|
||||||
}
|
}
|
||||||
|
@ -554,7 +554,16 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
$invalid_function_call_types = [];
|
$invalid_function_call_types = [];
|
||||||
$has_valid_function_call_type = false;
|
$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 instanceof Type\Atomic\TClosure || $var_type_part instanceof TCallable) {
|
||||||
if (!$var_type_part->is_pure && $context->pure) {
|
if (!$var_type_part->is_pure && $context->pure) {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
@ -593,8 +602,6 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
|||||||
|
|
||||||
$function_call_info->function_exists = true;
|
$function_call_info->function_exists = true;
|
||||||
$has_valid_function_call_type = 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) {
|
} elseif ($var_type_part instanceof TMixed || $var_type_part instanceof TTemplateParam) {
|
||||||
$has_valid_function_call_type = true;
|
$has_valid_function_call_type = true;
|
||||||
|
|
||||||
|
@ -569,7 +569,16 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
|||||||
|
|
||||||
$new_type = null;
|
$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 ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) {
|
||||||
if (!$statements_analyzer->node_data->getType($stmt)) {
|
if (!$statements_analyzer->node_data->getType($stmt)) {
|
||||||
$new_type_part = new Type\Atomic\TTemplateParam(
|
$new_type_part = new Type\Atomic\TTemplateParam(
|
||||||
@ -735,9 +744,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
|||||||
)) {
|
)) {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
} elseif ($lhs_type_part instanceof Type\Atomic\TMixed
|
} elseif ($lhs_type_part instanceof Type\Atomic\TMixed) {
|
||||||
|| $lhs_type_part instanceof Type\Atomic\TTemplateParam
|
|
||||||
) {
|
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
new MixedMethodCall(
|
new MixedMethodCall(
|
||||||
'Cannot call constructor on an unknown class',
|
'Cannot call constructor on an unknown class',
|
||||||
|
@ -1995,12 +1995,24 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
|
|||||||
$callable_types[] = $type;
|
$callable_types[] = $type;
|
||||||
$did_remove_type = true;
|
$did_remove_type = true;
|
||||||
} elseif ($type instanceof TTemplateParam) {
|
} elseif ($type instanceof TTemplateParam) {
|
||||||
if ($type->as->isMixed()) {
|
if ($type->as->hasCallableType() || $type->as->hasMixed()) {
|
||||||
$type = clone $type;
|
$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;
|
$did_remove_type = true;
|
||||||
|
|
||||||
|
$callable_types[] = $type;
|
||||||
} else {
|
} else {
|
||||||
$did_remove_type = true;
|
$did_remove_type = true;
|
||||||
}
|
}
|
||||||
|
@ -711,6 +711,40 @@ class ConditionalReturnTypeTest extends TestCase
|
|||||||
return new NullObject();
|
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