mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Add array_is_list(array $array) function
This function tests if an array contains only sequential integer keys. While list isn't an official type, this usage is consistent with the community usage of "list" as an annotation type, cf. https://psalm.dev/docs/annotating_code/type_syntax/array_types/#lists Rebased and modified version of #4886 - Use .stub.php files - Add opcache constant evaluation when argument is a constant - Change from is_list(mixed $value) to array_is_list(array $array) RFC: https://wiki.php.net/rfc/is_list Co-Authored-By: Tyson Andre <tysonandre775@hotmail.com> Co-Authored-By: Dusk <dusk@woofle.net> Closes GH-6070
This commit is contained in:
parent
04db2c8735
commit
13c430b1db
7 changed files with 151 additions and 23 deletions
|
@ -1188,6 +1188,33 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht,
|
|||
ZEND_HASH_FILL_FINISH(); \
|
||||
} while (0)
|
||||
|
||||
/* Check if an array is a list */
|
||||
static zend_always_inline zend_bool zend_array_is_list(zend_array *array)
|
||||
{
|
||||
zend_long expected_idx = 0;
|
||||
zend_long num_idx;
|
||||
zend_string* str_idx;
|
||||
/* Empty arrays are lists */
|
||||
if (zend_hash_num_elements(array) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Packed arrays are lists */
|
||||
if (HT_IS_PACKED(array) && HT_IS_WITHOUT_HOLES(array)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Check if the list could theoretically be repacked */
|
||||
ZEND_HASH_FOREACH_KEY(array, num_idx, str_idx) {
|
||||
if (str_idx != NULL || num_idx != expected_idx++) {
|
||||
return 0;
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static zend_always_inline zval *_zend_hash_append_ex(HashTable *ht, zend_string *key, zval *zv, bool interned)
|
||||
{
|
||||
uint32_t idx = ht->nNumUsed++;
|
||||
|
|
|
@ -36,29 +36,10 @@ static int php_json_escape_string(
|
|||
|
||||
static int php_json_determine_array_type(zval *val) /* {{{ */
|
||||
{
|
||||
int i;
|
||||
HashTable *myht = Z_ARRVAL_P(val);
|
||||
zend_array *myht = Z_ARRVAL_P(val);
|
||||
|
||||
i = myht ? zend_hash_num_elements(myht) : 0;
|
||||
if (i > 0) {
|
||||
zend_string *key;
|
||||
zend_ulong index, idx;
|
||||
|
||||
if (HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
|
||||
return PHP_JSON_OUTPUT_ARRAY;
|
||||
}
|
||||
|
||||
idx = 0;
|
||||
ZEND_HASH_FOREACH_KEY(myht, index, key) {
|
||||
if (key) {
|
||||
return PHP_JSON_OUTPUT_OBJECT;
|
||||
} else {
|
||||
if (index != idx) {
|
||||
return PHP_JSON_OUTPUT_OBJECT;
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
if (myht) {
|
||||
return zend_array_is_list(myht) ? PHP_JSON_OUTPUT_ARRAY : PHP_JSON_OUTPUT_OBJECT;
|
||||
}
|
||||
|
||||
return PHP_JSON_OUTPUT_ARRAY;
|
||||
|
|
|
@ -788,6 +788,7 @@ static bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **a
|
|||
|| zend_string_equals_literal(name, "array_diff")
|
||||
|| zend_string_equals_literal(name, "array_diff_assoc")
|
||||
|| zend_string_equals_literal(name, "array_diff_key")
|
||||
|| zend_string_equals_literal(name, "array_is_list")
|
||||
|| zend_string_equals_literal(name, "array_key_exists")
|
||||
|| zend_string_equals_literal(name, "array_keys")
|
||||
|| zend_string_equals_literal(name, "array_merge")
|
||||
|
|
|
@ -248,6 +248,8 @@ function array_chunk(array $array, int $length, bool $preserve_keys = false): ar
|
|||
|
||||
function array_combine(array $keys, array $values): array {}
|
||||
|
||||
function array_is_list(array $array): bool {}
|
||||
|
||||
/* base64.c */
|
||||
|
||||
function base64_encode(string $string): string {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 4e471966d507762dd6fdd2fc4200c8430fac97f4 */
|
||||
* Stub hash: 7540039937587f05584660bc1a1a8a80aa5ccbd1 */
|
||||
|
||||
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)
|
||||
|
@ -360,6 +360,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_combine, 0, 2, IS_ARRAY, 0
|
|||
ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_is_list, 0, 1, _IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_base64_encode, 0, 1, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
@ -2309,6 +2313,7 @@ ZEND_FUNCTION(array_map);
|
|||
ZEND_FUNCTION(array_key_exists);
|
||||
ZEND_FUNCTION(array_chunk);
|
||||
ZEND_FUNCTION(array_combine);
|
||||
ZEND_FUNCTION(array_is_list);
|
||||
ZEND_FUNCTION(base64_encode);
|
||||
ZEND_FUNCTION(base64_decode);
|
||||
ZEND_FUNCTION(constant);
|
||||
|
@ -2933,6 +2938,7 @@ static const zend_function_entry ext_functions[] = {
|
|||
ZEND_FALIAS(key_exists, array_key_exists, arginfo_key_exists)
|
||||
ZEND_FE(array_chunk, arginfo_array_chunk)
|
||||
ZEND_FE(array_combine, arginfo_array_combine)
|
||||
ZEND_FE(array_is_list, arginfo_array_is_list)
|
||||
ZEND_FE(base64_encode, arginfo_base64_encode)
|
||||
ZEND_FE(base64_decode, arginfo_base64_decode)
|
||||
ZEND_FE(constant, arginfo_constant)
|
||||
|
|
98
ext/standard/tests/general_functions/array_is_list.phpt
Normal file
98
ext/standard/tests/general_functions/array_is_list.phpt
Normal file
|
@ -0,0 +1,98 @@
|
|||
--TEST--
|
||||
Test array_is_list() function
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test_is_list(string $desc, $val) : void {
|
||||
try {
|
||||
printf("%s: %s\n", $desc, json_encode(array_is_list($val)));
|
||||
} catch (TypeError $e) {
|
||||
printf("%s: threw %s\n", $desc, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
test_is_list("empty", []);
|
||||
test_is_list("one", [1]);
|
||||
test_is_list("two", [1,2]);
|
||||
test_is_list("three", [1,2,3]);
|
||||
test_is_list("four", [1,2,3,4]);
|
||||
test_is_list("ten", range(0, 10));
|
||||
|
||||
test_is_list("null", null);
|
||||
test_is_list("int", 123);
|
||||
test_is_list("float", 1.23);
|
||||
test_is_list("string", "string");
|
||||
test_is_list("object", new stdClass());
|
||||
test_is_list("true", true);
|
||||
test_is_list("false", false);
|
||||
|
||||
test_is_list("string key", ["a" => 1]);
|
||||
test_is_list("mixed keys", [0 => 0, "a" => 1]);
|
||||
test_is_list("ordered keys", [0 => 0, 1 => 1]);
|
||||
test_is_list("shuffled keys", [1 => 0, 0 => 1]);
|
||||
test_is_list("skipped keys", [0 => 0, 2 => 2]);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
unset($arr[0]);
|
||||
test_is_list("unset first", $arr);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
unset($arr[1]);
|
||||
test_is_list("unset middle", $arr);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
unset($arr[2]);
|
||||
test_is_list("unset end", $arr);
|
||||
|
||||
$arr = [1, "a" => "a", 2];
|
||||
unset($arr["a"]);
|
||||
test_is_list("unset string key", $arr);
|
||||
|
||||
$arr = [1 => 1, 0 => 0];
|
||||
unset($arr[1]);
|
||||
test_is_list("unset into order", $arr);
|
||||
|
||||
$arr = ["a" => 1];
|
||||
unset($arr["a"]);
|
||||
test_is_list("unset to empty", $arr);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
$arr[] = 4;
|
||||
test_is_list("append implicit", $arr);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
$arr[3] = 4;
|
||||
test_is_list("append explicit", $arr);
|
||||
|
||||
$arr = [1, 2, 3];
|
||||
$arr[4] = 5;
|
||||
test_is_list("append with gap", $arr);
|
||||
|
||||
--EXPECT--
|
||||
empty: true
|
||||
one: true
|
||||
two: true
|
||||
three: true
|
||||
four: true
|
||||
ten: true
|
||||
null: threw array_is_list(): Argument #1 ($array) must be of type array, null given
|
||||
int: threw array_is_list(): Argument #1 ($array) must be of type array, int given
|
||||
float: threw array_is_list(): Argument #1 ($array) must be of type array, float given
|
||||
string: threw array_is_list(): Argument #1 ($array) must be of type array, string given
|
||||
object: threw array_is_list(): Argument #1 ($array) must be of type array, stdClass given
|
||||
true: threw array_is_list(): Argument #1 ($array) must be of type array, bool given
|
||||
false: threw array_is_list(): Argument #1 ($array) must be of type array, bool given
|
||||
string key: false
|
||||
mixed keys: false
|
||||
ordered keys: true
|
||||
shuffled keys: false
|
||||
skipped keys: false
|
||||
unset first: false
|
||||
unset middle: false
|
||||
unset end: true
|
||||
unset string key: true
|
||||
unset into order: true
|
||||
unset to empty: true
|
||||
append implicit: true
|
||||
append explicit: true
|
||||
append with gap: false
|
|
@ -321,6 +321,19 @@ PHP_FUNCTION(is_array)
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Returns true if $array is an array whose keys are all numeric, sequential, and start at 0 */
|
||||
PHP_FUNCTION(array_is_list)
|
||||
{
|
||||
HashTable *array;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
Z_PARAM_ARRAY_HT(array)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
RETURN_BOOL(zend_array_is_list(array));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Returns true if variable is an object
|
||||
Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
|
||||
PHP_FUNCTION(is_object)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue