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\ClassTemplateParamCollector;
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\StatementsAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
@ -258,6 +259,33 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
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;
if (!$naive_method_exists

View File

@ -35,40 +35,42 @@ class ClosureFromCallableReturnTypeProvider implements \Psalm\Plugin\Hook\Method
if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
return;
}
$type_provider = $source->getNodeTypeProvider();
$codebase = $source->getCodebase();
$closure_types = [];
if ($method_name_lowercase === 'fromcallable') {
$closure_types = [];
if ($method_name_lowercase === 'fromcallable'
&& isset($call_args[0])
&& ($input_type = $type_provider->getType($call_args[0]->value))
) {
foreach ($input_type->getAtomicTypes() as $atomic_type) {
$candidate_callable = CallableTypeComparator::getCallableFromAtomic(
$codebase,
$atomic_type,
null,
$source
);
if ($candidate_callable) {
$closure_types[] = new Type\Atomic\TFn(
'Closure',
$candidate_callable->params,
$candidate_callable->return_type,
$candidate_callable->is_pure
if (isset($call_args[0])
&& ($input_type = $type_provider->getType($call_args[0]->value))
) {
foreach ($input_type->getAtomicTypes() as $atomic_type) {
$candidate_callable = CallableTypeComparator::getCallableFromAtomic(
$codebase,
$atomic_type,
null,
$source
);
} else {
return Type::getClosure();
if ($candidate_callable) {
$closure_types[] = new Type\Atomic\TFn(
'Closure',
$candidate_callable->params,
$candidate_callable->return_type,
$candidate_callable->is_pure
);
} else {
return Type::getClosure();
}
}
}
}
if ($closure_types) {
return TypeCombination::combineTypes($closure_types, $codebase);
}
if ($closure_types) {
return TypeCombination::combineTypes($closure_types, $codebase);
}
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'
],
];
}