Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional return on interface template doesn't work #4511

Closed
thomasvargiu opened this issue Nov 8, 2020 · 7 comments
Closed

Conditional return on interface template doesn't work #4511

thomasvargiu opened this issue Nov 8, 2020 · 7 comments

Comments

@thomasvargiu
Copy link
Contributor

Using a conditional return, behaviour is different when defined in interface or in a class.

With class docblock:
https://psalm.dev/r/3cc2367e01

Using interface docblock:
https://psalm.dev/r/4484822d18

I would expect that conditional return works when defined in interfaces too.

Another thing I noticed is that (T is callable ? something : else) doesn't work.

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/3cc2367e01
<?php

/**
 * @template T
 */
interface A
{
    /**
     * @template V
     * @psalm-param V $value
     * @psalm-return (T is Closure ? A<V> : never-return)
     */
    public function test($value): A;
}

/**
 * @template T
 * @template-implements A<T>
 */
class B implements A
{
    /** @var T */
    private $value;
    /** @param T $value */
    public function __construct($value)
    {
        $this->value = $value;
    }
    
    /**
     * @template V
     * @psalm-param V $value
     * @psalm-return (T is Closure ? B<V> : never-return)
     */
    public function test($value): A
    {
        return new B($value);
    }
}

$bs = new B('foo');
$bf = new B(fn (string $a): int => strlen($a));

// this should return B<string>
/** @psalm-trace $result1 */
$result1 = $bf->test('foo');

// this should never-return
/** @psalm-trace $result2 */
$result2 = $bs->test('foo');
Psalm output (using commit 24c9702):

INFO: Trace - 46:1 - $result1: B<string(foo)>

ERROR: NoValue - 50:1 - This function or method call never returns output
https://psalm.dev/r/4484822d18
<?php

/**
 * @template T
 */
interface A
{
    /**
     * @template V
     * @psalm-param V $value
     * @psalm-return (T is Closure ? A<V> : never-return)
     */
    public function test($value): A;
}

/**
 * @template T
 * @template-implements A<T>
 */
class B implements A
{
    /** @var T */
    private $value;
    /** @param T $value */
    public function __construct($value)
    {
        $this->value = $value;
    }
    
    public function test($value): A
    {
        return new B($value);
    }
}

$bs = new B('foo');
$bf = new B(fn (string $a): int => strlen($a));

// this should return B<string>
/** @psalm-trace $result1 */
$result1 = $bf->test('foo');

// this should never-return
/** @psalm-trace $result2 */
$result2 = $bs->test('foo');
Psalm output (using commit 24c9702):

ERROR: NoValue - 41:1 - This function or method call never returns output

@thomasvargiu
Copy link
Contributor Author

Probably related to #4524

@muglug
Copy link
Collaborator

muglug commented Nov 11, 2020

Do you have an example of it breaking without never-return?

@muglug
Copy link
Collaborator

muglug commented Nov 11, 2020

When both a docblock return type and a signature return type are given, Psalm performs a trivial check to see whether the docblock is more specific than the signature. If that check passes, the docblock is inheritable. Here that check isn't passing, so no inheritance of the docblock occurs (the implementing function specifies its own return type A.

Removing A from the signature allows this behaviour: https://psalm.dev/r/68e87c0681

@muglug muglug closed this as completed Nov 11, 2020
@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/68e87c0681
<?php

/**
 * @template T
 */
interface A
{
    /**
     * @template V
     * @psalm-param V $value
     * @psalm-return (T is Closure ? A<V> : never-return)
     */
    public function test($value);
}

/**
 * @template T
 * @template-implements A<T>
 */
class B implements A
{
    /** @var T */
    private $value;
    /** @param T $value */
    public function __construct($value)
    {
        $this->value = $value;
    }
    
    public function test($value)
    {
        return new B($value);
    }
}

$bs = new B('foo');
$bf = new B(fn (string $a): int => strlen($a));

// this should never-return
/** @psalm-trace $result2 */
$result2 = $bs->test('foo');
Psalm output (using commit a8d7248):

ERROR: NoValue - 41:1 - This function or method call never returns output

@muglug
Copy link
Collaborator

muglug commented Nov 11, 2020

never-return wasn't intended for conditional return types, use them there at your own risk

@muglug
Copy link
Collaborator

muglug commented Nov 11, 2020

Here's the trivial check if you want to add support for conditional return types, but it wouldn't be a simple change:

public static function isSimplyContainedBy(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants