]> granicus.if.org Git - php/commitdiff
Allow for [] = $array; (alias for list())
authorBob Weinand <bobwei9@hotmail.com>
Mon, 4 Apr 2016 22:34:42 +0000 (00:34 +0200)
committerBob Weinand <bobwei9@hotmail.com>
Thu, 19 May 2016 23:51:05 +0000 (01:51 +0200)
14 files changed:
NEWS
UPGRADING
Zend/tests/assert/expect_015.phpt
Zend/tests/list_001.phpt
Zend/tests/list_008.phpt [new file with mode: 0644]
Zend/tests/list_009.phpt [new file with mode: 0644]
Zend/tests/list_010.phpt [new file with mode: 0644]
Zend/tests/list_011.phpt [new file with mode: 0644]
Zend/tests/list_012.phpt [new file with mode: 0644]
Zend/tests/list_mixed_keyed_unkeyed.phpt
Zend/zend_ast.c
Zend/zend_ast.h
Zend/zend_compile.c
Zend/zend_language_parser.y

diff --git a/NEWS b/NEWS
index 6ae33d3700d74e47c4a8d5aad35adc5f1e079872..5bb69bd52a423f3cff3ba99a726a3bb34c94c161 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,7 @@ PHP                                                                        NEWS
   . Implemented the RFC `Catching multiple exception types`. (Bronislaw Bialek,
     Pierrick)
   . Raise a compile-time warning on octal escape sequence overflow. (Sara)
+  . Added [] = as alternative construct to list() =. (Bob)
 
 - FTP:
   . Implemented FR #55651 (Option to ignore the returned FTP PASV address).
