Search code examples
static-analysisphpstan

PHPStan Extension: Dynamic Return Types and a Variadic Function Parameter


Since PHPStan 1.6, it's possible to use Conditional Return Types, where I've been able to do things like:

/**
 * @param string $x
 * @return ($x is literal-string ? literal-string : string)
 */
public function isNull($x)
{
}

This takes the form of (<template param> is <union type> ? <union type> : <union type>).

While it's not possible to do more complicated conditions, it is possible to nest them (even if it gets a bit messy):

/**
 * @param string $val
 * @param string $x
 * @param string $y
 * @return ($val is literal-string ? ($x is literal-string ? ($y is literal-string ? literal-string : string) : string) : string)
 */
public function between($val, $x, $y)
{
}

But I'm not sure how to handle a Variadic Function Parameter, where the function can accept any number of values.

I'd like to return a literal-string when all values are a literal-string, otherwise return a string.

Maybe something like the following (which does not work):

/**
 * @param string ...$x
 * @return ($x is array<literal-string> ? literal-string : string)
 */
function example(...$x) {
    return implode(', ', $x);
}

Is this a limitation of the current implementation in PHPStan, or am I missing something?

This relates to the PHPStan Doctrine Extension, and Pull Request 324.

One option is to use a Dynamic Return Type Extension (which I might revert).


Solution

  • The solution is to use a @template, e.g.

    /**
     * @template T of string
     * @param T ...$x
     * @return (T is literal-string ? literal-string : string)
     */
    public function countDistinct(...$x) {
    }
    

    But this does not work with Doctrine ORM 2.x, because it uses ($x) not (...$x) for its arguments (source), which PR 9911 fixes for 3.x.