vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.54k stars 660 forks source link

templates + `\Closure::fromCallable()` / first class callables bug #10171

Open someniatko opened 1 year ago

someniatko commented 1 year ago

https://psalm.dev/r/6bd1876d5a

the error is reported both for the first-class callable and \Closure::fromCallable() approaches (which it seems is under-the-hood for Psalm's handling of the first-class callables). Something breaks here about the input type inference.

But there is no error, as expected, when wiring the input argument manually.

psalm-github-bot[bot] commented 1 year ago

I found these snippets:

https://psalm.dev/r/6bd1876d5a ```php */ public function map(callable $c) { return new Option($c($this->value)); } } /** @template T */ abstract class Command {} /** @template-extends Command */ final class CommandReturningString extends Command {} /** @return Option */ function maybeReturnsCommand(): Option { return new Option(new CommandReturningString()); } interface CommandBusInterface { /** * @template T * @param Command $command * @return T */ public function send(Command $command); } final class Client { public function __construct(private CommandBusInterface $commandBus) {} public function usingFirstClassCallable(): void { maybeReturnsCommand() ->map($this->commandBus->send(...)); } public function usingClosureFromCallable(): void { maybeReturnsCommand() ->map(\Closure::fromCallable([ $this->commandBus, 'send' ])); } public function usingRegularCallable(): void { maybeReturnsCommand() ->map(fn (CommandReturningString $c): mixed => $this->commandBus->send($c)); } } ``` ``` Psalm output (using commit 96d8394): ERROR: InvalidArgument - 48:19 - Argument 1 of Option::map expects callable(CommandReturningString):mixed, but impure-Closure(Command):T:fn-commandbusinterface::send as mixed provided ERROR: InvalidArgument - 53:19 - Argument 1 of Option::map expects callable(CommandReturningString):mixed, but impure-Closure(Command):T:fn-commandbusinterface::send as mixed provided ```
weirdan commented 1 year ago

We need a way to represent a generic callable (the type of send(...) is generic until it's supplied a parameter), and Psalm currently can't do that.

someniatko commented 1 year ago

That makes sense, thank you