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

Fix erroneous Closure::__invoke return type

This commit is contained in:
Brown 2020-09-01 12:33:25 -04:00
parent c6ea274180
commit b5279cd7d4
3 changed files with 74 additions and 26 deletions

View File

@ -10,6 +10,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentMapPopulator; use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentMapPopulator;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector; use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier; use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Internal\Type\Comparator\UnionTypeComparator;
@ -258,6 +259,33 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
false false
); );
if ($naive_method_exists && $fq_class_name === 'Closure' && $method_name_lc === '__invoke') {
$old_node_data = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
$fake_function_call = new PhpParser\Node\Expr\FuncCall(
$stmt->var,
$stmt->args,
$stmt->getAttributes()
);
$ret_value = FunctionCallAnalyzer::analyze(
$statements_analyzer,
$fake_function_call,
$context
);
$function_return = $statements_analyzer->node_data->getType($fake_function_call);
$statements_analyzer->node_data = $old_node_data;
if (!$result->return_type) {
$result->return_type = $function_return;
} else {
$result->return_type = Type::combineUnionTypes($function_return, $result->return_type);
}
return;
}
$fake_method_exists = false; $fake_method_exists = false;
if (!$naive_method_exists if (!$naive_method_exists

View File

@ -35,13 +35,14 @@ class ClosureFromCallableReturnTypeProvider implements \Psalm\Plugin\Hook\Method
if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) { if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
return; return;
} }
$type_provider = $source->getNodeTypeProvider(); $type_provider = $source->getNodeTypeProvider();
$codebase = $source->getCodebase(); $codebase = $source->getCodebase();
if ($method_name_lowercase === 'fromcallable') {
$closure_types = []; $closure_types = [];
if ($method_name_lowercase === 'fromcallable' if (isset($call_args[0])
&& isset($call_args[0])
&& ($input_type = $type_provider->getType($call_args[0]->value)) && ($input_type = $type_provider->getType($call_args[0]->value))
) { ) {
foreach ($input_type->getAtomicTypes() as $atomic_type) { foreach ($input_type->getAtomicTypes() as $atomic_type) {
@ -72,3 +73,4 @@ class ClosureFromCallableReturnTypeProvider implements \Psalm\Plugin\Hook\Method
return Type::getClosure(); return Type::getClosure();
} }
} }
}

View File

@ -492,6 +492,24 @@ class ClosureTest extends TestCase
})() })()
);' );'
], ],
'callingInvokeOnClosureIsSameAsCallingDirectly' => [
'<?php
class A {
/** @var Closure(int):int */
private Closure $a;
public function __construct() {
$this->a = fn(int $a) : int => $a + 5;
}
public function invoker(int $b) : int {
return $this->a->__invoke($b);
}
}',
'assertions' => [],
'error_levels' => [],
'7.4'
],
]; ];
} }