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

Union type in phpdoc passes and gets an error at the same time #11101

Open
pkly opened this issue Sep 23, 2024 · 1 comment
Open

Union type in phpdoc passes and gets an error at the same time #11101

pkly opened this issue Sep 23, 2024 · 1 comment

Comments

@pkly
Copy link

pkly commented Sep 23, 2024

After upgrading from 5.24 I saw a new issue in known-good code, related to incorrect callback arguments when using symfony cacke, as psalm seems to get slightly confused. I think I've managed to reproduce the minimal issue with the following snippet, but in case you're curious the full(er) snippet is also available below.

https://psalm.dev/r/8276870767

// The Symfony Cache definition
namespace Symfony\Contracts\Cache;

use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;

interface CacheInterface
{
    /**
     * Fetches a value from the pool or computes it if not found.
     *
     * On cache misses, a callback is called that should return the missing value.
     * This callback is given a PSR-6 CacheItemInterface instance corresponding to the
     * requested key, that could be used e.g. for expiration control. It could also
     * be an ItemInterface instance when its additional features are needed.
     *
     * @template T
     *
     * @param string $key The key of the item to retrieve from the cache
     * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface<T> $callback
     * @param float|null $beta      A float that, as it grows, controls the likeliness of triggering
     *                              early expiration. 0 disables it, INF forces immediate expiration.
     *                              The default (or providing null) is implementation dependent but should
     *                              typically be 1.0, which should provide optimal stampede protection.
     *                              See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
     * @param array      &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()}
     *
     * @return T
     *
     * @throws InvalidArgumentException When $key is not valid or when $beta is negative
     */
    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null): mixed;
}

our code

        return $this->cache->get(
            'some-cache-name',
            function (Symfony\Contracts\Cache\ItemInterface $item) use ($foo) {
                $item->expiresAfter(60 * 60 * 24)
                    ->tag($foo->getCacheTags());

                // do stuff and return
            }
        );

Psalm errors out here with:

ERROR: MismatchingDocblockParamType - file.php:21:23 - Parameter $item has wrong type 'Psr\Cache\CacheItemInterface', should be 'Symfony\Contracts\Cache\ItemInterface' (see https://psalm.dev/141)
            function (ItemInterface $item) use ($foo) {


ERROR: UndefinedInterfaceMethod - file.php:23:23 - Method Psr\Cache\CacheItemInterface::tag does not exist (see https://psalm.dev/181)
                    ->tag($foo->getCacheTags());

It appears psalm gets confused and compares the definition to both declarations, instead of simply selecting the "best" one and bailing out.

Copy link

I found these snippets:

https://psalm.dev/r/8276870767
<?php

class Foo {}

class Bar {}

/**
  * @template T
  *
  * @param (callable(Foo, bool):T)|(callable(Bar, bool):T) $callback
  */
function something(callable $callback): void {
    $callback(random_int(0, 1) ? new Foo() : new Bar(), true);
}
Psalm output (using commit 16b24bd):

ERROR: PossiblyInvalidArgument - 13:15 - Argument 1 expects Bar, but possibly different type Bar|Foo provided

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

1 participant