Skip to content

Commit a4d238f

Browse files
committed
Allow passing methods with arguments to extensionMethod
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: 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
1 parent 90aac9e commit a4d238f

2 files changed

Lines changed: 6 additions & 4 deletions

File tree

‎src/Forms/Container.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Container extends Nette\ComponentModel\Container implements \ArrayAccess
3636
public array $onValidate = [];
3737
protected ?ControlGroup $currentGroup = null;
3838

39-
/** @var array<string, callable(static): mixed> */
39+
/** @var array<string, callable(static, never, never, never, never, never, never, never, never, never): mixed> */
4040
private static array $extMethods = [];
4141
private ?bool $validated = false;
4242
private ?string $mappedType = null;
@@ -626,14 +626,15 @@ public function addContainer(string|int $name): self
626626
public function __call(string $name, array $args)
627627
{
628628
if (isset(self::$extMethods[$name])) {
629+
/** @var never[] $args */
629630
return (self::$extMethods[$name])($this, ...$args);
630631
}
631632

632633
Nette\Utils\ObjectHelpers::strictCall(static::class, $name);
633634
}
634635

635636

636-
/** @param callable(static): mixed $callback */
637+
/** @param callable(static, never, never, never, never, never, never, never, never, never): mixed $callback */
637638
public static function extensionMethod(string $name, callable $callback): void
638639
{
639640
if (str_contains($name, '::')) { // back compatibility

‎src/Forms/Controls/BaseControl.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ abstract class BaseControl extends Nette\ComponentModel\Component implements Con
5050
/** @var bool|bool[] */
5151
protected bool|array $disabled = false;
5252

53-
/** @var array<string, array<class-string, callable(static): mixed>> */
53+
/** @var array<string, array<class-string, callable(static, never, never, never, never, never, never, never, never, never): mixed>> */
5454
private static array $extMethods = [];
5555
private string|Stringable|null $caption;
5656

@@ -566,6 +566,7 @@ public function __call(string $name, array $args)
566566
$class = static::class;
567567
do {
568568
if (isset(self::$extMethods[$name][$class])) {
569+
/** @var never[] $args */
569570
return (self::$extMethods[$name][$class])($this, ...$args);
570571
}
571572

@@ -576,7 +577,7 @@ public function __call(string $name, array $args)
576577
}
577578

578579

579-
/** @param callable(static): mixed $callback */
580+
/** @param callable(static, never, never, never, never, never, never, never, never, never): mixed $callback */
580581
public static function extensionMethod(string $name, callable $callback): void
581582
{
582583
if (str_contains($name, '::')) { // back compatibility

0 commit comments

Comments
 (0)