pass by-value only to native filter paths
This commit is contained in:
parent
d013a84f90
commit
b2550cf80b
@ -22,6 +22,7 @@ namespace Kadet\Functional\Collections;
|
||||
|
||||
use Kadet\Functional\Collections\Lazy as lazy;
|
||||
use function Kadet\Functional\Internal\callback;
|
||||
use function Kadet\Functional\Internal\takesByReference;
|
||||
use function Kadet\Functional\Collections\Internal\entries;
|
||||
use function Kadet\Functional\Collections\Internal\iterated;
|
||||
use function Kadet\Functional\Collections\Internal\keyedLike;
|
||||
@ -101,14 +102,18 @@ function filter(?callable $fn = null): \Closure
|
||||
{
|
||||
$fn = $fn === null ? static fn ($value) => $value : callback($fn);
|
||||
|
||||
return static function (iterable $input) use ($fn): iterable {
|
||||
// array_filter() passes values, not variables, so a callback taking a parameter by reference would warn on each
|
||||
// call. Through this wrapper it gets variables; as before, its changes don't reach the result.
|
||||
$native = takesByReference($fn) ? static fn ($value, $key) => $fn($value, $key) : $fn;
|
||||
|
||||
return static function (iterable $input) use ($fn, $native): iterable {
|
||||
if ($input instanceof \Ds\Map) {
|
||||
return $input->filter(static fn ($key, $value) => $fn($value, $key));
|
||||
}
|
||||
|
||||
$values = iterated($input);
|
||||
if (is_array($values)) {
|
||||
$values = array_filter($values, $fn, ARRAY_FILTER_USE_BOTH);
|
||||
$values = array_filter($values, $native, ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
return is_array($input) ? $values : listLike($input, $values);
|
||||
}
|
||||
|
||||
@ -64,17 +64,13 @@ function callback(callable $callable, int $arity = 1): \Closure
|
||||
$leadingOnly = $target->isInternal()
|
||||
|| (!$target->isVariadic() && $target->getNumberOfParameters() === $arity);
|
||||
|
||||
// Internal functions like array_filter() pass values, not variables, so a closure taking a parameter by reference
|
||||
// would warn on each call. Called from a wrapper, it gets the wrapper's variables instead.
|
||||
$reflection = new \ReflectionFunction($compiled);
|
||||
$byReference = takesByReference($reflection);
|
||||
|
||||
if (!$leadingOnly) {
|
||||
return $byReference ? static fn (...$args) => $compiled(...$args) : $compiled;
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
// A userland closure taking no more than the leading arguments already ignores the rest.
|
||||
if (!$byReference && !$reflection->isInternal() && !$reflection->isVariadic() && $reflection->getNumberOfParameters() <= $arity) {
|
||||
$reflection = new \ReflectionFunction($compiled);
|
||||
if (!$reflection->isInternal() && !$reflection->isVariadic() && $reflection->getNumberOfParameters() <= $arity) {
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
@ -88,9 +84,9 @@ function callback(callable $callable, int $arity = 1): \Closure
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function takesByReference(\ReflectionFunction $reflection): bool
|
||||
function takesByReference(\Closure $closure): bool
|
||||
{
|
||||
foreach ($reflection->getParameters() as $parameter) {
|
||||
foreach ((new \ReflectionFunction($closure))->getParameters() as $parameter) {
|
||||
if ($parameter->isPassedByReference()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -130,6 +130,22 @@ class TransformsTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertSame([1 => 2, 2 => 3], $both);
|
||||
}
|
||||
|
||||
public function testByReferenceCallbacksWriteBackOutsideNativeFilter()
|
||||
{
|
||||
// Where the callback is called with a variable, its changes to the value are kept, as in 1.0.0.
|
||||
$trim = function (&$v) {
|
||||
$v = trim($v);
|
||||
|
||||
return $v !== '';
|
||||
};
|
||||
$gen = fn () => self::gen([' a ', ' ', 'b ']);
|
||||
|
||||
$this->assertSame(['a', 'b'], iterator_to_array(\Kadet\Functional\Collections\Lazy\filter($trim)($gen()), false));
|
||||
$this->assertSame([1 => ''], c\reject($trim)([' a ', ' ', 'b ']));
|
||||
$this->assertSame('x', c\find($trim)(new Seq([' x '])));
|
||||
$this->assertSame('a', c\first($trim)($gen()));
|
||||
}
|
||||
|
||||
public function testFilterWithInternalPredicate()
|
||||
{
|
||||
$this->assertSame([1 => 'a'], c\filter(is_string(...))([1, 'a']));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user