drop extra arguments for compiled internal functions

This commit is contained in:
Kacper Donat 2026-10-27 22:08:37 +01:00
parent 1e94adc872
commit bdb49b0d78
3 changed files with 32 additions and 3 deletions

View File

@ -66,17 +66,26 @@ class PartiallyAppliedFunction implements Decorator, Compilable
return $this->callable; return $this->callable;
} }
/**
* Internal functions throw when given too many arguments, so for a non-variadic internal target the compiled
* closure drops trailing arguments past its parameter count (e.g. the key passed by collection functions).
*/
public function compile(): \Closure public function compile(): \Closure
{ {
$callable = compile($this->callable); $callable = compile($this->callable);
$arguments = $this->arguments; $arguments = $this->arguments;
$placeholders = array_keys($arguments, PLACEHOLDER, true); $placeholders = array_keys($arguments, PLACEHOLDER, true);
$target = new \ReflectionFunction($callable);
$extra = $target->isInternal() && !$target->isVariadic()
? max(0, $target->getNumberOfParameters() - count($arguments))
: null;
if (empty($placeholders)) { if (empty($placeholders)) {
return static fn (...$args) => $callable(...$arguments, ...array_values($args)); return static fn (...$args) => $callable(...$arguments, ...array_slice(array_values($args), 0, $extra));
} }
return static function (...$args) use ($callable, $arguments, $placeholders) { return static function (...$args) use ($callable, $arguments, $placeholders, $extra) {
$args = array_values($args); $args = array_values($args);
$count = min(count($placeholders), count($args)); $count = min(count($placeholders), count($args));
@ -84,7 +93,7 @@ class PartiallyAppliedFunction implements Decorator, Compilable
$arguments[$placeholders[$i]] = $args[$i]; $arguments[$placeholders[$i]] = $args[$i];
} }
return $callable(...$arguments, ...array_slice($args, $count)); return $callable(...$arguments, ...array_slice($args, $count, $extra));
}; };
} }

View File

@ -38,6 +38,11 @@ class CallbackTest extends \PHPUnit\Framework\TestCase
$this->assertSame(['v', 'k'], callback(fn (...$args) => $args)('v', 'k')); $this->assertSame(['v', 'k'], callback(fn (...$args) => $args)('v', 'k'));
} }
public function testPartialOfInternalFunctionDoesNotGetKey()
{
$this->assertSame('a', callback(f\partial(trim(...), _, 'x'))('xax', 'key'));
}
public function testDecoratorIsCompiled() public function testDecoratorIsCompiled()
{ {
$callback = callback(f\partial(fn ($prefix, $v, $k) => "$prefix$k=$v", '>')); $callback = callback(f\partial(fn ($prefix, $v, $k) => "$prefix$k=$v", '>'));

View File

@ -88,6 +88,21 @@ class CompileTest extends \PHPUnit\Framework\TestCase
$this->assertSame($partial(), f\compile($partial)()); $this->assertSame($partial(), f\compile($partial)());
} }
public function testCompileDropsExtraArgumentsForInternalTarget()
{
$this->assertSame('aa', f\compile(f\partial(str_repeat(...), _, 2))('a', 'extra'));
}
public function testCompileKeepsExtraArgumentsForVariadicInternalTarget()
{
$this->assertSame(['a', 'b', 'c'], f\compile(f\partial(array_merge(...), ['a']))(['b'], ['c']));
}
public function testCompileDropsExtraArgumentsForCurriedInternalTarget()
{
$this->assertSame('aa', f\compile(f\curry(str_repeat(...), null, 'a'))(2, 'extra'));
}
public function testCompileFunctionDelegatesToMethod() public function testCompileFunctionDelegatesToMethod()
{ {
$partial = f\partial(str_replace(...), 'a', _, 'banana'); $partial = f\partial(str_replace(...), 'a', _, 'banana');