Repository: phpactor/phpactor (php)

Issue:
Recognize types returned by `__invoke()`
![image](https://user-images.githubusercontent.com/11404453/167432853-6c33f6f3-429e-4e46-bfd9-7946e6f7b706.png)
![image](https://user-images.githubusercontent.com/11404453/167433077-83215865-7487-4bc6-afb8-6261c312943d.png)


Pull request:
Gh 1562 datetime
Fix #1562 

Patch:
diff --git a/lib/WorseReflection/Core/Inference/Resolver/CallExpressionResolver.php b/lib/WorseReflection/Core/Inference/Resolver/CallExpressionResolver.php
index 2272f4ac..2d320b4b 100644
--- a/lib/WorseReflection/Core/Inference/Resolver/CallExpressionResolver.php
+++ b/lib/WorseReflection/Core/Inference/Resolver/CallExpressionResolver.php
@@ -11,6 +11,7 @@ use Phpactor\WorseReflection\Core\Inference\NodeContextFactory;
 use Phpactor\WorseReflection\Core\Inference\Resolver;
 use Phpactor\WorseReflection\Core\Inference\NodeContextResolver;
 use Phpactor\WorseReflection\Core\Type\CallableType;
+use Phpactor\WorseReflection\Core\Type\ReflectedClassType;
 use Phpactor\WorseReflection\Core\Util\NodeUtil;
 
 class CallExpressionResolver implements Resolver
@@ -20,24 +21,26 @@ class CallExpressionResolver implements Resolver
         assert($node instanceof CallExpression);
         $resolvableNode = $node->callableExpression;
 
-        if ($resolvableNode instanceof Variable) {
-            return $this->reosolveCallable($resolver, $frame, $resolvableNode);
+        $context = $resolver->resolveNode($frame, $resolvableNode);
+        $type = $context->type();
+
+        if ($type instanceof ReflectedClassType && $type->isInvokable()) {
+            return NodeContextFactory::forNode($node)
+                ->withType($type->invokeType());
         }
 
-        return $resolver->resolveNode($frame, $resolvableNode);
-    }
+        if (!$resolvableNode instanceof Variable) {
+            return $context;
+        }
 
-    private function reosolveCallable(NodeContextResolver $resolver, Frame $frame, Variable $variable): NodeContext
-    {
-        $type = $resolver->resolveNode($frame, $variable)->type();
         if (!$type instanceof CallableType) {
             return NodeContext::none();
         }
 
         return NodeContextFactory::create(
-            NodeUtil::nameFromTokenOrNode($variable, $variable->name),
-            $variable->getStartPosition(),
-            $variable->getEndPosition(),
+            NodeUtil::nameFromTokenOrNode($resolvableNode, $resolvableNode->name),
+            $resolvableNode->getStartPosition(),
+            $resolvableNode->getEndPosition(),
             [
                 'type' => $type->returnType,
             ]
diff --git a/lib/WorseReflection/Core/Type/ReflectedClassType.php b/lib/WorseReflection/Core/Type/ReflectedClassType.php
index b5093046..a35aa374 100644
--- a/lib/WorseReflection/Core/Type/ReflectedClassType.php
+++ b/lib/WorseReflection/Core/Type/ReflectedClassType.php
@@ -10,6 +10,7 @@ use Phpactor\WorseReflection\Core\Reflection\ReflectionInterface;
 use Phpactor\WorseReflection\Core\Reflector\ClassReflector;
 use Phpactor\WorseReflection\Core\Trinary;
 use Phpactor\WorseReflection\Core\Type;
+use Phpactor\WorseReflection\Core\TypeFactory;
 use Phpactor\WorseReflection\Core\Type\Resolver\IterableTypeResolver;
 
 class ReflectedClassType extends ClassType
@@ -34,6 +35,16 @@ class ReflectedClassType extends ClassType
         return $this->__toString();
     }
 
+    public function isInvokable(): bool
+    {
+        $reflection = $this->reflectionOrNull();
+        if (null === $reflection) {
+            return false;
+        }
+
+        return $reflection->methods()->has('__invoke');
+    }
+
     /**
      * Accept if same class or class extends this class
      */
@@ -162,4 +173,18 @@ class ReflectedClassType extends ClassType
 
         return new GenericClassType($this->reflector, $this->name(), $reflection->templateMap()->toArray());
     }
+
+    public function invokeType(): Type
+    {
+        $reflection = $this->reflectionOrNull();
+        if (null === $reflection) {
+            return TypeFactory::undefined();
+        }
+
+        try {
+            return $reflection->methods()->get('__invoke')->inferredType();
+        } catch (NotFound $notFound) {
+            return TypeFactory::undefined();
+        }
+    }
 }


Test patch:
diff --git a/lib/WorseReflection/Tests/Inference/call-expression/invoke.test b/lib/WorseReflection/Tests/Inference/call-expression/invoke.test
new file mode 100644
index 00000000..5b70fc60
--- /dev/null
+++ b/lib/WorseReflection/Tests/Inference/call-expression/invoke.test
@@ -0,0 +1,13 @@
+<?php
+
+class Foobar 
+{
+    public function __invoke(): string
+    {
+        return 'hi';
+    }
+}
+
+$f = new Foobar();
+wrAssertType('string', $f());
+
diff --git a/lib/WorseReflection/Tests/Inference/call-expression/invoke2.test b/lib/WorseReflection/Tests/Inference/call-expression/invoke2.test
new file mode 100644
index 00000000..be90d3e9
--- /dev/null
+++ b/lib/WorseReflection/Tests/Inference/call-expression/invoke2.test
@@ -0,0 +1,30 @@
+<?php
+
+class Foobar 
+{
+    /**
+     * @return Generator<DateTime>
+     */
+    public function __invoke(): Generator
+    {
+        yield new DateTime();
+    }
+}
+
+class Baz
+{
+    private Foobar $foo;
+
+    public function __construct(Foobar $foo) {
+        $this->foo = $foo;
+    }
+
+    public function baz(): void {
+        $f = $this->foo;
+
+        foreach (($this->foo)() as $bar) {
+            wrAssertType('DateTime', $bar);
+        }
+    }
+}
+