Skip to content

Commit

Permalink
Fix erroneous Closure::__invoke return type
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Sep 1, 2020
1 parent c7c0493 commit 167e1c4
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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;
Expand Down Expand Up @@ -258,6 +259,33 @@ public static function analyze(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,42 @@ public static function getMethodReturnType(
if (!$source instanceof \Psalm\Internal\Analyzer\StatementsAnalyzer) {
return;
}

$type_provider = $source->getNodeTypeProvider();
$codebase = $source->getCodebase();

$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 ($method_name_lowercase === 'fromcallable') {
$closure_types = [];

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();
}
}
}
18 changes: 18 additions & 0 deletions tests/ClosureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,24 @@ function accept(Generator $gen): void {}
})()
);'
],
'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'
],
];
}

Expand Down

0 comments on commit 167e1c4

Please sign in to comment.