diff --git a/src/Psalm/Checker/ClassChecker.php b/src/Psalm/Checker/ClassChecker.php index 46a45aafb..3f5798780 100644 --- a/src/Psalm/Checker/ClassChecker.php +++ b/src/Psalm/Checker/ClassChecker.php @@ -161,6 +161,10 @@ class ClassChecker extends ClassLikeChecker { $interface_id = strtolower($interface); + if ($interface_id === 'callable' && $absolute_class === 'Closure') { + return true; + } + if (isset(self::$class_implements[$absolute_class][$interface_id])) { return true; } diff --git a/src/Psalm/Checker/FunctionChecker.php b/src/Psalm/Checker/FunctionChecker.php index d834c3633..cc7572b38 100644 --- a/src/Psalm/Checker/FunctionChecker.php +++ b/src/Psalm/Checker/FunctionChecker.php @@ -302,7 +302,8 @@ class FunctionChecker extends FunctionLikeChecker $function_call_arg = $call_args[$function_index]; if ($function_call_arg->value instanceof PhpParser\Node\Expr\Closure) { - $closure_return_types = \Psalm\EffectsAnalyser::getReturnTypes($function_call_arg->value->stmts, true); + $closure_yield_types = []; + $closure_return_types = \Psalm\EffectsAnalyser::getReturnTypes($function_call_arg->value->stmts, $closure_yield_types, true); if (!$closure_return_types) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index e90591489..6f154f8b9 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -1116,6 +1116,8 @@ class StatementsChecker $closure_checker->check($use_context, $context->check_methods); + $stmt->inferredType = Type::getClosure(); + } elseif ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) { if ($this->checkArrayAccess($stmt, $context, $array_assignment, $assignment_key_type, $assignment_value_type, $assignment_key_value) === false) { return false; diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 984858ba3..e1beef86d 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -254,6 +254,14 @@ abstract class Type return new Union([$type]); } + /** @return Type\Union */ + public static function getClosure() + { + $type = new Atomic('Closure'); + + return new Union([$type]); + } + /** @return Type\Union */ public static function getArray() { diff --git a/tests/Php70Test.php b/tests/Php70Test.php index 5b4980c25..de1a47ef4 100644 --- a/tests/Php70Test.php +++ b/tests/Php70Test.php @@ -153,10 +153,10 @@ class Php70Test extends PHPUnit_Framework_TestCase public function testGeneratorWithReturn() { - $this->markTestIncomplete('Not yet supported'); $stmts = self::$_parser->parse(' + * @return Generator + * @psalm-generator-return string */ function foo(int $i) : Generator { if ($i === 1) { @@ -170,31 +170,6 @@ class Php70Test extends PHPUnit_Framework_TestCase $file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts); $context = new Context('somefile.php'); $file_checker->check(true, true, $context); - $this->assertEquals('array', (string) $context->vars_in_scope['$gen']); - $this->assertEquals('int', (string) $context->vars_in_scope['$gen2']); - } - - public function testClosureCall() - { - $this->markTestIncomplete('Not yet supported'); - $stmts = self::$_parser->parse('x;}; - $getX = $getXCB->bindTo(new A, "A"); // intermediate closure - $a = $getX(); - - // PHP 7+ code - $getX = function() {return $this->x;}; - $b = $getX->call(new A); - '); - - $file_checker = new \Psalm\Checker\FileChecker('somefile.php', $stmts); - $context = new Context('somefile.php'); - $file_checker->check(true, true, $context); - $this->assertEquals('mixed', (string) $context->vars_in_scope['$a']); - $this->assertEquals('mixed', (string) $context->vars_in_scope['$b']); } public function testGeneratorDelegation() @@ -202,6 +177,7 @@ class Php70Test extends PHPUnit_Framework_TestCase $stmts = self::$_parser->parse(' + * @psalm-generator-return int */ function count_to_ten() : Generator { yield 1; @@ -229,6 +205,7 @@ class Php70Test extends PHPUnit_Framework_TestCase /** * @return Generator + * @psalm-generator-return int */ function nine_ten() : Generator { yield 9;