psalm: Psalm cannot infer return types of higher level functions

Given following example:

<?php

class HelloWorld
{
    public function sayHello() : void
    {
        echo 'Hello World!';
    }
}

/**
 * @psalm-template InstanceType as object
 * @psalm-param class-string<InstanceType> $className
 * @psalm-return InstanceType
 */
function factory(string $className) : object
{
    return new $className;
}

/**
 * @psalm-template InputType of string
 * @psalm-template OutputType
 * @psalm-param callable(InputType) : OutputType $factory
 * @psalm-param InputType $input
 * @psalm-return OutputType
 */
function makeThroughFactory(callable $factory, string $input)
{
    return $factory($input);
}

makeThroughFactory('factory', HelloWorld::class)->sayHello();

Psalm reports (https://psalm.dev/r/411d7d36b9):

Psalm output (using commit e32b92b):

INFO: MixedMethodCall - 33:51 - Cannot determine the type of the object on the left hand side of this expression

I realise that this is extremely complex, but ideally I’d expect the return type of makeThroughFactory() (at the call site) to be inferred from the return type of factory (InstanceType), which is coupled to the input (InputType of string).

In practice, this allows chaining functions through pipe-alike structures. In following example, assumping compose() to be the function composition operation:

$a = function (T1 $v) : T2 {}
$b = function (T2 $v) : T3 {}
$b = function (T3 $v) : T4 {}

$composed = compose($a, compose($b, compose($c)));

Then $composed should be inferred as function (T1 $v) : T4 {}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 20 (7 by maintainers)

Most upvoted comments

Also the suppressions in the first example won’t be required once https://github.com/vimeo/psalm/issues/2896 is fixed (cc @ragboyjr)