Skip to content

Commit

Permalink
Merge pull request #7460 from orklah/7453
Browse files Browse the repository at this point in the history
handle two more cases of firstClassCallable
  • Loading branch information
orklah committed Jan 22, 2022
2 parents 4912139 + 73af019 commit f5a093d
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 0 deletions.
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -188,6 +189,41 @@ public static function analyze(
);
}

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(),
Expand Down
Expand Up @@ -225,6 +225,42 @@ public static function analyze(
);
}

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(),
Expand Down
38 changes: 38 additions & 0 deletions tests/ClosureTest.php
Expand Up @@ -609,6 +609,27 @@ public function length(): int {
[],
'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;
Expand Down Expand Up @@ -637,6 +658,23 @@ public static function length(string $param): int {
[],
'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 {
Expand Down

0 comments on commit f5a093d

Please sign in to comment.