Repository navigation
callable parameter with any number of arguments #8214
Description
Activity
Another idea for the syntax:
callable(function-parameters<doFoo>): void callable(method-parameters<T, doFoo>): void callable(constructor-parameters<T>): voidAlso we'd need
return-typepseudotype for the same reason.Related: vimeo/psalm#8716
@ondrejmirtes Sorry for the duplicate #8581, did not find the duplicate under 'Closure' term :(
But, when the closure is desired to be dynamic then what would the syntax look like? I mean when used with methods like Laravel's
container->callfunction that calls the closure with resolved parameters? Thenmixed...is then desired, right?We should always be able to provide a precise signature for the thing that is called. Feel free to show an example on phpstan.org with expected behaviour.
If you don't care about the signature and want less type checking, you can always type just
callableorClosurewithout additional details.Thank you for the reply. I think that with dependency injection it is little bit different. This is an example from the issue .
A real code is here:
The solution as you are proposing is passing
Containerto the closure and let the consumer manually make classes from the container. This would cause slightly less attractive DX.If its not in aim of PHPStan to support
mixed...to help improve type static, then I understand your point of view.Thanks and happy holidays
@pionl After the latest push in 1.11.x, PHPStan now reports different result with your code snippet:
@@ @@ -45: Parameter #1 $createState of method ContextService::get() expects Closure(...mixed): Value, Closure(string): Value given. +45: Parameter #1 $createState of method ContextService::get() expects Closure(mixed ...): Value, Closure(string): Value given.
Full report
Line Error 45 Parameter #1 $createState of method ContextService::get() expects Closure(mixed ...): Value, Closure(string): Value given.Hi @ondrejmirtes any chance of rethinking this?
I do not need to check closure parameters, I just need the type of the return value :) The "hack" solution does not work on Level 9 :)
I've tried to do an extension / locate the source code where the types are handled but no success :(
@pionl After the latest push in 2.2.x, PHPStan now reports different result with your code snippet:
@@ @@ -45: Parameter #1 $createState of method ContextService::get() expects Closure(...mixed): Value, Closure(string): Value given. +38: Callable passed to call_user_func_array() invoked with 1 parameter, 6 required. +45: Parameter #1 $createState of method ContextService::get() expects Closure(mixed ...): Value, Closure(string): Value given.
Full report
Line Error 38 Callable passed to call_user_func_array() invoked with 1 parameter, 6 required.45 Parameter #1 $createState of method ContextService::get() expects Closure(mixed ...): Value, Closure(string): Value given.When working on framework/library related things, that want to expose nice API's to end-users, we often want to accept a callable that has variable types. Using reflection or other logic, we can then call the callable with some payload.
See this example below:
#[Attribute(flags: Attribute::TARGET_METHOD)] final readonly class Idempotency { /** * @param Closure(): ?IdempotencyKey $key */ public function __construct( public Closure $key, ) {} } final class SomeHandler { #[Idempotency( key: static function (SomeCommand $command, UserActor $actor) : IdempotencyKey { return new IdempotencyKey('some', $command->id, $actor->userId); }, )] public function __invoke(SomeCommand $command, UserActor $actor) : void { } }
The end-user can use the
#[Idempotency]attribute and pass a closure. The payload of the closure, is the exact same payload as the__invokemethod's signature. It just depends on what the end-user needs to calculate the Idempotency Key.It's currently impossible to type this situation in PHPStan. I tried using
Closure(mixed...)but that results in:Parameter $key of attribute class Idempotency constructor expects Closure(mixed ...): (IdempotencyKey|null), Closure(SomeCommand, UserActor): IdempotencyKey given.
callable(function-parameters<doFoo>): void callable(method-parameters<T, doFoo>): void callable(constructor-parameters<T>): voidWhich would be epic. I can already tell a few examples where I might be able to use that. But in situations with Attributes, I don't think that will work.
Reacted by Martin Kluska and Daniel Thoma
Feature request
From #6813
I recently tried to create something where this would have been quite useful: a higher-order function to take functions that throw exceptions, catch (and log) them, and then return a WP_Error object instead.
In this case, I was hoping that I could get around the issue of having variable numbers of params by hardcoding generics for a reasonable max number of parameters. My hope was that, that for Closures with less than 5 params, the unused
PXtemplate types would default tonever, andClosure(never): Rwould be considered identical toClosure(): R.Of course, it didn't actually work when I tried it, and I suspect that not even setting explicit defaults for the template types (once #1835 lands) would alter the behavior here.
But should it work? Is a parameter of type
neverthe same thing as no parameter at all? Or would that conceptually be more like avoidparameter, if such a thing even makes sense?It's also worth noting that TypeScript has a
Parametersutility type. Presumably, if PHPStan had something like this, the kind of static typing I'm looking for would be straightforward, using syntax that could look a little like this:What do you think of these ideas?
Originally posted by @ZebulanStanphill in #6813 (comment)