index e2bac1642374459bd7866d4a42ffc77b64ac2b9f..dcf80a60293371898a6533a78af5504042e5f856 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -43,6 +43,8 @@ PHP 7.1 UPGRADE NOTES
     (RFC: https://wiki.php.net/rfc/negative-string-offsets)
   . Added a form of the list() construct where keys can be specified.
     (RFC: https://wiki.php.net/rfc/list_keys)
+  . Added [] = as alternative construct to list() =.
+    (RFC: https://wiki.php.net/rfc/short_list_syntax)
   . Number operators taking numeric strings now emit "A non well formed numeric
     value encountered" E_NOTICEs for leading-numeric strings, and "A
     non-numeric value encountered" E_WARNINGs for non-numeric strings.
index 030913f7f9ce288d04aa6b753b0cdd8a49290978..0c53c75c4da9e2ec3e9dc05ac2455474d994a870 100644 (file)
@@ -154,7 +154,7 @@ Warning: assert(): assert(0 && ($a = function () {
     $x = $a ? $b : $c;
     $x = $a ?: $c;
     $x = $a ?? $b;
-    list($a, $b, $c) = [1, 2 => 'x', 'z' => 'c'];
+    [$a, $b, $c] = [1, 2 => 'x', 'z' => 'c'];
     @foo();
     $y = clone $x;
     yield 1 => 2;
index a9fff55004fbfb30e5403d9779b46f87cbb76550..4e0053edeeab2c300813322c633d29d1f503335f 100644 (file)
@@ -5,6 +5,8 @@
 
 list($a, list($b)) = array(new stdclass, array(new stdclass));
 var_dump($a, $b);
+[$a, [$b]] = array(new stdclass, array(new stdclass));
+var_dump($a, $b);
 
 ?>
 --EXPECT--
@@ -12,3 +14,7 @@ object(stdClass)#1 (0) {
 }
 object(stdClass)#2 (0) {
 }
+object(stdClass)#3 (0) {
+}
+object(stdClass)#4 (0) {
+}
diff --git a/Zend/tests/list_008.phpt b/Zend/tests/list_008.phpt
new file mode 100644 (file)
index 0000000..de8160c
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Assignment to invalid list() value
+--FILE--
+<?php
+
+[42] = [1];
+
+?>
+--EXPECTF--
+Fatal error: Assignments can only happen to writable values in %s on line %d
diff --git a/Zend/tests/list_009.phpt b/Zend/tests/list_009.phpt
new file mode 100644 (file)
index 0000000..c28ca80
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+list with by-reference assignment should fail
+--FILE--
+<?php
+
+$a = [1];
+[&$foo] = $a;
+$foo = 2;
+
+var_dump($a);
+
+?>
+--EXPECTF--
+Fatal error: [] and list() assignments cannot be by reference in %s on line %d
diff --git a/Zend/tests/list_010.phpt b/Zend/tests/list_010.phpt
new file mode 100644 (file)
index 0000000..a89ffda
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Do not allow mixing [] and list()
+--FILE--
+<?php
+
+list([$a]) = [[1]];
+var_dump($a);
+
+?>
+--EXPECTF--
+Fatal error: Cannot mix [] and list() in %s on line %d
diff --git a/Zend/tests/list_011.phpt b/Zend/tests/list_011.phpt
new file mode 100644 (file)
index 0000000..316498c
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Disallow list() usage as if it were an array
+--FILE--
+<?php
+
+var_dump(list(1, 2, 3));
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected ')', expecting '=' in %s on line %d
diff --git a/Zend/tests/list_012.phpt b/Zend/tests/list_012.phpt
new file mode 100644 (file)
index 0000000..072d28c
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Disallow empty elements in normal arrays
+--FILE--
+<?php
+
+var_dump([, 1, 2]);
+
+?>
+--EXPECTF--
+Fatal error: Cannot use empty array elements in arrays in %s on line %d
index 5562479fc387d959c72492bb27d9c8aaf66ecb4a..245d20f31fb9395aebbcb4902fb01e2abd23b51d 100644 (file)
@@ -13,4 +13,4 @@ list($zero, 1 => $one, "foo" => $foo) = $contrivedKeyedAndUnkeyedArrayExample;
 
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected %s in %s on line %d
+Fatal error: Cannot mix keyed and unkeyed array entries in assignments in %s on line %d
index b738ac7ff35e1392bb0bbe41993f578316632034..236d0783fc960d24b17be6a01891f3f3601d97f9 100644 (file)
@@ -1102,11 +1102,6 @@ tail_call:
 simple_list:
                        zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
                        break;
-               case ZEND_AST_LIST:
-                       smart_str_appends(str, "list(");
-                       zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
-                       smart_str_appendc(str, ')');
-                       break;
                case ZEND_AST_ARRAY:
                        smart_str_appendc(str, '[');
                        zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
index ec771003c0c9dc17ccf4c0019094099be96002ec..886b10622873cbc84f556438ae0eb000a220296d 100644 (file)
@@ -42,7 +42,6 @@ enum _zend_ast_kind {
 
        /* list nodes */
        ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,
-       ZEND_AST_LIST,
        ZEND_AST_ARRAY,
        ZEND_AST_ENCAPS_LIST,
        ZEND_AST_EXPR_LIST,
index 7ae54da5bbda3173c34a0012acd8fccde42e9ae5..e401ce243039ee3941e349c12e81c9163d79b019 100644 (file)
@@ -2578,13 +2578,13 @@ static void zend_separate_if_call_and_write(znode *node, zend_ast *ast, uint32_t
 
 void zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t type);
 void zend_compile_assign(znode *result, zend_ast *ast);
-static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node);
+static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node, zend_bool old_style);
 
 static inline void zend_emit_assign_znode(zend_ast *var_ast, znode *value_node) /* {{{ */
 {
        znode dummy_node;
-       if (var_ast->kind == ZEND_AST_LIST) {
-               zend_compile_list_assign(&dummy_node, var_ast, value_node);
+       if (var_ast->kind == ZEND_AST_ARRAY) {
+               zend_compile_list_assign(&dummy_node, var_ast, value_node, var_ast->attr);
        } else {
                zend_ast *assign_ast = zend_ast_create(ZEND_AST_ASSIGN, var_ast,
                        zend_ast_create_znode(value_node));
@@ -2728,18 +2728,35 @@ void zend_compile_static_prop(znode *result, zend_ast *ast, uint32_t type, int d
 }
 /* }}} */
 
-static void zend_compile_unkeyed_list_assign(zend_ast_list *list, znode *expr_node) /* {{{ */
+static void zend_verify_list_assign_target(zend_ast *var_ast, zend_bool old_style) /* {{{ */ {
+       if (var_ast->kind == ZEND_AST_ARRAY) {
+               if (old_style != var_ast->attr) {
+                       zend_error(E_COMPILE_ERROR, "Cannot mix [] and list()");
+               }
+       } else if (!zend_can_write_to_variable(var_ast)) {
+               zend_error(E_COMPILE_ERROR, "Assignments can only happen to writable values");
+       }
+}
+/* }}} */
+
+static void zend_compile_unkeyed_list_assign(zend_ast_list *list, znode *expr_node, zend_bool old_style) /* {{{ */
 {
        uint32_t i;
        zend_bool has_elems = 0;
 
        for (i = 0; i < list->children; ++i) {
-               zend_ast *var_ast = list->child[i];
+               zend_ast *elem_ast = list->child[i];
+               zend_ast *var_ast;
                znode fetch_result, dim_node;
 
-               if (var_ast == NULL) {
+               if (elem_ast == NULL) {
                        continue;
                }
+               if (elem_ast->attr) {
+                       zend_error(E_COMPILE_ERROR, "[] and list() assignments cannot be by reference");
+               }
+
+               var_ast = elem_ast->child[0];
                has_elems = 1;
 
                dim_node.op_type = IS_CONST;
@@ -2749,6 +2766,12 @@ static void zend_compile_unkeyed_list_assign(zend_ast_list *list, znode *expr_no
                        Z_TRY_ADDREF(expr_node->u.constant);
                }
 
+               if (elem_ast->child[1] != NULL) {
+                       zend_error(E_COMPILE_ERROR, "Cannot mix keyed and unkeyed array entries in assignments");
+               }
+
+               zend_verify_list_assign_target(var_ast, old_style);
+
                zend_emit_op(&fetch_result, ZEND_FETCH_LIST, expr_node, &dim_node);
                zend_emit_assign_znode(var_ast, &fetch_result);
        }
@@ -2759,7 +2782,7 @@ static void zend_compile_unkeyed_list_assign(zend_ast_list *list, znode *expr_no
 }
 /* }}} */
 
-static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node) /* {{{ */
+static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node, zend_bool old_style) /* {{{ */
 {
        uint32_t i;
 
@@ -2769,26 +2792,40 @@ static void zend_compile_keyed_list_assign(zend_ast_list *list, znode *expr_node
                zend_ast *key_ast = pair_ast->child[1];
                znode fetch_result, dim_node;
 
+               if (pair_ast->attr) {
+                       zend_error(E_COMPILE_ERROR, "[] and list() assignments cannot be by reference");
+               }
+
                zend_compile_expr(&dim_node, key_ast);
 
                if (expr_node->op_type == IS_CONST) {
                        Z_TRY_ADDREF(expr_node->u.constant);
                }
 
+               if (var_ast == NULL) {
+                       zend_error(E_COMPILE_ERROR, "Cannot use empty array entries in keyed array");
+               }
+
+               if (key_ast == NULL) {
+                       zend_error(E_COMPILE_ERROR, "Cannot mix keyed and unkeyed array entries in assignments");
+               }
+
+               zend_verify_list_assign_target(var_ast, old_style);
+
                zend_emit_op(&fetch_result, ZEND_FETCH_LIST, expr_node, &dim_node);
                zend_emit_assign_znode(var_ast, &fetch_result);
        }
 }
 /* }}} */
 
-static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node) /* {{{ */
+static void zend_compile_list_assign(znode *result, zend_ast *ast, znode *expr_node, zend_bool old_style) /* {{{ */
 {
        zend_ast_list *list = zend_ast_get_list(ast);
 
-       if (list->children > 0 && list->child[0] != NULL && list->child[0]->kind == ZEND_AST_ARRAY_ELEM) {
-               zend_compile_keyed_list_assign(list, expr_node);
+       if (list->children > 0 && list->child[0] != NULL && list->child[0]->child[1] != NULL /* has key */) {
+               zend_compile_keyed_list_assign(list, expr_node, old_style);
        } else {
-               zend_compile_unkeyed_list_assign(list, expr_node);
+               zend_compile_unkeyed_list_assign(list, expr_node, old_style);
        }
 
        *result = *expr_node;
@@ -2838,13 +2875,16 @@ zend_bool zend_list_has_assign_to(zend_ast *list_ast, zend_string *name) /* {{{
        zend_ast_list *list = zend_ast_get_list(list_ast);
        uint32_t i;
        for (i = 0; i < list->children; i++) {
-               zend_ast *var_ast = list->child[i];
-               if (!var_ast) {
+               zend_ast *elem_ast = list->child[i];
+               zend_ast *var_ast;
+
+               if (!elem_ast) {
                        continue;
                }
+               var_ast = elem_ast->child[0];
 
                /* Recursively check nested list()s */
-               if (var_ast->kind == ZEND_AST_LIST && zend_list_has_assign_to(var_ast, name)) {
+               if (var_ast->kind == ZEND_AST_ARRAY && zend_list_has_assign_to(var_ast, name)) {
                        return 1;
                }
 
@@ -2926,7 +2966,7 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
 
                        zend_emit_op_data(&expr_node);
                        return;
-               case ZEND_AST_LIST:
+               case ZEND_AST_ARRAY:
                        if (zend_list_has_assign_to_self(var_ast, expr_ast)) {
                                /* list($a, $b) = $a should evaluate the right $a first */
                                zend_compile_simple_var_no_cv(&expr_node, expr_ast, BP_VAR_R, 0);
@@ -2934,7 +2974,7 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
                                zend_compile_expr(&expr_node, expr_ast);
                        }
 
-                       zend_compile_list_assign(result, var_ast, &expr_node);
+                       zend_compile_list_assign(result, var_ast, &expr_node, var_ast->attr);
                        return;
                EMPTY_SWITCH_DEFAULT_CASE();
        }
@@ -4327,7 +4367,7 @@ void zend_compile_foreach(zend_ast *ast) /* {{{ */
                if (key_ast->kind == ZEND_AST_REF) {
                        zend_error_noreturn(E_COMPILE_ERROR, "Key element cannot be a reference");
                }
-               if (key_ast->kind == ZEND_AST_LIST) {
+               if (key_ast->kind == ZEND_AST_ARRAY) {
                        zend_error_noreturn(E_COMPILE_ERROR, "Cannot use list as key element");
                }
        }
@@ -6365,14 +6405,22 @@ static zend_bool zend_try_ct_eval_array(zval *result, zend_ast *ast) /* {{{ */
        uint32_t i;
        zend_bool is_constant = 1;
 
+       if (ast->attr) {
+               zend_error(E_COMPILE_ERROR, "Cannot use list() as standalone expression");
+       }
+
        /* First ensure that *all* child nodes are constant and by-val */
        for (i = 0; i < list->children; ++i) {
                zend_ast *elem_ast = list->child[i];
-               zend_bool by_ref = elem_ast->attr;
+
+               if (elem_ast == NULL) {
+                       zend_error(E_COMPILE_ERROR, "Cannot use empty array elements in arrays");
+               }
+
                zend_eval_const_expr(&elem_ast->child[0]);
                zend_eval_const_expr(&elem_ast->child[1]);
 
-               if (by_ref || elem_ast->child[0]->kind != ZEND_AST_ZVAL
+               if (elem_ast->attr /* by_ref */ || elem_ast->child[0]->kind != ZEND_AST_ZVAL
                        || (elem_ast->child[1] && elem_ast->child[1]->kind != ZEND_AST_ZVAL)
                ) {
                        is_constant = 0;
@@ -6990,9 +7038,16 @@ void zend_compile_array(znode *result, zend_ast *ast) /* {{{ */
 
        for (i = 0; i < list->children; ++i) {
                zend_ast *elem_ast = list->child[i];
-               zend_ast *value_ast = elem_ast->child[0];
-               zend_ast *key_ast = elem_ast->child[1];
-               zend_bool by_ref = elem_ast->attr;
+               zend_ast *value_ast, *key_ast;
+               zend_bool by_ref;
+
+               if (elem_ast == NULL) {
+                       zend_error(E_COMPILE_ERROR, "Cannot use empty array elements in arrays");
+               }
+
+               value_ast = elem_ast->child[0];
+               key_ast = elem_ast->child[1];
+               by_ref = elem_ast->attr;
 
                znode value_node, key_node, *key_node_ptr = NULL;
 
index bedb7e62bc9eac36f52d9e8100ff278145015e2a..5f233d6a3b8689c78c88ef4cda91fad1554c9103 100644 (file)
@@ -241,7 +241,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %type <ast> exit_expr scalar backticks_expr lexical_var function_call member_name property_name
 %type <ast> variable_class_name dereferencable_scalar constant dereferencable
 %type <ast> callable_expr callable_variable static_member new_variable
-%type <ast> unkeyed_assignment_list_element keyed_assignment_list_element array_pair
 %type <ast> encaps_var encaps_var_offset isset_variables
 %type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
 %type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
@@ -250,8 +249,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %type <ast> non_empty_parameter_list argument_list non_empty_argument_list property_list
 %type <ast> class_const_list class_const_decl name_list trait_adaptations method_body non_empty_for_exprs
 %type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
-%type <ast> lexical_var_list encaps_list array_pair_list non_empty_array_pair_list
-%type <ast> assignment_list unkeyed_assignment_list keyed_assignment_list
+%type <ast> lexical_var_list encaps_list
+%type <ast> array_pair non_empty_array_pair_list array_pair_list
 %type <ast> isset_variable type return_type
 %type <ast> identifier
 
@@ -545,7 +544,8 @@ implements_list:
 foreach_variable:
                variable                        { $$ = $1; }
        |       '&' variable            { $$ = zend_ast_create(ZEND_AST_REF, $2); }
-       |       T_LIST '(' assignment_list ')' { $$ = $3; }
+       |       T_LIST '(' array_pair_list ')' { $3->attr = 1; $$ = $3; }
+       |       '[' array_pair_list ']' { $$ = $2; }
 ;
 
 for_statement:
@@ -860,8 +860,10 @@ new_expr:
 ;
 
 expr_without_variable:
-               T_LIST '(' assignment_list ')' '=' expr
-                       { $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $6); }
+               T_LIST '(' array_pair_list ')' '=' expr
+                       { $3->attr = 1; $$ = zend_ast_create(ZEND_AST_ASSIGN, $3, $6); }
+       |       '[' array_pair_list ']' '=' expr
+                       { $$ = zend_ast_create(ZEND_AST_ASSIGN, $2, $5); }
        |       variable '=' expr
                        { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }
        |       variable '=' '&' variable
@@ -1086,11 +1088,6 @@ constant:
                        { $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $3); }
 ;
 
-possible_comma:
-               /* empty */
-       |       ','
-;
-
 expr:
                variable                                        { $$ = $1; }
        |       expr_without_variable           { $$ = $1; }
@@ -1180,44 +1177,9 @@ property_name:
        |       simple_variable { $$ = zend_ast_create(ZEND_AST_VAR, $1); }
 ;
 
-assignment_list:
-               unkeyed_assignment_list
-                       { $$ = $1; }
-       |       keyed_assignment_list possible_comma
-                       { $$ = $1; }
-;
-
-unkeyed_assignment_list:
-               unkeyed_assignment_list ',' unkeyed_assignment_list_element
-                       { $$ = zend_ast_list_add($1, $3); }
-       |       unkeyed_assignment_list_element
-                       { $$ = zend_ast_create_list(1, ZEND_AST_LIST, $1); }
-;
-
-unkeyed_assignment_list_element:
-               variable                                                { $$ = $1; }
-       |       T_LIST '(' assignment_list ')'  { $$ = $3; }
-       |       /* empty */                                             { $$ = NULL; }
-;
-
-keyed_assignment_list:
-               keyed_assignment_list ',' keyed_assignment_list_element
-                       { $$ = zend_ast_list_add($1, $3); }
-       |       keyed_assignment_list_element
-                       { $$ = zend_ast_create_list(1, ZEND_AST_LIST, $1); }
-;
-
-keyed_assignment_list_element:
-               expr T_DOUBLE_ARROW variable
-                       { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); }
-       |       expr T_DOUBLE_ARROW T_LIST '(' assignment_list ')'
-                       { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $5, $1); }
-;
-
-
 array_pair_list:
                /* empty */ { $$ = zend_ast_create_list(0, ZEND_AST_ARRAY); }
-       |       non_empty_array_pair_list possible_comma { $$ = $1; }
+       |       non_empty_array_pair_list { /* allow single trailing comma */ zend_ast_list *list = zend_ast_get_list($$ = $1); if (list->child[list->children - 1] == NULL) { list->children--; } }
 ;
 
 non_empty_array_pair_list:
@@ -1225,16 +1187,27 @@ non_empty_array_pair_list:
                        { $$ = zend_ast_list_add($1, $3); }
        |       array_pair
                        { $$ = zend_ast_create_list(1, ZEND_AST_ARRAY, $1); }
+       |       non_empty_array_pair_list ',' /* empty, for LHS array lists */
+                       { $$ = zend_ast_list_add($1, NULL); }
+       |       ','
+                       { $$ = zend_ast_create_list(1, ZEND_AST_ARRAY, NULL); }
+       |       ',' array_pair
+                       { $$ = zend_ast_create_list(2, ZEND_AST_ARRAY, NULL, $2); }
 ;
 
 array_pair:
                expr T_DOUBLE_ARROW expr
                        { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); }
-       |       expr { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); }
+       |       expr
+                       { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); }
        |       expr T_DOUBLE_ARROW '&' variable
                        { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $4, $1); }
        |       '&' variable
                        { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $2, NULL); }
+       |       expr T_DOUBLE_ARROW T_LIST '(' array_pair_list ')'
+                       { $5->attr = 1; $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $5, $1); }
+       |       T_LIST '(' array_pair_list ')'
+                       { $3->attr = 1; $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, NULL); }
 ;
 
 encaps_list: