Skip to content

Proposal: Add iterable\any(iterable $input, ?callable $cb=null), all(...), none(...), find(...), reduce(...) #6053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Prev Previous commit
Next Next commit
Add iterable\none(iterable $iterable, ?callable $callback=null):bool
  • Loading branch information
TysonAndre committed May 31, 2021
commit 7f3cf58259f178bc1c8735794ea510f8bb121b4b
33 changes: 19 additions & 14 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -6195,7 +6195,7 @@ PHP_FUNCTION(array_combine)
}
/* }}} */

static inline void php_array_until(zval *return_value, HashTable *htbl, zend_fcall_info fci, zend_fcall_info_cache fci_cache, int stop_value) /* {{{ */
static inline void php_array_until(zval *return_value, HashTable *htbl, zend_fcall_info fci, zend_fcall_info_cache fci_cache, int stop_value, int negate) /* {{{ */
{
zval args[1];
zend_bool have_callback = 0;
Expand All @@ -6204,7 +6204,7 @@ static inline void php_array_until(zval *return_value, HashTable *htbl, zend_fca
int result;

if (zend_hash_num_elements(htbl) == 0) {
RETURN_BOOL(!stop_value);
RETURN_BOOL(!stop_value ^ negate);
}

if (ZEND_FCI_INITIALIZED(fci)) {
Expand All @@ -6229,16 +6229,16 @@ static inline void php_array_until(zval *return_value, HashTable *htbl, zend_fca
zval_ptr_dtor(&retval);
/* The user-provided callback rarely returns refcounted values. */
if (retval_true == stop_value) {
RETURN_BOOL(stop_value);
RETURN_BOOL(stop_value ^ negate);
}
} else {
if (zend_is_true(operand) == stop_value) {
RETURN_BOOL(stop_value);
RETURN_BOOL(stop_value ^ negate);
}
}
} ZEND_HASH_FOREACH_END();

RETURN_BOOL(!stop_value);
RETURN_BOOL(!stop_value ^ negate);
}
/* }}} */

Expand Down Expand Up @@ -6289,7 +6289,7 @@ static int php_traversable_func_until(zend_object_iterator *iter, void *puser) /
}
/* }}} */

static inline void php_iterable_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value) /* {{{ */
static inline void php_iterable_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value, int negate) /* {{{ */
{
zval *input;
zend_fcall_info fci = empty_fcall_info;
Expand All @@ -6303,7 +6303,7 @@ static inline void php_iterable_until(INTERNAL_FUNCTION_PARAMETERS, int stop_val

switch (Z_TYPE_P(input)) {
case IS_ARRAY:
php_array_until(return_value, Z_ARRVAL_P(input), fci, fci_cache, stop_value);
php_array_until(return_value, Z_ARRVAL_P(input), fci, fci_cache, stop_value, negate);
return;
case IS_OBJECT: {
ZEND_ASSERT(instanceof_function(Z_OBJCE_P(input), zend_ce_traversable));
Expand All @@ -6318,26 +6318,31 @@ static inline void php_iterable_until(INTERNAL_FUNCTION_PARAMETERS, int stop_val
until_info.found = 0;

if (spl_iterator_apply(until_info.obj, php_traversable_func_until, (void*)&until_info) == SUCCESS && until_info.result == SUCCESS) {
RETURN_BOOL(!(until_info.found ^ stop_value));
RETURN_BOOL(!(until_info.found ^ stop_value ^ negate));
}
return;
}
EMPTY_SWITCH_DEFAULT_CASE();
}
}

/* {{{ proto bool all(array input, ?callable predicate = null)
Determines whether the predicate holds for all elements in the array */
/* Determines whether the predicate holds for all elements in the array */
PHP_FUNCTION(all)
{
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
}
/* }}} */

/* {{{ proto bool any(array input, ?callable predicate = null)
Determines whether the predicate holds for at least one element in the array */
/* {{{ Determines whether the predicate holds for at least one element in the array */
PHP_FUNCTION(any)
{
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0);
}
/* }}} */

/* {{{ Determines whether the predicate holds for no elements of the array */
PHP_FUNCTION(none)
{
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1);
}
/* }}} */
2 changes: 2 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1520,4 +1520,6 @@ function any(iterable $iterable, ?callable $callback = null): bool {}

function all(iterable $iterable, ?callable $callback = null): bool {}

function none(iterable $iterable, ?callable $callback = null): bool {}

}
6 changes: 5 additions & 1 deletion ext/standard/basic_functions_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 1bbe2028d78f83db95e129acff83e14f57456f3e */
* Stub hash: 59dd3527ae66c463cb59b6cc20b41d7fbdcc0ff0 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
Expand Down Expand Up @@ -2225,6 +2225,8 @@ ZEND_END_ARG_INFO()

#define arginfo_iterable_all arginfo_iterable_any

#define arginfo_iterable_none arginfo_iterable_any


