. 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).
(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.
$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;
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--
}
object(stdClass)#2 (0) {
}
+object(stdClass)#3 (0) {
+}
+object(stdClass)#4 (0) {
+}
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
?>
--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
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);
/* 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,
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));
}
/* }}} */
-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;
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);
}
}
/* }}} */
-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;
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;
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;
}
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);
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();
}
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");
}
}
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;
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;
%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
%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
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:
;
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
{ $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $3); }
;
-possible_comma:
- /* empty */
- | ','
-;
-
expr:
variable { $$ = $1; }
| expr_without_variable { $$ = $1; }
| 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:
{ $$ = 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: