Skip to content

Commit f9162a1

Browse files
duskwuffTysonAndre
andcommitted
Add is_list function
This function tests if an array contains only sequential integer keys. While this 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 version of php#4886 - Use .stub.php files - Add opcache constant evaluation when argument is a constant Co-Authored-By: Tyson Andre <[email protected]> Co-Authored-By: Dusk <[email protected]>
1 parent c3299d7 commit f9162a1

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

ext/standard/basic_functions.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,8 @@ function is_string(mixed $value): bool {}
14431443

14441444
function is_array(mixed $value): bool {}
14451445

1446+
function is_list(mixed $value): bool {}
1447+
14461448
function is_object(mixed $value): bool {}
14471449

14481450
function is_scalar(mixed $value): bool {}

ext/standard/basic_functions_arginfo.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 8b6ef365e9635c92ef86adb40b2aba077867f3b2 */
2+
* Stub hash: c82b5b9e9bab13ae814ff29994f2822f73431d1c */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
55
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -2102,6 +2102,8 @@ ZEND_END_ARG_INFO()
21022102

21032103
#define arginfo_is_array arginfo_boolval
21042104

2105+
#define arginfo_is_list arginfo_boolval
2106+
21052107
#define arginfo_is_object arginfo_boolval
21062108

21072109
#define arginfo_is_scalar arginfo_boolval
@@ -2811,6 +2813,7 @@ ZEND_FUNCTION(is_float);
28112813
ZEND_FUNCTION(is_numeric);
28122814
ZEND_FUNCTION(is_string);
28132815
ZEND_FUNCTION(is_array);
2816+
ZEND_FUNCTION(is_list);
28142817
ZEND_FUNCTION(is_object);
28152818
ZEND_FUNCTION(is_scalar);
28162819
ZEND_FUNCTION(is_callable);
@@ -3462,6 +3465,7 @@ static const zend_function_entry ext_functions[] = {
34623465
ZEND_FE(is_numeric, arginfo_is_numeric)
34633466
ZEND_FE(is_string, arginfo_is_string)
34643467
ZEND_FE(is_array, arginfo_is_array)
3468+
ZEND_FE(is_list, arginfo_is_list)
34653469
ZEND_FE(is_object, arginfo_is_object)
34663470
ZEND_FE(is_scalar, arginfo_is_scalar)
34673471
ZEND_FE(is_callable, arginfo_is_callable)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
--TEST--
2+
Test is_list() function
3+
--FILE--
4+
<?php
5+
6+
function test_is_list(string $desc, $val) : void {
7+
printf("%s: %s\n", $desc, is_list($val) ? "true" : "false");
8+
}
9+
10+
test_is_list("empty", []);
11+
test_is_list("one", [1]);
12+
test_is_list("two", [1,2]);
13+
test_is_list("three", [1,2,3]);
14+
test_is_list("four", [1,2,3,4]);
15+
test_is_list("ten", range(0, 10));
16+
17+
test_is_list("null", null);
18+
test_is_list("int", 123);
19+
test_is_list("float", 1.23);
20+
test_is_list("string", "string");
21+
test_is_list("object", new stdclass);
22+
test_is_list("true", true);
23+
test_is_list("false", false);
24+
25+
test_is_list("string key", ["a" => 1]);
26+
test_is_list("mixed keys", [0 => 0, "a" => 1]);
27+
test_is_list("ordered keys", [0 => 0, 1 => 1]);
28+
test_is_list("shuffled keys", [1 => 0, 0 => 1]);
29+
test_is_list("skipped keys", [0 => 0, 2 => 2]);
30+
31+
$arr = [1, 2, 3];
32+
unset($arr[0]);
33+
test_is_list("unset first", $arr);
34+
35+
$arr = [1, 2, 3];
36+
unset($arr[1]);
37+
test_is_list("unset middle", $arr);
38+
39+
$arr = [1, 2, 3];
40+
unset($arr[2]);
41+
test_is_list("unset end", $arr);
42+
43+
$arr = [1, "a" => "a", 2];
44+
unset($arr["a"]);
45+
test_is_list("unset string key", $arr);
46+
47+
$arr = [1 => 1, 0 => 0];
48+
unset($arr[1]);
49+
test_is_list("unset into order", $arr);
50+
51+
$arr = ["a" => 1];
52+
unset($arr["a"]);
53+
test_is_list("unset to empty", $arr);
54+
55+
$arr = [1, 2, 3];
56+
$arr[] = 4;
57+
test_is_list("append implicit", $arr);
58+
59+
$arr = [1, 2, 3];
60+
$arr[3] = 4;
61+
test_is_list("append explicit", $arr);
62+
63+
$arr = [1, 2, 3];
64+
$arr[4] = 5;
65+
test_is_list("append with gap", $arr);
66+
67+
--EXPECT--
68+
empty: true
69+
one: true
70+
two: true
71+
three: true
72+
four: true
73+
ten: true
74+
null: false
75+
int: false
76+
float: false
77+
string: false
78+
object: false
79+
true: false
80+
false: false
81+
string key: false
82+
mixed keys: false
83+
ordered keys: true
84+
shuffled keys: false
85+
skipped keys: false
86+
unset first: false
87+
unset middle: false
88+
unset end: true
89+
unset string key: true
90+
unset into order: true
91+
unset to empty: true
92+
append implicit: true
93+
append explicit: true
94+
append with gap: false

ext/standard/type.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,41 @@ PHP_FUNCTION(is_array)
321321
}
322322
/* }}} */
323323

324+
/* {{{ Returns true if variable is an array whose keys are all numeric, sequential, and start at 0 */
325+
PHP_FUNCTION(is_list)
326+
{
327+
zval *arg;
328+
zend_array *arrval;
329+
zend_ulong num_idx, expected_idx = 0;
330+
zend_string *str_idx;
331+
332+
ZEND_PARSE_PARAMETERS_START(1, 1)
333+
Z_PARAM_ZVAL(arg)
334+
ZEND_PARSE_PARAMETERS_END();
335+
336+
if (Z_TYPE_P(arg) != IS_ARRAY)
337+
RETURN_FALSE;
338+
339+
arrval = Z_ARRVAL_P(arg);
340+
341+
/* Empty arrays are lists */
342+
if (zend_hash_num_elements(arrval) == 0)
343+
RETURN_TRUE;
344+
345+
/* Packed arrays are lists */
346+
if (HT_IS_PACKED(arrval) && HT_IS_WITHOUT_HOLES(arrval))
347+
RETURN_TRUE;
348+
349+
/* Check if the list could theoretically be repacked */
350+
ZEND_HASH_FOREACH_KEY(arrval, num_idx, str_idx) {
351+
if (str_idx != NULL || num_idx != expected_idx++)
352+
RETURN_FALSE;
353+
} ZEND_HASH_FOREACH_END();
354+
355+
RETURN_TRUE;
356+
}
357+
/* }}} */
358+
324359
/* {{{ Returns true if variable is an object
325360
Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
326361
PHP_FUNCTION(is_object)

0 commit comments

Comments
 (0)