1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Contextual inference for closure param types

This commit is contained in:
adrew 2021-12-27 19:35:37 +03:00
parent 910c34f09c
commit c0ca383020
3 changed files with 115 additions and 2 deletions

View File

@ -231,6 +231,23 @@ class ArgumentsAnalyzer
);
}
$inferred_arg_type = $statements_analyzer->node_data->getType($arg->value);
if (null !== $inferred_arg_type && null !== $template_result && null !== $param && null !== $param->type) {
$codebase = $statements_analyzer->getCodebase();
TemplateStandinTypeReplacer::replace(
clone $param->type,
$template_result,
$codebase,
$statements_analyzer,
$inferred_arg_type,
$argument_offset,
$context->self,
$context->calling_method_id ?: $context->calling_function_id
);
}
if ($toggled_class_exists) {
$context->inside_class_exists = false;
}

View File

@ -10,7 +10,6 @@ use Psalm\Internal\Algebra;
use Psalm\Internal\Algebra\FormulaGenerator;
use Psalm\Internal\Analyzer\AlgebraAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
@ -165,13 +164,20 @@ class FunctionCallAnalyzer extends CallAnalyzer
}
if (!$is_first_class_callable) {
$template_result = null;
if (isset($function_call_info->function_storage->template_types)) {
$template_result = new TemplateResult($function_call_info->function_storage->template_types ?: [], []);
}
ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$function_call_info->function_params,
$function_call_info->function_id,
$function_call_info->allow_named_args,
$context
$context,
$template_result
);
}

View File

@ -147,6 +147,96 @@ class CallableTest extends TestCase
'$b' => 'ArrayList<array{int}>',
],
],
'inferArgByPreviousFunctionArg' => [
'<?php
/**
* @template A
* @template B
*
* @param iterable<array-key, A> $_collection
* @param callable(A): B $_ab
* @return list<B>
*/
function map(iterable $_collection, callable $_ab) { return []; }
/** @template T */
final class Foo
{
/** @return Foo<int> */
public function toInt() { throw new RuntimeException("???"); }
}
/** @var list<Foo<string>> */
$items = [];
$inferred = map($items, function ($i) {
return $i->toInt();
});',
'assertions' => [
'$inferred' => 'list<Foo<int>>',
],
],
'inferTemplateForExplicitlyTypedArgByPreviousFunctionArg' => [
'<?php
/**
* @template A
* @template B
*
* @param iterable<array-key, A> $_collection
* @param callable(A): B $_ab
* @return list<B>
*/
function map(iterable $_collection, callable $_ab) { return []; }
/** @template T */
final class Foo
{
/** @return Foo<int> */
public function toInt() { throw new RuntimeException("???"); }
}
/** @var list<Foo<string>> */
$items = [];
$inferred = map($items, function (Foo $i) {
return $i->toInt();
});',
'assertions' => [
'$inferred' => 'list<Foo<int>>',
],
],
'doNotInferTemplateForExplicitlyTypedWithPhpdocArgByPreviousFunctionArg' => [
'<?php
/**
* @template A
* @template B
*
* @param iterable<array-key, A> $_collection
* @param callable(A): B $_ab
* @return list<B>
*/
function map(iterable $_collection, callable $_ab) { return []; }
/** @template T */
final class Foo
{
/** @return Foo<int> */
public function toInt() { throw new RuntimeException("???"); }
}
/** @var list<Foo<string>> */
$items = [];
$inferred = map($items,
/** @param Foo $i */
function ($i) {
return $i;
}
);',
'assertions' => [
'$inferred' => 'list<Foo>',
],
],
'varReturnType' => [
'<?php
$add_one = function(int $a) : int {