ZEND_FUNCTION(set_time_limit);
ZEND_FUNCTION(header_register_callback);
Expand Down Expand Up @@ -2848,6 +2850,7 @@ ZEND_FUNCTION(sapi_windows_generate_ctrl_event);
#endif
ZEND_FUNCTION(any);
ZEND_FUNCTION(all);
ZEND_FUNCTION(none);


static const zend_function_entry ext_functions[] = {
Expand Down Expand Up @@ -3503,6 +3506,7 @@ static const zend_function_entry ext_functions[] = {
#endif
ZEND_NS_FE("iterable", any, arginfo_iterable_any)
ZEND_NS_FE("iterable", all, arginfo_iterable_all)
ZEND_NS_FE("iterable", none, arginfo_iterable_none)
ZEND_FE_END
};

Expand Down
81 changes: 81 additions & 0 deletions ext/standard/tests/iterable/none_array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
--TEST--
Test none() function
--FILE--
<?php

use function iterable\none;

/*
Prototype: bool none(array $iterable, mixed $callback);
Description: Iterate array and stop based on return value of callback
*/

function is_int_ex($nr)
{
return is_int($nr);
}

echo "\n*** Testing not enough or wrong arguments ***\n";

function dump_none(...$args) {
try {
var_dump(none(...$args));
} catch (Error $e) {
printf("Caught %s: %s\n", $e::class, $e->getMessage());
}
}

dump_none();
dump_none(true);
dump_none([]);
dump_none(true, function () {});
dump_none([], true);

echo "\n*** Testing basic functionality ***\n";

dump_none(['hello', 'world'], 'is_int_ex');
dump_none(['hello', 1, 2, 3], 'is_int_ex');
$iterations = 0;
dump_none(['hello', 1, 2, 3], function($item) use (&$iterations) {
++$iterations;
return is_int($item);
});
var_dump($iterations);

echo "\n*** Testing second argument to predicate ***\n";

dump_none([1, 2, 3], function($item, $key) {
var_dump($key);
return false;
});

echo "\n*** Testing edge cases ***\n";

dump_none([], 'is_int_ex');

dump_none(['key' => 'x'], null);

echo "\nDone";
?>
--EXPECT--
*** Testing not enough or wrong arguments ***
Caught ArgumentCountError: iterable\none() expects at least 1 argument, 0 given
Caught TypeError: iterable\none(): Argument #1 ($iterable) must be of type iterable, bool given
bool(true)
Caught TypeError: iterable\none(): Argument #1 ($iterable) must be of type iterable, bool given
Caught TypeError: iterable\none(): Argument #2 ($callback) must be a valid callback or null, no array or string given

*** Testing basic functionality ***
bool(true)
bool(false)
bool(false)
int(2)

*** Testing second argument to predicate ***
Caught ArgumentCountError: Too few arguments to function {closure}(), 1 passed and exactly 2 expected

*** Testing edge cases ***
bool(true)
bool(false)

Done
100 changes: 100 additions & 0 deletions ext/standard/tests/iterable/none_traversable.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
--TEST--
Test none() function
--FILE--
<?php

use function iterable\none;

/*
Prototype: bool none(array $array, ?callable $callback = null, int $use_type = 0);
Description: Iterate array and stop based on return value of callback
*/

function is_int_ex($item)
{
return is_int($item);
}

function dump_none(...$args) {
try {
var_dump(none(...$args));
} catch (Error $e) {
printf("Caught %s: %s\n", $e::class, $e->getMessage());
}
}


echo "\n*** Testing not enough or wrong arguments ***\n";

dump_none(new ArrayIterator());
dump_none(new ArrayIterator(), true);

echo "\n*** Testing basic functionality ***\n";

dump_none(new ArrayIterator([1, 2, 3]), 'is_int_ex');
dump_none(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex');
$iterations = 0;
dump_none(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
++$iterations;
return is_int($item);
});
var_dump($iterations);

echo "\n*** Testing edge cases ***\n";

dump_none(new ArrayIterator([[], 0, '', 'hello']), function($item) {
var_dump($item);
return $item;
});

dump_none(new ArrayIterator(), 'is_int_ex');

dump_none(new ArrayObject(['key' => true]), null);

echo "testing generator\n";
function my_generator() {
yield false;
echo "Before returning []\n";
yield [];
echo "Before returning true\n";
yield true;
echo "Unreachable\n";
}
dump_none(my_generator());

function my_other_generator() {
yield false;
echo "Before my_other_generator() end\n";
}
dump_none(my_other_generator());

echo "\nDone";
?>
--EXPECT--
*** Testing not enough or wrong arguments ***
bool(true)
Caught TypeError: iterable\none(): Argument #2 ($callback) must be a valid callback or null, no array or string given

*** Testing basic functionality ***
bool(false)
bool(false)
bool(false)
int(2)

*** Testing edge cases ***
array(0) {
}
int(0)
string(0) ""
string(5) "hello"
bool(false)
bool(true)
bool(false)
testing generator
Before returning []
Before returning true
bool(false)
Before my_other_generator() end
bool(true)

Done