diff options
-rw-r--r-- | Zend/zend_hash.h | 27 | ||||
-rw-r--r-- | ext/json/json_encoder.c | 25 | ||||
-rw-r--r-- | ext/opcache/Optimizer/sccp.c | 1 | ||||
-rwxr-xr-x | ext/standard/basic_functions.stub.php | 2 | ||||
-rw-r--r-- | ext/standard/basic_functions_arginfo.h | 8 | ||||
-rw-r--r-- | ext/standard/tests/general_functions/array_is_list.phpt | 98 | ||||
-rw-r--r-- | ext/standard/type.c | 13 |
7 files changed, 151 insertions, 23 deletions
diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index e339345a32..64a9220573 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -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++; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 92e4a10933..f608936349 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -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); - - i = myht ? zend_hash_num_elements(myht) : 0; - if (i > 0) { - zend_string *key; - zend_ulong index, idx; + zend_array *myht = Z_ARRVAL_P(val); - 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; diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index 7f9bdbdbb5..bc9dc38680 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -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") diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index b6597d262e..99d0d3c63d 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -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 {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index e416fc5e27..5eafa59b50 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -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) diff --git a/ext/standard/tests/general_functions/array_is_list.phpt b/ext/standard/tests/general_functions/array_is_list.phpt new file mode 100644 index 0000000000..1cc2886f96 --- /dev/null +++ b/ext/standard/tests/general_functions/array_is_list.phpt @@ -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
\ No newline at end of file diff --git a/ext/standard/type.c b/ext/standard/type.c index afe0e7afc2..1036dd7d06 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -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) |