Skip to content

Allow passing methods with arguments to extensionMethod#354

Open
jtojnar wants to merge 1 commit into
nette:masterfrom
jtojnar:extension-methods
Open

Allow passing methods with arguments to extensionMethod#354
jtojnar wants to merge 1 commit into
nette:masterfrom
jtojnar:extension-methods

Conversation

@jtojnar

@jtojnar jtojnar commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Since 01863f2 introduced callable(static): mixed type hint for the extensionMethod parameter, it is no longer possible to pass functions with more than one argument to it.

This is because argument of type callable(static): mixed only has a limited number of subtypes: functions that can be called with the static argument, i.e. those functions expecting any superclass of static. The functions can also narrow down as they want since the return type since mixed is supertype of every type. The functions cannot have any extra arguments since the container would not know what to pass to them. But having fewer arguments is fine since PHP will ignore any extra arguments passed to a function.

Formally, the subtyping relation of callables must respect the variance rules.

If we want to allow wider range of subtypes, we must choose a suitably wide supertype in the type lattice. One option would be just using mixed or callable but those do not really guide user very well. We want to include at least the static argument since that is always passed by __call.

One option would be using a bottom type (never) as the parameter type since it is a subtype of any type – the function with never argument can never be called so substituting it with a function that accepts a supertype cannot break any callers, satisfying the contravariance rule. For the return type, we can use a top type (mixed) since callers expecting mixed must be able to deal with narrower types so functions returning a subtype cannot break anything either, thus covariance of return type. The main limitation is that we can only have as many arguments as we specify in the type signature as mentioned above. I chose 9 extra arguments since that should be enough for most uses.

Though, since the function with never argument cannot be called because there are no values that can inhabit never type, and thus there is nothing to be passed to arguments, we need to cast down the arguments in __call to never types. This is a valid operation since never is a subtype of any type but it is a bit weird.

  • bug fix
  • BC break? no
  • doc PR: N/A

Since nette@01863f2
introduced `callable(static): mixed` type hint for the `extensionMethod` parameter,
it is no longer possible to pass functions with more than one argument to it.

This is because argument of type `callable(static): mixed` only has a limited
number of subtypes: functions that can be called with the `static` argument,
i.e. those functions expecting any superclass of `static`.
The functions can also narrow down as they want since the return type
since `mixed` is supertype of every type.
The functions cannot have any extra arguments since the container
would not know what to pass to them. But having fewer arguments is fine
since PHP will ignore any extra arguments passed to a function.

Formally, the subtyping relation of callables must respect
the variance rules:
https://www.php.net/manual/en/language.oop5.variance.php

If we want to allow wider range of subtypes, we must choose a suitably
wide supertype in the type lattice. One option would be just using `mixed`
or `callable` but those do not really guide user very well.
We want to include at least the `static` argument since that
is always passed by `__call`.

One option would be using a _bottom_ type (`never`) as the parameter type
since it is a subtype of any type – the function with `never` argument
can never be called so substituting it with a function that accepts
a supertype cannot break any callers, satisfying the contravariance
rule. For the return type, we can use a _top_ type (`mixed`) since
callers expecting mixed must be able to deal with narrower types
so functions returning a subtype cannot break anything either,
thus covariance of return type.
The main limitation is that we can only have as many arguments
as we specify in the type signature as mentioned above.
I chose 9 extra arguments since that should be enough for most uses.

https://phpstan.org/writing-php-code/phpdoc-types#bottom-type

Though, since the function with `never` argument cannot be called
because there are no values that can inhabit `never` type, and thus
there is nothing to be passed to arguments, we need to cast down the
arguments in `__call` to `never` types. This is a valid operation
since `never` is a subtype of any type but it is a bit weird.

https://phpstan.org/writing-php-code/phpdocs-basics#inline-%40var
@JanTvrdik

Copy link
Copy Markdown
Contributor

Have you tried callable(static, ...never): mixed instead?

@jtojnar

jtojnar commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Oh, good idea. Unfortunately, that does not seem to work with PHPStan 2.2.5:

Parameter #2 $callback of static method Nette\Forms\Container::extensionMethod() expects callable(static, never ...): mixed,     
         Closure(Nette\Forms\Container, string, callable, int=, bool=): static given.

https://phpstan.org/r/86e4483a-24eb-41e2-afc8-ed3c5c5ba899

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

Successfully merging this pull request may close these issues.

2 participants