From: Bob Weinand Date: Mon, 4 Apr 2016 22:34:42 +0000 (+0200) Subject: Allow for [] = $array; (alias for list()) X-Git-Tag: php-7.1.0alpha1~102 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4f077aee836ad7d8335cf62629a8364bdf939db9;p=php Allow for [] = $array; (alias for list()) --- diff --git a/NEWS b/NEWS index 6ae33d3700..5bb69bd52a 100644 --- 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). diff --git a/UPGRADING b/UPGRADING index e2bac16423..dcf80a6029 100644 --- 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. diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 030913f7f9..0c53c75c4d 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -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; diff --git a/Zend/tests/list_001.phpt b/Zend/tests/list_001.phpt index a9fff55004..4e0053edee 100644 --- a/Zend/tests/list_001.phpt +++ b/Zend/tests/list_001.phpt @@ -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 index 0000000000..de8160c77e --- /dev/null +++ b/Zend/tests/list_008.phpt @@ -0,0 +1,10 @@ +--TEST-- +Assignment to invalid list() value +--FILE-- + +--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 index 0000000000..c28ca8000a --- /dev/null +++ b/Zend/tests/list_009.phpt @@ -0,0 +1,14 @@ +--TEST-- +list with by-reference assignment should fail +--FILE-- + +--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 index 0000000000..a89ffda5cd --- /dev/null +++ b/Zend/tests/list_010.phpt @@ -0,0 +1,11 @@ +--TEST-- +Do not allow mixing [] and list() +--FILE-- + +--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 index 0000000000..316498c411 --- /dev/null +++ b/Zend/tests/list_011.phpt @@ -0,0 +1,10 @@ +--TEST-- +Disallow list() usage as if it were an array +--FILE-- + +--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 index 0000000000..072d28c01d --- /dev/null +++ b/Zend/tests/list_012.phpt @@ -0,0 +1,10 @@ +--TEST-- +Disallow empty elements in normal arrays +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use empty array elements in arrays in %s on line %d diff --git a/Zend/tests/list_mixed_keyed_unkeyed.phpt b/Zend/tests/list_mixed_keyed_unkeyed.phpt index 5562479fc3..245d20f31f 100644 --- a/Zend/tests/list_mixed_keyed_unkeyed.phpt +++ b/Zend/tests/list_mixed_keyed_unkeyed.phpt @@ -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 diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index b738ac7ff3..236d0783fc 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -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); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index ec771003c0..886b106228 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -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, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 7ae54da5bb..e401ce2430 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index bedb7e62bc..5f233d6a3b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -241,7 +241,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type exit_expr scalar backticks_expr lexical_var function_call member_name property_name %type variable_class_name dereferencable_scalar constant dereferencable %type callable_expr callable_variable static_member new_variable -%type unkeyed_assignment_list_element keyed_assignment_list_element array_pair %type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type 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 non_empty_parameter_list argument_list non_empty_argument_list property_list %type class_const_list class_const_decl name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars -%type lexical_var_list encaps_list array_pair_list non_empty_array_pair_list -%type assignment_list unkeyed_assignment_list keyed_assignment_list +%type lexical_var_list encaps_list +%type array_pair non_empty_array_pair_list array_pair_list %type isset_variable type return_type %type 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: