1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 12:55:26 +01:00

Merge pull request #7460 from orklah/7453

handle two more cases of firstClassCallable
This commit is contained in:
orklah 2022-01-22 18:30:57 +01:00 committed by GitHub
commit f5a093d015
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 0 deletions

View File

@ -27,6 +27,7 @@ use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TEmptyMixed;
use Psalm\Type\Atomic\TFalse;
@ -188,6 +189,41 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
);
}
if ($stmt->isFirstClassCallable()) {
$return_type_candidate = null;
$method_name_type = $statements_analyzer->node_data->getType($stmt->name);
if ($method_name_type && $method_name_type->isSingleStringLiteral()) {
$method_identifier = new MethodIdentifier(
$fq_class_name,
strtolower($method_name_type->getSingleStringLiteral()->value)
);
//the call to methodExists will register that the method was called from somewhere
if ($codebase->methods->methodExists(
$method_identifier,
$context->calling_method_id,
null,
$statements_analyzer,
$statements_analyzer->getFilePath(),
true,
$context->insideUse()
)) {
$method_storage = $codebase->methods->getStorage($method_identifier);
$return_type_candidate = new Union([new TClosure(
'Closure',
$method_storage->params,
$method_storage->return_type,
$method_storage->pure
)]);
}
}
$statements_analyzer->node_data->setType($stmt, $return_type_candidate ?? Type::getClosure());
return;
}
ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),

View File

@ -225,6 +225,42 @@ class AtomicStaticCallAnalyzer
);
}
if ($stmt->isFirstClassCallable()) {
$return_type_candidate = null;
if (!$stmt->name instanceof PhpParser\Node\Identifier) {
$method_name_type = $statements_analyzer->node_data->getType($stmt->name);
if ($method_name_type && $method_name_type->isSingleStringLiteral()) {
$method_identifier = new MethodIdentifier(
$fq_class_name,
strtolower($method_name_type->getSingleStringLiteral()->value)
);
//the call to methodExists will register that the method was called from somewhere
if ($codebase->methods->methodExists(
$method_identifier,
$context->calling_method_id,
null,
$statements_analyzer,
$statements_analyzer->getFilePath(),
true,
$context->insideUse()
)) {
$method_storage = $codebase->methods->getStorage($method_identifier);
$return_type_candidate = new Union([new TClosure(
'Closure',
$method_storage->params,
$method_storage->return_type,
$method_storage->pure
)]);
}
}
}
$statements_analyzer->node_data->setType($stmt, $return_type_candidate ?? Type::getClosure());
return;
}
if (ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),

View File

@ -609,6 +609,27 @@ class ClosureTest extends TestCase
[],
'8.1'
],
'FirstClassCallable:InstanceMethod:Expr' => [
'<?php
class Test {
public function __construct(private readonly string $string) {
}
public function length(): int {
return strlen($this->string);
}
}
$test = new Test("test");
$method_name = "length";
$closure = $test->$method_name(...);
$length = $closure();
',
'assertions' => [
'$length' => 'int',
],
[],
'8.1'
],
'FirstClassCallable:InstanceMethod:BuiltIn' => [
'<?php
$queue = new \SplQueue;
@ -637,6 +658,23 @@ class ClosureTest extends TestCase
[],
'8.1'
],
'FirstClassCallable:StaticMethod:Expr' => [
'<?php
class Test {
public static function length(string $param): int {
return strlen($param);
}
}
$method_name = "length";
$closure = Test::$method_name(...);
$length = $closure("test");
',
'assertions' => [
'$length' => 'int',
],
[],
'8.1'
],
'FirstClassCallable:InvokableObject' => [
'<?php
class Test {