]> granicus.if.org Git - php/commitdiff
Add support for string keys in array unpacking
authorNikita Popov <nikita.ppv@gmail.com>
Thu, 7 Jan 2021 09:49:50 +0000 (10:49 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 9 Feb 2021 09:04:27 +0000 (10:04 +0100)
This adds support for:

    $array1 = ['a' => 1, 'b' => 2];
    $array2 = ['b' => 3, 'c' => 4];
    $array = [...$array1, ...$array2];
    // => ['a' => 1, 'b' => 3, 'c' => 4]

RFC: https://wiki.php.net/rfc/array_unpacking_string_keys

Closes GH-6584.

UPGRADING
Zend/Optimizer/sccp.c
Zend/Optimizer/zend_inference.c
Zend/tests/array_unpack/non_integer_keys.phpt
Zend/tests/array_unpack/string_keys.phpt
Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt
Zend/zend_ast.c
Zend/zend_compile.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

index 29701c37141f80c933e9e3aa36016c80c4737b1e..301459786840928b360dcb9fbce3d9d469d70f99 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -82,8 +82,11 @@ PHP 8.1 UPGRADE NOTES
 
 - Core:
   . It is now possible to specify octal integer by using the explicit "0o"/"0O"
-    prefix similar to hexadecimal ("0x"/"0X) and binary ("0b"/"0B") integer literals
+    prefix similar to hexadecimal ("0x"/"0X) and binary ("0b"/"0B") integer
+    literals.
     RFC: https://wiki.php.net/rfc/explicit_octal_notation
+  . Added support for array unpacking with strings keys.
+    RFC: https://wiki.php.net/rfc/array_unpacking_string_keys
 
 - Curl:
   . Added CURLOPT_DOH_URL option.
index e097f654c89f563df74a2d4d677bf22ebc533804..490fbdf16b58bf96be67577ea13d8f2c1fe88a6a 100644 (file)
@@ -575,9 +575,10 @@ static inline int ct_eval_add_array_unpack(zval *result, zval *array) {
        SEPARATE_ARRAY(result);
        ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(array), key, value) {
                if (key) {
-                       return FAILURE;
+                       value = zend_hash_update(Z_ARR_P(result), key, value);
+               } else {
+                       value = zend_hash_next_index_insert(Z_ARR_P(result), value);
                }
-               value = zend_hash_next_index_insert(Z_ARR_P(result), value);
                if (!value) {
                        return FAILURE;
                }
index ba51283bebc006be610f8c40743d1731bd0b4de4..a61a18176c576bfaa3ad9f51e32ecb1311323b0a 100644 (file)
@@ -3163,12 +3163,9 @@ static zend_always_inline int _zend_update_type_info(
                case ZEND_ADD_ARRAY_UNPACK:
                        tmp = ssa_var_info[ssa_op->result_use].type;
                        ZEND_ASSERT(tmp & MAY_BE_ARRAY);
-                       /* Ignore string keys as they will throw. */
-                       if (t1 & MAY_BE_ARRAY_KEY_LONG) {
-                               tmp |= MAY_BE_ARRAY_KEY_LONG | (t1 & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF));
-                       }
+                       tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
                        if (t1 & MAY_BE_OBJECT) {
-                               tmp |= MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY;
+                               tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
                        }
                        UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
index a5e407743c37d20e1df604c4d66db086a2f414e1..ab7a20ac86b98640571cd39a1e0083d0aa72595a 100644 (file)
@@ -1,5 +1,5 @@
 --TEST--
-Array unpacking does not work with non-integer keys
+Array unpacking does not work with non-integer/string keys
 --FILE--
 <?php
 function gen() {
@@ -15,4 +15,4 @@ try {
 
 ?>
 --EXPECT--
-Exception: Cannot unpack Traversable with non-integer keys
+Exception: Keys must be of type int|string during array unpacking
index e4cfd77f5863439fc4874076a9f742dc6643a58d..d446e69cabc2bc9a956f8b6633b78181233707a1 100644 (file)
@@ -1,22 +1,58 @@
 --TEST--
-array unpacking with string keys (not supported)
+Array unpacking with string keys
 --FILE--
 <?php
 
-try {
-    $array = [1, 2, "foo" => 3, 4];
-    var_dump([...$array]);
-} catch (Error $ex) {
-    var_dump($ex->getMessage());
-}
-try {
-    $iterator = new ArrayIterator([1, 2, "foo" => 3, 4]);
-    var_dump([...$iterator]);
-} catch (Error $ex) {
-    var_dump($ex->getMessage());
+// Works with both arrays and Traversables.
+$array = [1, 2, "foo" => 3, 4];
+var_dump([...$array]);
+
+$iterator = new ArrayIterator([1, 2, "foo" => 3, 4]);
+var_dump([...$iterator]);
+
+// Test overwriting behavior.
+$array1 = ["foo" => 1];
+$array2 = ["foo" => 2];
+var_dump(["foo" => 0, ...$array1, ...$array2]);
+var_dump(["foo" => 0, ...$array1, ...$array2, "foo" => 3]);
+
+// Test numeric string key from iterator.
+function gen() {
+    yield "42" => 42;
 }
+var_dump([...gen()]);
 
 ?>
 --EXPECT--
-string(36) "Cannot unpack array with string keys"
-string(42) "Cannot unpack Traversable with string keys"
+array(4) {
+  [0]=>
+  int(1)
+  [1]=>
+  int(2)
+  ["foo"]=>
+  int(3)
+  [2]=>
+  int(4)
+}
+array(4) {
+  [0]=>
+  int(1)
+  [1]=>
+  int(2)
+  ["foo"]=>
+  int(3)
+  [2]=>
+  int(4)
+}
+array(1) {
+  ["foo"]=>
+  int(2)
+}
+array(1) {
+  ["foo"]=>
+  int(3)
+}
+array(1) {
+  [0]=>
+  int(42)
+}
index 1401fb9bd5ed97e34ef691cd8c0270c3b01d20f9..df58d78a6ac8770f5a1d0f86ee6af70f7c034122 100644 (file)
@@ -1,10 +1,23 @@
 --TEST--
-Unpacking of string keys detected at compile-time
+Unpacking of string keys is supported at compile-time
 --FILE--
 <?php
 
 var_dump([...['a' => 'b']]);
+var_dump(['a' => 'X', ...['a' => 'b']]);
+var_dump([...['a' => 'b'], 'a' => 'X']);
 
 ?>
---EXPECTF--
-Fatal error: Cannot unpack array with string keys in %s on line %d
+--EXPECT--
+array(1) {
+  ["a"]=>
+  string(1) "b"
+}
+array(1) {
+  ["a"]=>
+  string(1) "b"
+}
+array(1) {
+  ["a"]=>
+  string(1) "X"
+}
index b2af322d9119ec6e4665a138b238f80d19079b6e..73f2390c11c7bf2722083a0589911ac3fce72200 100644 (file)
@@ -485,16 +485,15 @@ static zend_result zend_ast_add_unpacked_element(zval *result, zval *expr) {
 
                ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
                        if (key) {
-                               zend_throw_error(NULL, "Cannot unpack array with string keys");
-                               return FAILURE;
+                               zend_hash_update(Z_ARRVAL_P(result), key, val);
                        } else {
                                if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
                                        zend_throw_error(NULL,
                                                "Cannot add element to the array as the next element is already occupied");
                                        return FAILURE;
                                }
-                               Z_TRY_ADDREF_P(val);
                        }
+                       Z_TRY_ADDREF_P(val);
                } ZEND_HASH_FOREACH_END();
                return SUCCESS;
        }
index c671628479e68d28d46c64c0defee12fcff688be..65fa1862344e0692d03bd4f956768e7b83733e26 100644 (file)
@@ -8100,9 +8100,8 @@ static bool zend_try_ct_eval_array(zval *result, zend_ast *ast) /* {{{ */
 
                                ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
                                        if (key) {
-                                               zend_error_noreturn(E_COMPILE_ERROR, "Cannot unpack array with string keys");
-                                       }
-                                       if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
+                                               zend_hash_update(Z_ARRVAL_P(result), key, val);
+                                       } else if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
                                                zval_ptr_dtor(result);
                                                return 0;
                                        }
index 2663945a665b90a2d500e74d934b317a7af2451e..4b927461a2085388199a84efa52c2caa3bc3b8aa 100644 (file)
@@ -5952,9 +5952,11 @@ ZEND_VM_HANDLER(147, ZEND_ADD_ARRAY_UNPACK, ANY, ANY)
 {
        USE_OPLINE
        zval *op1;
+       HashTable *result_ht;
 
        SAVE_OPLINE();
        op1 = GET_OP1_ZVAL_PTR(BP_VAR_R);
+       result_ht = Z_ARRVAL_P(EX_VAR(opline->result.var));
 
 ZEND_VM_C_LABEL(add_unpack_again):
        if (EXPECTED(Z_TYPE_P(op1) == IS_ARRAY)) {
@@ -5963,16 +5965,14 @@ ZEND_VM_C_LABEL(add_unpack_again):
                zend_string *key;
 
                ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
+                       if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) {
+                               val = Z_REFVAL_P(val);
+                       }
+                       Z_TRY_ADDREF_P(val);
                        if (key) {
-                               zend_throw_error(NULL, "Cannot unpack array with string keys");
-                               FREE_OP1();
-                               HANDLE_EXCEPTION();
+                               zend_hash_update(result_ht, key, val);
                        } else {
-                               if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) {
-                                       val = Z_REFVAL_P(val);
-                               }
-                               Z_TRY_ADDREF_P(val);
-                               if (!zend_hash_next_index_insert(Z_ARRVAL_P(EX_VAR(opline->result.var)), val)) {
+                               if (!zend_hash_next_index_insert(result_ht, val)) {
                                        zend_cannot_add_element();
                                        zval_ptr_dtor_nogc(val);
                                        break;
@@ -6013,32 +6013,42 @@ ZEND_VM_C_LABEL(add_unpack_again):
                                        break;
                                }
 
+                               zval key;
                                if (iter->funcs->get_current_key) {
-                                       zval key;
                                        iter->funcs->get_current_key(iter, &key);
                                        if (UNEXPECTED(EG(exception) != NULL)) {
                                                break;
                                        }
 
-                                       if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
+                                       if (UNEXPECTED(Z_TYPE(key) != IS_LONG && Z_TYPE(key) != IS_STRING)) {
                                                zend_throw_error(NULL,
-                                                       (Z_TYPE(key) == IS_STRING) ?
-                                                               "Cannot unpack Traversable with string keys" :
-                                                               "Cannot unpack Traversable with non-integer keys");
+                                                       "Keys must be of type int|string during array unpacking");
                                                zval_ptr_dtor(&key);
                                                break;
                                        }
+                               } else {
+                                       ZVAL_UNDEF(&key);
                                }
 
                                ZVAL_DEREF(val);
                                Z_TRY_ADDREF_P(val);
 
-                               if (!zend_hash_next_index_insert(Z_ARRVAL_P(EX_VAR(opline->result.var)), val)) {
-                                       zend_cannot_add_element();
-                                       zval_ptr_dtor_nogc(val);
+                               zend_ulong num_key;
+                               if (Z_TYPE(key) == IS_STRING && !ZEND_HANDLE_NUMERIC(Z_STR(key), num_key)) {
+                                       zend_hash_update(result_ht, Z_STR(key), val);
+                                       zval_ptr_dtor_str(&key);
+                               } else {
+                                       if (!zend_hash_next_index_insert(result_ht, val)) {
+                                               zend_cannot_add_element();
+                                               zval_ptr_dtor_nogc(val);
+                                               break;
+                                       }
                                }
 
                                iter->funcs->move_forward(iter);
+                               if (UNEXPECTED(EG(exception))) {
+                                       break;
+                               }
                        }
 
                        zend_iterator_dtor(iter);
index e4b1c03aeea2dcab550ab8956c14010bc8d08994..2f1f6fc298dfcdbda18cddf96de1ce77017c2fe5 100644 (file)
@@ -2499,9 +2499,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_UNPACK_SPEC_HANDLER(
 {
        USE_OPLINE
        zval *op1;
+       HashTable *result_ht;
 
        SAVE_OPLINE();
        op1 = get_zval_ptr(opline->op1_type, opline->op1, BP_VAR_R);
+       result_ht = Z_ARRVAL_P(EX_VAR(opline->result.var));
 
 add_unpack_again:
        if (EXPECTED(Z_TYPE_P(op1) == IS_ARRAY)) {
@@ -2510,16 +2512,14 @@ add_unpack_again:
                zend_string *key;
 
                ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
+                       if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) {
+                               val = Z_REFVAL_P(val);
+                       }
+                       Z_TRY_ADDREF_P(val);
                        if (key) {
-                               zend_throw_error(NULL, "Cannot unpack array with string keys");
-                               FREE_OP(opline->op1_type, opline->op1.var);
-                               HANDLE_EXCEPTION();
+                               zend_hash_update(result_ht, key, val);
                        } else {
-                               if (Z_ISREF_P(val) && Z_REFCOUNT_P(val) == 1) {
-                                       val = Z_REFVAL_P(val);
-                               }
-                               Z_TRY_ADDREF_P(val);
-                               if (!zend_hash_next_index_insert(Z_ARRVAL_P(EX_VAR(opline->result.var)), val)) {
+                               if (!zend_hash_next_index_insert(result_ht, val)) {
                                        zend_cannot_add_element();
                                        zval_ptr_dtor_nogc(val);
                                        break;
@@ -2560,32 +2560,42 @@ add_unpack_again:
                                        break;
                                }
 
+                               zval key;
                                if (iter->funcs->get_current_key) {
-                                       zval key;
                                        iter->funcs->get_current_key(iter, &key);
                                        if (UNEXPECTED(EG(exception) != NULL)) {
                                                break;
                                        }
 
-                                       if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
+                                       if (UNEXPECTED(Z_TYPE(key) != IS_LONG && Z_TYPE(key) != IS_STRING)) {
                                                zend_throw_error(NULL,
-                                                       (Z_TYPE(key) == IS_STRING) ?
-                                                               "Cannot unpack Traversable with string keys" :
-                                                               "Cannot unpack Traversable with non-integer keys");
+                                                       "Keys must be of type int|string during array unpacking");
                                                zval_ptr_dtor(&key);
                                                break;
                                        }
+                               } else {
+                                       ZVAL_UNDEF(&key);
                                }
 
                                ZVAL_DEREF(val);
                                Z_TRY_ADDREF_P(val);
 
-                               if (!zend_hash_next_index_insert(Z_ARRVAL_P(EX_VAR(opline->result.var)), val)) {
-                                       zend_cannot_add_element();
-                                       zval_ptr_dtor_nogc(val);
+                               zend_ulong num_key;
+                               if (Z_TYPE(key) == IS_STRING && !ZEND_HANDLE_NUMERIC(Z_STR(key), num_key)) {
+                                       zend_hash_update(result_ht, Z_STR(key), val);
+                                       zval_ptr_dtor_str(&key);
+                               } else {
+                                       if (!zend_hash_next_index_insert(result_ht, val)) {
+                                               zend_cannot_add_element();
+                                               zval_ptr_dtor_nogc(val);
+                                               break;
+                                       }
                                }
 
                                iter->funcs->move_forward(iter);
+                               if (UNEXPECTED(EG(exception))) {
+                                       break;
+                               }
                        }
 
                        zend_iterator_dtor(iter);