diff --git a/Zend/tests/block_expr/coalesce.phpt b/Zend/tests/block_expr/coalesce.phpt new file mode 100644 index 0000000000000..391c92e37b241 --- /dev/null +++ b/Zend/tests/block_expr/coalesce.phpt @@ -0,0 +1,21 @@ +--TEST-- +Coalesce block +--FILE-- + +--EXPECT-- +Executed +int(42) +int(42) diff --git a/Zend/tests/block_expr/coalesce_assign.phpt b/Zend/tests/block_expr/coalesce_assign.phpt new file mode 100644 index 0000000000000..4a0618b6e83b7 --- /dev/null +++ b/Zend/tests/block_expr/coalesce_assign.phpt @@ -0,0 +1,21 @@ +--TEST-- +Coalesce assignment block +--FILE-- + +--EXPECT-- +Executed +int(42) +int(42) diff --git a/Zend/tests/match/block_arg_return.phpt b/Zend/tests/match/block_arg_return.phpt new file mode 100644 index 0000000000000..6bca507917a66 --- /dev/null +++ b/Zend/tests/match/block_arg_return.phpt @@ -0,0 +1,10 @@ +--TEST-- +Match expression block must not use return +--FILE-- + { return; } +}); +?> +--EXPECTF-- +Fatal error: Match expression whose result is used must not contain return, break, continue or goto in %s on line %d diff --git a/Zend/tests/match/block_basic.phpt b/Zend/tests/match/block_basic.phpt new file mode 100644 index 0000000000000..7e36804a095b5 --- /dev/null +++ b/Zend/tests/match/block_basic.phpt @@ -0,0 +1,35 @@ +--TEST-- +Basic match blocks +--FILE-- + { 1 }, + 2 => { + $x = 2; + $x + }, + 3 => { + foo(); + bar() + }, + }); +} + +test(1); +test(2); +test(3); +?> +--EXPECT-- +int(1) +int(2) +foo() +int(3) diff --git a/Zend/tests/match/block_expr_break_escape.phpt b/Zend/tests/match/block_expr_break_escape.phpt new file mode 100644 index 0000000000000..3e18c29fe9152 --- /dev/null +++ b/Zend/tests/match/block_expr_break_escape.phpt @@ -0,0 +1,10 @@ +--TEST-- +Match expression block must not use break +--FILE-- + { break; }, +}); +?> +--EXPECTF-- +Fatal error: Match expression whose result is used must not contain return, break, continue or goto in %s on line %d diff --git a/Zend/tests/match/block_expr_break_no_escape.phpt b/Zend/tests/match/block_expr_break_no_escape.phpt new file mode 100644 index 0000000000000..f9879ff74ade8 --- /dev/null +++ b/Zend/tests/match/block_expr_break_no_escape.phpt @@ -0,0 +1,17 @@ +--TEST-- +Match expression block may use break if block is not escaped +--FILE-- + { + foreach ([1, 2, 3] as $value) { + echo $value, "\n"; + break; + } + 42 + }, +}); +?> +--EXPECT-- +1 +int(42) diff --git a/Zend/tests/match/block_expr_goto_escape.phpt b/Zend/tests/match/block_expr_goto_escape.phpt new file mode 100644 index 0000000000000..b82b401df79cc --- /dev/null +++ b/Zend/tests/match/block_expr_goto_escape.phpt @@ -0,0 +1,13 @@ +--TEST-- +Match expression block must not use goto +--FILE-- + { + goto after; + }, +}); +after: +?> +--EXPECTF-- +Fatal error: Match expression whose result is used must not contain return, break, continue or goto in %s on line %d diff --git a/Zend/tests/match/block_expr_goto_into.phpt b/Zend/tests/match/block_expr_goto_into.phpt new file mode 100644 index 0000000000000..e3d528592fd82 --- /dev/null +++ b/Zend/tests/match/block_expr_goto_into.phpt @@ -0,0 +1,14 @@ +--TEST-- +May not goto into match expression block +--FILE-- + { +in: + 42 + }, +}); +?> +--EXPECTF-- +Fatal error: 'goto' into loop or switch statement is disallowed in %s on line %d diff --git a/Zend/tests/match/block_expr_no_result.phpt b/Zend/tests/match/block_expr_no_result.phpt new file mode 100644 index 0000000000000..e44ad9d31919b --- /dev/null +++ b/Zend/tests/match/block_expr_no_result.phpt @@ -0,0 +1,20 @@ +--TEST-- +Match expression block must return a value +--FILE-- + { + echo "Not returning anything\n"; + }, + }); +} +try { + test(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Not returning anything +NULL diff --git a/Zend/tests/match/block_expr_return.phpt b/Zend/tests/match/block_expr_return.phpt new file mode 100644 index 0000000000000..b6e64a20bc1e9 --- /dev/null +++ b/Zend/tests/match/block_expr_return.phpt @@ -0,0 +1,10 @@ +--TEST-- +Match expression block must not use return +--FILE-- + { return; }, +}); +?> +--EXPECTF-- +Fatal error: Match expression whose result is used must not contain return, break, continue or goto in %s on line %d diff --git a/Zend/tests/match/block_expr_throw.phpt b/Zend/tests/match/block_expr_throw.phpt new file mode 100644 index 0000000000000..01841451e614b --- /dev/null +++ b/Zend/tests/match/block_expr_throw.phpt @@ -0,0 +1,20 @@ +--TEST-- +Throwing match expression block must clean up live-vars +--FILE-- + { throw new Exception('Exception with live var'); }, + }); +} + +try { + throw_(1); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Exception with live var diff --git a/Zend/tests/match/block_stmt_break_escape.phpt b/Zend/tests/match/block_stmt_break_escape.phpt new file mode 100644 index 0000000000000..967e704f97f62 --- /dev/null +++ b/Zend/tests/match/block_stmt_break_escape.phpt @@ -0,0 +1,16 @@ +--TEST-- +Match statement block may break out of block +--FILE-- + { + echo "Before break\n"; + break; + echo "After break\n"; + }, +}; +echo "After match\n"; +?> +--EXPECT-- +Before break +After match diff --git a/Zend/tests/match/block_stmt_continue_escape.phpt b/Zend/tests/match/block_stmt_continue_escape.phpt new file mode 100644 index 0000000000000..f3c82fda0066a --- /dev/null +++ b/Zend/tests/match/block_stmt_continue_escape.phpt @@ -0,0 +1,17 @@ +--TEST-- +Match statement block may continue out of block, with a warning +--FILE-- + { + echo "Before continue\n"; + continue; + echo "After continue\n"; + }, +}; +echo "After match\n"; +?> +--EXPECTF-- +Warning: "continue" targeting switch is equivalent to "break" in %s on line %d +Before continue +After match diff --git a/Zend/tests/match/block_stmt_goto_escape.phpt b/Zend/tests/match/block_stmt_goto_escape.phpt new file mode 100644 index 0000000000000..6bef5094b3235 --- /dev/null +++ b/Zend/tests/match/block_stmt_goto_escape.phpt @@ -0,0 +1,17 @@ +--TEST-- +May escape match statement block with goto +--FILE-- + { + echo "Before goto\n"; + goto after; + echo "After goto\n"; + }, +}; +after: +echo "After match\n"; +?> +--EXPECT-- +Before goto +After match diff --git a/Zend/tests/match/block_stmt_goto_into.phpt b/Zend/tests/match/block_stmt_goto_into.phpt new file mode 100644 index 0000000000000..56d08cc705079 --- /dev/null +++ b/Zend/tests/match/block_stmt_goto_into.phpt @@ -0,0 +1,14 @@ +--TEST-- +May not goto into match statement block +--FILE-- + { +in: + echo "Inside match block\n"; + }, +}; +?> +--EXPECTF-- +Fatal error: 'goto' into loop or switch statement is disallowed in %s on line %d diff --git a/Zend/tests/match/block_stmt_with_result.phpt b/Zend/tests/match/block_stmt_with_result.phpt new file mode 100644 index 0000000000000..3219b50212251 --- /dev/null +++ b/Zend/tests/match/block_stmt_with_result.phpt @@ -0,0 +1,11 @@ +--TEST-- +Match statement block must not return a value +--FILE-- + { new stdClass }, +}; +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index b5001a9ff8f54..face8ccc5490b 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -148,6 +148,7 @@ enum _zend_ast_kind { ZEND_AST_ATTRIBUTE, ZEND_AST_MATCH, ZEND_AST_MATCH_ARM, + ZEND_AST_BLOCK_EXPR, ZEND_AST_NAMED_ARG, /* 3 child nodes */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 87bfd0b1e94e5..84c93f1faa986 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -679,7 +679,7 @@ void zend_stop_lexing(void) } static inline void zend_begin_loop( - uint8_t free_opcode, const znode *loop_var, bool is_switch) /* {{{ */ + uint8_t free_opcode, const znode *loop_var, zend_brk_cont_kind kind) /* {{{ */ { zend_brk_cont_element *brk_cont_element; int parent = CG(context).current_brk_cont; @@ -688,7 +688,7 @@ static inline void zend_begin_loop( CG(context).current_brk_cont = CG(context).last_brk_cont; brk_cont_element = get_next_brk_cont_element(); brk_cont_element->parent = parent; - brk_cont_element->is_switch = is_switch; + brk_cont_element->kind = kind; if (loop_var && (loop_var->op_type & (IS_VAR|IS_TMP_VAR))) { uint32_t start = get_next_op_number(); @@ -5055,8 +5055,18 @@ static bool zend_handle_loops_and_finally_ex(zend_long depth, znode *return_valu if (!loop_var) { return 1; } + + zend_brk_cont_element *brk_ctrl_element = CG(context).current_brk_cont != -1 + ? &CG(context).brk_cont_array[CG(context).current_brk_cont] + : NULL; + if (brk_ctrl_element && brk_ctrl_element->kind == ZEND_BRK_CONT_KIND_MATCH_EXPR) { + zend_error_noreturn(E_COMPILE_ERROR, "Match expression whose result is used must not contain return, break, continue or goto"); + } + base = zend_stack_base(&CG(loop_var_stack)); for (; loop_var >= base; loop_var--) { + bool decrement_depth = false; + if (loop_var->opcode == ZEND_FAST_CALL) { zend_op *opline = get_next_op(); @@ -5079,7 +5089,7 @@ static bool zend_handle_loops_and_finally_ex(zend_long depth, znode *return_valu return 1; } else if (loop_var->opcode == ZEND_NOP) { /* Loop doesn't have freeable variable */ - depth--; + decrement_depth = true; } else { zend_op *opline; @@ -5089,8 +5099,21 @@ static bool zend_handle_loops_and_finally_ex(zend_long depth, znode *return_valu opline->op1_type = loop_var->var_type; opline->op1.var = loop_var->var_num; opline->extended_value = ZEND_FREE_ON_RETURN; + decrement_depth = true; + } + if (decrement_depth) { depth--; - } + if (brk_ctrl_element) { + if (brk_ctrl_element->parent != -1) { + brk_ctrl_element = &CG(context).brk_cont_array[brk_ctrl_element->parent]; + if (brk_ctrl_element->kind == ZEND_BRK_CONT_KIND_MATCH_EXPR) { + zend_error_noreturn(E_COMPILE_ERROR, "Match expression whose result is used must not contain return, break, continue or goto"); + } + } else { + brk_ctrl_element = NULL; + } + } + } } return (depth == 0); } @@ -5214,11 +5237,13 @@ static void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */ zend_compile_expr(&expr_node, expr_ast); zend_op *opline = zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL); - if (result) { + if (result || CG(context).in_block_expr) { /* Mark this as an "expression throw" for opcache. */ opline->extended_value = ZEND_THROW_IS_EXPR; - result->op_type = IS_CONST; - ZVAL_TRUE(&result->u.constant); + if (result) { + result->op_type = IS_CONST; + ZVAL_TRUE(&result->u.constant); + } } } /* }}} */ @@ -5268,7 +5293,8 @@ static void zend_compile_break_continue(zend_ast *ast) /* {{{ */ ZEND_ASSERT(cur != -1); } - if (CG(context).brk_cont_array[cur].is_switch) { + if (CG(context).brk_cont_array[cur].kind == ZEND_BRK_CONT_KIND_SWITCH_MATCH_STMT + || CG(context).brk_cont_array[cur].kind == ZEND_BRK_CONT_KIND_MATCH_EXPR) { if (depth == 1) { if (CG(context).brk_cont_array[cur].parent == -1) { zend_error(E_WARNING, @@ -5406,7 +5432,7 @@ static void zend_compile_while(zend_ast *ast) /* {{{ */ opnum_jmp = zend_emit_jump(0); - zend_begin_loop(ZEND_NOP, NULL, 0); + zend_begin_loop(ZEND_NOP, NULL, ZEND_BRK_CONT_KIND_LOOP); opnum_start = get_next_op_number(); zend_compile_stmt(stmt_ast); @@ -5429,7 +5455,7 @@ static void zend_compile_do_while(zend_ast *ast) /* {{{ */ znode cond_node; uint32_t opnum_start, opnum_cond; - zend_begin_loop(ZEND_NOP, NULL, 0); + zend_begin_loop(ZEND_NOP, NULL, ZEND_BRK_CONT_KIND_LOOP); opnum_start = get_next_op_number(); zend_compile_stmt(stmt_ast); @@ -5480,7 +5506,7 @@ static void zend_compile_for(zend_ast *ast) /* {{{ */ opnum_jmp = zend_emit_jump(0); - zend_begin_loop(ZEND_NOP, NULL, 0); + zend_begin_loop(ZEND_NOP, NULL, ZEND_BRK_CONT_KIND_LOOP); opnum_start = get_next_op_number(); zend_compile_stmt(stmt_ast); @@ -5542,7 +5568,7 @@ static void zend_compile_foreach(zend_ast *ast) /* {{{ */ opnum_reset = get_next_op_number(); opline = zend_emit_op(&reset_node, by_ref ? ZEND_FE_RESET_RW : ZEND_FE_RESET_R, &expr_node, NULL); - zend_begin_loop(ZEND_FE_FREE, &reset_node, 0); + zend_begin_loop(ZEND_FE_FREE, &reset_node, ZEND_BRK_CONT_KIND_LOOP); opnum_fetch = get_next_op_number(); opline = zend_emit_op(NULL, by_ref ? ZEND_FE_FETCH_RW : ZEND_FE_FETCH_R, &reset_node, NULL); @@ -5715,7 +5741,7 @@ static void zend_compile_switch(zend_ast *ast) /* {{{ */ zend_compile_expr(&expr_node, expr_ast); - zend_begin_loop(ZEND_FREE, &expr_node, 1); + zend_begin_loop(ZEND_FREE, &expr_node, ZEND_BRK_CONT_KIND_SWITCH_MATCH_STMT); case_node.op_type = IS_TMP_VAR; case_node.u.op.var = get_temporary_variable(); @@ -5877,6 +5903,8 @@ static bool can_match_use_jumptable(zend_ast_list *arms) { return 1; } +static void zend_compile_stmt_list(zend_ast *ast); + static void zend_compile_match(znode *result, zend_ast *ast) { zend_ast *expr_ast = ast->child[0]; @@ -5887,6 +5915,8 @@ static void zend_compile_match(znode *result, zend_ast *ast) znode expr_node; zend_compile_expr(&expr_node, expr_ast); + zend_begin_loop(ZEND_FREE, &expr_node, result ? ZEND_BRK_CONT_KIND_MATCH_EXPR : ZEND_BRK_CONT_KIND_SWITCH_MATCH_STMT); + znode case_node; case_node.op_type = IS_TMP_VAR; case_node.u.op.var = get_temporary_variable(); @@ -6028,20 +6058,23 @@ static void zend_compile_match(znode *result, zend_ast *ast) znode body_node; zend_compile_expr(&body_node, body_ast); - - if (is_first_case) { - zend_emit_op_tmp(result, ZEND_QM_ASSIGN, &body_node, NULL); - is_first_case = 0; + if (result) { + if (is_first_case) { + zend_emit_op_tmp(result, ZEND_QM_ASSIGN, &body_node, NULL); + is_first_case = 0; + } else { + zend_op *opline_qm_assign = zend_emit_op(NULL, ZEND_QM_ASSIGN, &body_node, NULL); + SET_NODE(opline_qm_assign->result, result); + } } else { - zend_op *opline_qm_assign = zend_emit_op(NULL, ZEND_QM_ASSIGN, &body_node, NULL); - SET_NODE(opline_qm_assign->result, result); + zend_do_free(&body_node); } jmp_end_opnums[i] = zend_emit_jump(0); } // Initialize result in case there is no arm - if (arms->children == 0) { + if (result && arms->children == 0) { result->op_type = IS_CONST; ZVAL_NULL(&result->u.constant); } @@ -6050,6 +6083,8 @@ static void zend_compile_match(znode *result, zend_ast *ast) zend_update_jump_target_to_next(jmp_end_opnums[i]); } + zend_end_loop(get_next_op_number(), &expr_node); + if (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) { zend_op *opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL); opline->extended_value = ZEND_FREE_SWITCH; @@ -9465,11 +9500,13 @@ static void zend_compile_exit(znode *result, zend_ast *ast) /* {{{ */ } zend_op *opline = zend_emit_op(NULL, ZEND_EXIT, &expr_node, NULL); - if (result) { + if (result || CG(context).in_block_expr) { /* Mark this as an "expression throw" for opcache. */ opline->extended_value = ZEND_THROW_IS_EXPR; - result->op_type = IS_CONST; - ZVAL_TRUE(&result->u.constant); + if (result) { + result->op_type = IS_CONST; + ZVAL_TRUE(&result->u.constant); + } } } /* }}} */ @@ -10438,6 +10475,10 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_EXIT: zend_compile_expr(NULL, ast); break; + case ZEND_AST_MATCH: { + zend_compile_match(NULL, ast); + break; + } default: { znode result; @@ -10452,6 +10493,21 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_block_expr(znode *result, zend_ast *ast) +{ + bool prev_in_block_expr = CG(context).in_block_expr; + CG(context).in_block_expr = true; + zend_compile_stmt_list(ast->child[0]); + zend_ast *result_expr_ast = ast->child[1]; + if (result_expr_ast) { + zend_compile_expr(result, result_expr_ast); + } else { + result->op_type = IS_CONST; + ZVAL_NULL(&result->u.constant); + } + CG(context).in_block_expr = prev_in_block_expr; +} + static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ { /* CG(zend_lineno) = ast->lineno; */ @@ -10590,6 +10646,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_MATCH: zend_compile_match(result, ast); return; + case ZEND_AST_BLOCK_EXPR: + zend_compile_block_expr(result, ast); + return; default: ZEND_ASSERT(0 /* not supported */); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 765e54fb56ee8..84a59ae1e80ff 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -145,13 +145,20 @@ struct _zend_op { uint8_t result_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */ }; +typedef enum { + ZEND_BRK_CONT_KIND_LOOP, + /* switch or match with unused result. */ + ZEND_BRK_CONT_KIND_SWITCH_MATCH_STMT, + /* match with used result. */ + ZEND_BRK_CONT_KIND_MATCH_EXPR, +} zend_brk_cont_kind; typedef struct _zend_brk_cont_element { int start; int cont; int brk; int parent; - bool is_switch; + zend_brk_cont_kind kind; } zend_brk_cont_element; typedef struct _zend_label { @@ -190,6 +197,7 @@ typedef struct _zend_oparray_context { int last_brk_cont; zend_brk_cont_element *brk_cont_array; HashTable *labels; + bool in_block_expr; } zend_oparray_context; /* Class, property and method flags class|meth.|prop.|const*/ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 298eaf95ad055..0d9c0b449eb7c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -70,7 +70,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %left '^' %left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG %nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP -%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL +%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL T_THIN_ARROW_LEFT %left '.' %left T_SL T_SR %left '+' '-' @@ -216,6 +216,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_OBJECT_OPERATOR "'->'" %token T_NULLSAFE_OBJECT_OPERATOR "'?->'" %token T_DOUBLE_ARROW "'=>'" +%token T_THIN_ARROW_LEFT "'<-'" %token T_COMMENT "comment" %token T_DOC_COMMENT "doc comment" %token T_OPEN_TAG "open tag" @@ -276,7 +277,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type inline_function union_type_element union_type intersection_type %type attributed_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name -%type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list +%type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list match_arm_body block_expr %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers @@ -726,9 +727,9 @@ non_empty_match_arm_list: ; match_arm: - match_arm_cond_list possible_comma T_DOUBLE_ARROW expr + match_arm_cond_list possible_comma T_DOUBLE_ARROW match_arm_body { $$ = zend_ast_create(ZEND_AST_MATCH_ARM, $1, $4); } - | T_DEFAULT possible_comma T_DOUBLE_ARROW expr + | T_DEFAULT possible_comma T_DOUBLE_ARROW match_arm_body { $$ = zend_ast_create(ZEND_AST_MATCH_ARM, NULL, $4); } ; @@ -737,6 +738,14 @@ match_arm_cond_list: | match_arm_cond_list ',' expr { $$ = zend_ast_list_add($1, $3); } ; +match_arm_body: + expr { $$ = $1; } + | block_expr { $$ = $1; } +; + +block_expr: + '{' inner_statement_list optional_expr '}' { $$ = zend_ast_create(ZEND_AST_BLOCK_EXPR, $2, $3); } +; while_statement: statement { $$ = $1; } @@ -1170,6 +1179,8 @@ expr: { $$ = zend_ast_create_assign_op(ZEND_SR, $1, $3); } | variable T_COALESCE_EQUAL expr { $$ = zend_ast_create(ZEND_AST_ASSIGN_COALESCE, $1, $3); } + | variable T_COALESCE_EQUAL block_expr + { $$ = zend_ast_create(ZEND_AST_ASSIGN_COALESCE, $1, $3); } | variable T_INC { $$ = zend_ast_create(ZEND_AST_POST_INC, $1); } | T_INC variable { $$ = zend_ast_create(ZEND_AST_PRE_INC, $2); } | variable T_DEC { $$ = zend_ast_create(ZEND_AST_POST_DEC, $1); } @@ -1211,6 +1222,9 @@ expr: { $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); } | expr '<' expr { $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); } + /* BC for $foo<-$bar */ + | expr T_THIN_ARROW_LEFT expr + { $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, zend_ast_create(ZEND_AST_UNARY_MINUS, $3)); } | expr T_IS_SMALLER_OR_EQUAL expr { $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER_OR_EQUAL, $1, $3); } | expr '>' expr @@ -1232,6 +1246,8 @@ expr: { $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, NULL, $4); } | expr T_COALESCE expr { $$ = zend_ast_create(ZEND_AST_COALESCE, $1, $3); } + | expr T_COALESCE block_expr + { $$ = zend_ast_create(ZEND_AST_COALESCE, $1, $3); } | internal_functions_in_yacc { $$ = $1; } | T_INT_CAST expr { $$ = zend_ast_create_cast(IS_LONG, $2); } | T_DOUBLE_CAST expr { $$ = zend_ast_create_cast(IS_DOUBLE, $2); } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 054ed7bdc1ef6..21daf58c45b59 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1603,6 +1603,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ goto restart; } +"<-" { + RETURN_TOKEN(T_THIN_ARROW_LEFT); +} + "::" { RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 77b67de6b1c6e..078b6f9f1886b 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -154,6 +154,7 @@ char *get_token_type_name(int token_type) case T_OBJECT_OPERATOR: return "T_OBJECT_OPERATOR"; case T_NULLSAFE_OBJECT_OPERATOR: return "T_NULLSAFE_OBJECT_OPERATOR"; case T_DOUBLE_ARROW: return "T_DOUBLE_ARROW"; + case T_THIN_ARROW_LEFT: return "T_THIN_ARROW_LEFT"; case T_COMMENT: return "T_COMMENT"; case T_DOC_COMMENT: return "T_DOC_COMMENT"; case T_OPEN_TAG: return "T_OPEN_TAG"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 42c69c4f82eff..0dd6866eda822 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -637,6 +637,11 @@ * @cvalue T_DOUBLE_ARROW */ const T_DOUBLE_ARROW = UNKNOWN; +/** + * @var int + * @cvalue T_THIN_ARROW_LEFT + */ +const T_THIN_ARROW_LEFT = UNKNOWN; /** * @var int * @cvalue T_COMMENT diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index ef665193b2ff3..aa8bba6e74c55 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1dd42ee5b5b818c5bd131b5c4bbb13c153d99499 */ + * Stub hash: 1e1689d0aadc95ee3c4a10ebb10d3ddcf68971aa */ @@ -132,6 +132,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_OBJECT_OPERATOR", T_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NULLSAFE_OBJECT_OPERATOR", T_NULLSAFE_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_ARROW", T_DOUBLE_ARROW, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_THIN_ARROW_LEFT", T_THIN_ARROW_LEFT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_COMMENT", T_COMMENT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOC_COMMENT", T_DOC_COMMENT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_OPEN_TAG", T_OPEN_TAG, CONST_PERSISTENT);