mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #742 - infer template type from closure
This commit is contained in:
parent
cc26ce682e
commit
b19dd25881
@ -834,9 +834,17 @@ class CallAnalyzer
|
||||
if ($class_storage && $class_storage->template_types) {
|
||||
$template_types = array_merge($template_types, $class_storage->template_types);
|
||||
}
|
||||
|
||||
foreach ($template_types as $key => $type) {
|
||||
$template_types[$key] = clone $type;
|
||||
}
|
||||
}
|
||||
|
||||
$existing_generic_params_to_strings = $generic_params ?: [];
|
||||
$existing_generic_params = $generic_params ?: [];
|
||||
|
||||
foreach ($existing_generic_params as $key => $type) {
|
||||
$existing_generic_params[$key] = clone $type;
|
||||
}
|
||||
|
||||
foreach ($args as $argument_offset => $arg) {
|
||||
$function_param = count($function_params) > $argument_offset
|
||||
@ -952,11 +960,11 @@ class CallAnalyzer
|
||||
}
|
||||
|
||||
if ($function_storage) {
|
||||
if ($existing_generic_params_to_strings) {
|
||||
if ($existing_generic_params) {
|
||||
$empty_generic_params = [];
|
||||
|
||||
$param_type->replaceTemplateTypesWithStandins(
|
||||
$existing_generic_params_to_strings,
|
||||
$existing_generic_params,
|
||||
$empty_generic_params,
|
||||
$codebase,
|
||||
$arg->value->inferredType
|
||||
|
@ -517,7 +517,7 @@ abstract class Atomic
|
||||
* @return void
|
||||
*/
|
||||
public function replaceTemplateTypesWithStandins(
|
||||
array $template_types,
|
||||
array &$template_types,
|
||||
array &$generic_params,
|
||||
Codebase $codebase = null,
|
||||
Type\Atomic $input_type = null
|
||||
|
@ -166,7 +166,7 @@ trait CallableTrait
|
||||
* @return void
|
||||
*/
|
||||
public function replaceTemplateTypesWithStandins(
|
||||
array $template_types,
|
||||
array &$template_types,
|
||||
array &$generic_params,
|
||||
Codebase $codebase = null,
|
||||
Atomic $input_type = null
|
||||
@ -189,7 +189,8 @@ trait CallableTrait
|
||||
$template_types,
|
||||
$generic_params,
|
||||
$codebase,
|
||||
$input_param_type
|
||||
$input_param_type,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ trait GenericTrait
|
||||
* @return void
|
||||
*/
|
||||
public function replaceTemplateTypesWithStandins(
|
||||
array $template_types,
|
||||
array &$template_types,
|
||||
array &$generic_params,
|
||||
Codebase $codebase = null,
|
||||
Atomic $input_type = null
|
||||
|
@ -789,10 +789,11 @@ class Union
|
||||
* @return void
|
||||
*/
|
||||
public function replaceTemplateTypesWithStandins(
|
||||
array $template_types,
|
||||
array &$template_types,
|
||||
array &$generic_params,
|
||||
Codebase $codebase = null,
|
||||
Type\Union $input_type = null
|
||||
Type\Union $input_type = null,
|
||||
bool $add_upper_bound = false
|
||||
) {
|
||||
$keys_to_unset = [];
|
||||
|
||||
@ -802,6 +803,9 @@ class Union
|
||||
) {
|
||||
if ($template_types[$key]->getId() !== $key) {
|
||||
$first_atomic_type = array_values($template_types[$key]->getTypes())[0];
|
||||
if ($add_upper_bound && $input_type) {
|
||||
$template_types[$key] = clone $input_type;
|
||||
}
|
||||
$this->types[$first_atomic_type->getKey()] = clone $first_atomic_type;
|
||||
if ($first_atomic_type->getKey() !== $key) {
|
||||
$keys_to_unset[] = $key;
|
||||
|
@ -1141,6 +1141,28 @@ class TemplateTest extends TestCase
|
||||
'assertions' => [],
|
||||
'error_levels' => [],
|
||||
],
|
||||
'bindFirstTemplatedClosureParameter' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param Closure(T):void $t1
|
||||
* @param T $t2
|
||||
*/
|
||||
function apply(Closure $t1, $t2) : void
|
||||
{
|
||||
$t1($t2);
|
||||
}
|
||||
|
||||
apply(function(int $_i) : void {}, 5);
|
||||
apply(function(string $_i) : void {}, "hello");
|
||||
apply(function(stdClass $_i) : void {}, new stdClass);
|
||||
|
||||
class A {}
|
||||
class AChild extends A {}
|
||||
|
||||
apply(function(A $_i) : void {}, new AChild());',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -1412,6 +1434,41 @@ class TemplateTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'MixedTypeCoercion',
|
||||
],
|
||||
'bindFirstTemplatedClosureParameter' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param Closure(T):void $t1
|
||||
* @param T $t2
|
||||
*/
|
||||
function apply(Closure $t1, $t2) : void
|
||||
{
|
||||
$t1($t2);
|
||||
}
|
||||
|
||||
apply(function(int $_i) : void {}, "hello");',
|
||||
'error_message' => 'InvalidScalarArgument',
|
||||
],
|
||||
'bindFirstTemplatedClosureParameterTypeCoercion' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template T
|
||||
*
|
||||
* @param Closure(T):void $t1
|
||||
* @param T $t2
|
||||
*/
|
||||
function apply(Closure $t1, $t2) : void
|
||||
{
|
||||
$t1($t2);
|
||||
}
|
||||
|
||||
class A {}
|
||||
class AChild extends A {}
|
||||
|
||||
apply(function(AChild $_i) : void {}, new A());',
|
||||
'error_message' => 'TypeCoercion',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user