summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2021-01-07 10:49:50 +0100
committerNikita Popov <nikita.ppv@gmail.com>2021-02-09 10:04:27 +0100
commit27cd7a11cb9f0dc9f2a906a659775e221eb87efa (patch)
treebbde3f2bf36779cb5fdda40b39fc6ab95aaf3a8a
parent3fbd3d2e2f105aa1cb4790af536ca807724fbed0 (diff)
downloadphp-git-27cd7a11cb9f0dc9f2a906a659775e221eb87efa.tar.gz
Add support for string keys in array unpacking
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.
-rw-r--r--UPGRADING5
-rw-r--r--Zend/Optimizer/sccp.c5
-rw-r--r--Zend/Optimizer/zend_inference.c7
-rw-r--r--Zend/tests/array_unpack/non_integer_keys.phpt4
-rw-r--r--Zend/tests/array_unpack/string_keys.phpt64
-rw-r--r--Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt19
-rw-r--r--Zend/zend_ast.c5
-rw-r--r--Zend/zend_compile.c5
-rw-r--r--Zend/zend_vm_def.h42
-rw-r--r--Zend/zend_vm_execute.h42
10 files changed, 133 insertions, 65 deletions
diff --git a/UPGRADING b/UPGRADING
index 29701c3714..3014597868 100644
--- 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.
diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
index e097f654c8..490fbdf16b 100644
--- a/Zend/Optimizer/sccp.c
+++ b/Zend/Optimizer/sccp.c
@@ -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;
}
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index ba51283beb..a61a18176c 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -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;
diff --git a/Zend/tests/array_unpack/non_integer_keys.phpt b/Zend/tests/array_unpack/non_integer_keys.phpt
index a5e407743c..ab7a20ac86 100644
--- a/Zend/tests/array_unpack/non_integer_keys.phpt
+++ b/Zend/tests/array_unpack/non_integer_keys.phpt
@@ -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
diff --git a/Zend/tests/array_unpack/string_keys.phpt b/Zend/tests/array_unpack/string_keys.phpt
index e4cfd77f58..d446e69cab 100644
--- a/Zend/tests/array_unpack/string_keys.phpt
+++ b/Zend/tests/array_unpack/string_keys.phpt
@@ -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)
+}
diff --git a/Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt b/Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt
index 1401fb9bd5..df58d78a6a 100644
--- a/Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt
+++ b/Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt
@@ -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"
+}
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index b2af322d91..73f2390c11 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -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;
}
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index c671628479..65fa186234 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -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;
}
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 2663945a66..4b927461a2 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -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);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index e4b1c03aee..2f1f6fc298 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -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);