diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2bc0cf7b703d9..695b1c0c624a1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5661,7 +5661,7 @@ static bool zend_has_finally(void) /* {{{ */ } /* }}} */ -static void zend_compile_return(zend_ast *ast) /* {{{ */ +static void zend_compile_return(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; bool is_generator = (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) != 0; @@ -5704,8 +5704,7 @@ static void zend_compile_return(zend_ast *ast) /* {{{ */ zend_handle_loops_and_finally((expr_node.op_type & (IS_TMP_VAR | IS_VAR)) ? &expr_node : NULL); - opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, - &expr_node, NULL); + opline = zend_emit_op(result, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); if (by_ref && expr_ast) { if (zend_is_call(expr_ast)) { @@ -5767,7 +5766,7 @@ static void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_compile_break_continue(zend_ast *ast) /* {{{ */ +static void zend_compile_break_continue(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *depth_ast = ast->child[0]; @@ -5838,7 +5837,7 @@ static void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } } - opline = zend_emit_op(NULL, ast->kind == ZEND_AST_BREAK ? ZEND_BRK : ZEND_CONT, NULL, NULL); + opline = zend_emit_op(result, ast->kind == ZEND_AST_BREAK ? ZEND_BRK : ZEND_CONT, NULL, NULL); opline->op1.num = CG(context).current_brk_cont; opline->op2.num = depth; } @@ -11590,16 +11589,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_UNSET: zend_compile_unset(ast); break; - case ZEND_AST_RETURN: - zend_compile_return(ast); - break; case ZEND_AST_ECHO: zend_compile_echo(ast); break; - case ZEND_AST_BREAK: - case ZEND_AST_CONTINUE: - zend_compile_break_continue(ast); - break; case ZEND_AST_GOTO: zend_compile_goto(ast); break; @@ -11664,6 +11656,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_HALT_COMPILER: zend_compile_halt_compiler(ast); break; + case ZEND_AST_RETURN: + case ZEND_AST_CONTINUE: + case ZEND_AST_BREAK: case ZEND_AST_THROW: zend_compile_expr(NULL, ast); break; @@ -11814,9 +11809,16 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_ARROW_FUNC: zend_compile_func_decl(result, ast, FUNC_DECL_LEVEL_NESTED); return; + case ZEND_AST_CONTINUE: + case ZEND_AST_BREAK: + zend_compile_break_continue(result, ast); + return; case ZEND_AST_THROW: zend_compile_throw(result, ast); return; + case ZEND_AST_RETURN: + zend_compile_return(result, ast); + return; case ZEND_AST_MATCH: zend_compile_match(result, ast); return; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 816b8126cbf25..8702606d23e84 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -51,7 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %destructor { zend_ast_destroy($$); } %destructor { if ($$) zend_string_release_ex($$, 0); } -%precedence T_THROW +%precedence T_RETURN T_BREAK T_CONTINUE T_THROW %precedence PREC_ARROW_FUNCTION %precedence T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE %left T_LOGICAL_OR @@ -257,7 +257,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type group_use_declaration inline_use_declarations inline_use_declaration %type mixed_group_use_declaration use_declaration unprefixed_use_declaration %type unprefixed_use_declarations const_decl inner_statement -%type expr optional_expr while_statement for_statement foreach_variable +%type expr optional_expr while_statement for_statement foreach_variable early_return %type foreach_statement declare_statement finally_statement unset_variable variable %type extends_from parameter optional_type_without_static argument global_var %type static_var class_statement trait_adaptation trait_precedence trait_alias @@ -518,7 +518,7 @@ statement: { $$ = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); } | T_SWITCH '(' expr ')' switch_case_list { $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $5); } - | T_BREAK optional_expr ';' { $$ = zend_ast_create(ZEND_AST_BREAK, $2); } + | T_BREAK optional_expr ';' { $$ = zend_ast_create(ZEND_AST_BREAK, $2); } | T_CONTINUE optional_expr ';' { $$ = zend_ast_create(ZEND_AST_CONTINUE, $2); } | T_RETURN optional_expr ';' { $$ = zend_ast_create(ZEND_AST_RETURN, $2); } | T_GLOBAL global_var_list ';' { $$ = $2; } @@ -1318,8 +1318,12 @@ expr: { $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, $3, $5); } | expr '?' ':' expr { $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, NULL, $4); } + | expr '?' ':' early_return + { $$ = zend_ast_create(ZEND_AST_CONDITIONAL, $1, NULL, $4); } | expr T_COALESCE expr { $$ = zend_ast_create(ZEND_AST_COALESCE, $1, $3); } + | expr T_COALESCE early_return + { $$ = 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); } @@ -1335,21 +1339,30 @@ expr: } | '@' expr { $$ = zend_ast_create(ZEND_AST_SILENCE, $2); } | scalar { $$ = $1; } - | '`' backticks_expr '`' { $$ = zend_ast_create(ZEND_AST_SHELL_EXEC, $2); } - | T_PRINT expr { $$ = zend_ast_create(ZEND_AST_PRINT, $2); } - | T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } - | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } - | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } - | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } - | T_THROW expr { $$ = zend_ast_create(ZEND_AST_THROW, $2); } - | inline_function { $$ = $1; } - | attributes inline_function { $$ = zend_ast_with_attributes($2, $1); } - | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } + | '`' backticks_expr '`' { $$ = zend_ast_create(ZEND_AST_SHELL_EXEC, $2); } + | T_PRINT expr { $$ = zend_ast_create(ZEND_AST_PRINT, $2); } + | T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } + | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } + | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } + | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } + | T_THROW expr { $$ = zend_ast_create(ZEND_AST_THROW, $2); } + | inline_function { $$ = $1; } + | attributes inline_function { $$ = zend_ast_with_attributes($2, $1); } + | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } | attributes T_STATIC inline_function { $$ = zend_ast_with_attributes($3, $1); ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } | match { $$ = $1; } ; +early_return: + T_RETURN { $$ = zend_ast_create(ZEND_AST_RETURN, NULL); } + | T_RETURN expr { $$ = zend_ast_create(ZEND_AST_RETURN, $2); } + | T_BREAK { $$ = zend_ast_create(ZEND_AST_BREAK, NULL); } + | T_BREAK expr { $$ = zend_ast_create(ZEND_AST_BREAK, $2); } + | T_CONTINUE { $$ = zend_ast_create(ZEND_AST_CONTINUE, NULL); } + | T_CONTINUE expr { $$ = zend_ast_create(ZEND_AST_CONTINUE, $2); } +; + inline_function: function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type diff --git a/tests/lang/early-return/001.phpt b/tests/lang/early-return/001.phpt new file mode 100644 index 0000000000000..ae14b7440d4fd --- /dev/null +++ b/tests/lang/early-return/001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Early break +--FILE-- + +--EXPECT-- +012 diff --git a/tests/lang/early-return/002.phpt b/tests/lang/early-return/002.phpt new file mode 100644 index 0000000000000..ab6ed47e47ea5 --- /dev/null +++ b/tests/lang/early-return/002.phpt @@ -0,0 +1,46 @@ +--TEST-- +Early returns +--FILE-- + 0, + 'parent' => [ + 'level' => 1, + 'parent' => null, + ] +])); + +function test1($userOrNull, $subscriptionOrNull) { + $user = $userOrNull ?? return 1; + $subscription = $subscriptionOrNull ?? return 2; + $lastCard = $subscription->invoices[0]->charges[0]->cardDetails->card ?? return 3; + + return 4; +} +echo test1(null,null); +echo test1(new stdClass(),null); +echo test1(new stdClass(),new stdClass()); +?> +--EXPECT-- +tree is null +array(2) { + ["level"]=> + int(1) + ["parent"]=> + NULL +} +123 diff --git a/tests/lang/early-return/003.phpt b/tests/lang/early-return/003.phpt new file mode 100644 index 0000000000000..ca6547ead8349 --- /dev/null +++ b/tests/lang/early-return/003.phpt @@ -0,0 +1,30 @@ +--TEST-- +Early operators mixed +--FILE-- + +--EXPECT-- +211-1 +3210-1 diff --git a/tests/lang/early-return/early-return1.phpt b/tests/lang/early-return/early-return1.phpt new file mode 100644 index 0000000000000..7bedb5944a79b --- /dev/null +++ b/tests/lang/early-return/early-return1.phpt @@ -0,0 +1,30 @@ +--TEST-- +Memory leaks +--FILE-- + +--EXPECT-- +string(5) "hello" +falsenull +string(5) "world" +bool(false) +null \ No newline at end of file diff --git a/tests/lang/early-return/mem-leaks.phpt b/tests/lang/early-return/mem-leaks.phpt new file mode 100644 index 0000000000000..a197e15aa0396 --- /dev/null +++ b/tests/lang/early-return/mem-leaks.phpt @@ -0,0 +1,11 @@ +--TEST-- +Memory leaks +--FILE-- + +--EXPECT-- diff --git a/tests/lang/loops/break_001.phpt b/tests/lang/loops/break_001.phpt new file mode 100644 index 0000000000000..d011e15fcd607 --- /dev/null +++ b/tests/lang/loops/break_001.phpt @@ -0,0 +1,34 @@ +--TEST-- +Early break +--FILE-- += 0; $i--) { + echo $i; +} +echo PHP_EOL; +// old way break +for($i = 5; $i >= 0; $i--) { + if ($i < 3) { + break; + } + echo $i; +} + +// new way break +echo PHP_EOL; +for($i = 5; $i >= 0; $i--) { + $i > 3 ?: break; + echo $i; +} +echo PHP_EOL; +for($i = 5; $i >= 0; $i--) { + $var = $i ?: break; + echo $i; +} +?> +--EXPECTF-- +543210 +543 +54 +54321 diff --git a/tests/lang/loops/break_002_context.phpt b/tests/lang/loops/break_002_context.phpt new file mode 100644 index 0000000000000..efbdc34d30d65 --- /dev/null +++ b/tests/lang/loops/break_002_context.phpt @@ -0,0 +1,14 @@ +--TEST-- +Early break +--FILE-- += 0; $i--) { + try { + echo (function(){ return $var = $i ?: break; })($i); + } catch (\Error $e) { + var_dump($e->getMessage()); + } +} +?> +--EXPECTF-- +Fatal error: 'break' not in the 'loop' or 'switch' context in %s on line %d diff --git a/tests/lang/loops/continue_001.phpt b/tests/lang/loops/continue_001.phpt new file mode 100644 index 0000000000000..7cd97f9fdbadf --- /dev/null +++ b/tests/lang/loops/continue_001.phpt @@ -0,0 +1,34 @@ +--TEST-- +Early break +--FILE-- += 0; $i--) { + echo $i; +} +echo PHP_EOL; +// old way break +for($i = 5; $i >= 0; $i--) { + if ($i < 3) { + continue; + } + echo $i; +} + +// new way continue +echo PHP_EOL; +for($i = 5; $i >= 0; $i--) { + $i > 3 ?: continue; + echo $i; +} +echo PHP_EOL; +for($i = 5; $i >= 0; $i--) { + $var = $i ?: continue; + echo $i; +} +?> +--EXPECT-- +543210 +543 +54 +54321 diff --git a/tests/lang/loops/continue_002_context.phpt b/tests/lang/loops/continue_002_context.phpt new file mode 100644 index 0000000000000..d20d150d885bd --- /dev/null +++ b/tests/lang/loops/continue_002_context.phpt @@ -0,0 +1,14 @@ +--TEST-- +Early break +--FILE-- += 0; $i--) { + try { + echo (function(){ return $var = $i ?: continue; })($i); + } catch (\Error $e) { + var_dump($e->getMessage()); + } +} +?> +--EXPECTF-- +Fatal error: 'continue' not in the 'loop' or 'switch' context in %s on line %d