diff --git a/Zend/tests/switch/001.phpt b/Zend/tests/switch/001.phpt new file mode 100644 index 0000000000000..bb2fb1616d25b --- /dev/null +++ b/Zend/tests/switch/001.phpt @@ -0,0 +1,36 @@ +--TEST-- +Basic switch expression functionality test +--FILE-- + 'Zero', + 1 => 'One', + 2 => 'Two', + 3 => 'Three', + 4 => 'Four', + 5 => 'Five', + 6 => 'Six', + 7 => 'Seven', + 8 => 'Eight', + 9 => 'Nine', + }; +} + +for ($i = 0; $i <= 9; $i++) { + print wordify($i) . "\n"; +} + +?> +--EXPECT-- +Zero +One +Two +Three +Four +Five +Six +Seven +Eight +Nine diff --git a/Zend/tests/switch/002.phpt b/Zend/tests/switch/002.phpt new file mode 100644 index 0000000000000..873a9d67491c4 --- /dev/null +++ b/Zend/tests/switch/002.phpt @@ -0,0 +1,19 @@ +--TEST-- +Switch expression omit trailing comma +--FILE-- + "true\n", + false => "false\n" + }; +} + +print_bool(true); +print_bool(false); + +?> +--EXPECT-- +true +false diff --git a/Zend/tests/switch/003.phpt b/Zend/tests/switch/003.phpt new file mode 100644 index 0000000000000..a6712e13c4ef3 --- /dev/null +++ b/Zend/tests/switch/003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Switch expression default case +--FILE-- + 1, + 2 => 2, + default => 'default', + }; +} + +echo get_value(0) . "\n"; +echo get_value(1) . "\n"; +echo get_value(2) . "\n"; +echo get_value(3) . "\n"; + +?> +--EXPECT-- +default +1 +2 +default diff --git a/Zend/tests/switch/004.phpt b/Zend/tests/switch/004.phpt new file mode 100644 index 0000000000000..b29615f06237d --- /dev/null +++ b/Zend/tests/switch/004.phpt @@ -0,0 +1,31 @@ +--TEST-- +Switch expression with true as expression +--FILE-- += 50 => '50+', + $i >= 40 => '40-50', + $i >= 30 => '30-40', + $i >= 20 => '20-30', + $i >= 10 => '10-20', + default => '0-10', + }; +} + +echo get_range(22) . "\n"; +echo get_range(0) . "\n"; +echo get_range(59) . "\n"; +echo get_range(13) . "\n"; +echo get_range(39) . "\n"; +echo get_range(40) . "\n"; + +?> +--EXPECT-- +20-30 +0-10 +50+ +10-20 +30-40 +40-50 diff --git a/Zend/tests/switch/005.phpt b/Zend/tests/switch/005.phpt new file mode 100644 index 0000000000000..0503b6e46daa9 --- /dev/null +++ b/Zend/tests/switch/005.phpt @@ -0,0 +1,12 @@ +--TEST-- +Switch expression discarding result +--FILE-- + print "Executed\n", +}; + +?> +--EXPECT-- +Executed diff --git a/Zend/tests/switch/006.phpt b/Zend/tests/switch/006.phpt new file mode 100644 index 0000000000000..427e6bd4d9375 --- /dev/null +++ b/Zend/tests/switch/006.phpt @@ -0,0 +1,10 @@ +--TEST-- +Switch expression with no cases +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected '}' in %s on line %d diff --git a/Zend/tests/switch/007.phpt b/Zend/tests/switch/007.phpt new file mode 100644 index 0000000000000..761e4132defa8 --- /dev/null +++ b/Zend/tests/switch/007.phpt @@ -0,0 +1,26 @@ +--TEST-- +Switch expression exception on unhandled case +--FILE-- + 1, + 2 => 2, + }; +} + +echo get_value(1) . "\n"; +echo get_value(2) . "\n"; +echo get_value(3) . "\n"; + +?> +--EXPECTF-- +1 +2 + +Fatal error: Uncaught UnhandledSwitchCaseError in %s +Stack trace: +#0 %s: get_value(3) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/switch/008.phpt b/Zend/tests/switch/008.phpt new file mode 100644 index 0000000000000..7cfc8642630ad --- /dev/null +++ b/Zend/tests/switch/008.phpt @@ -0,0 +1,23 @@ +--TEST-- +Switch expression precedence +--FILE-- + "! has higher precedence\n" +}; + +$throwableInterface = Throwable::class; +print switch (new RuntimeException() instanceof $throwableInterface) { + true => "instanceof has higher precedence\n" +}; + +print switch (10 ** 2) { + 100 => "** has higher precedence\n" +}; + +?> +--EXPECT-- +! has higher precedence +instanceof has higher precedence +** has higher precedence diff --git a/Zend/tests/switch/009.phpt b/Zend/tests/switch/009.phpt new file mode 100644 index 0000000000000..feeb2e2e0fdc8 --- /dev/null +++ b/Zend/tests/switch/009.phpt @@ -0,0 +1,25 @@ +--TEST-- +Switch expression multiple conditions per case +--FILE-- + false, + 2, 3, 4, 5, 6 => true, + }; +} + +for ($i = 1; $i <= 7; $i++) { + var_dump(is_working_day($i)); +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) diff --git a/Zend/tests/switch/010.phpt b/Zend/tests/switch/010.phpt new file mode 100644 index 0000000000000..2ef0b1067cdcd --- /dev/null +++ b/Zend/tests/switch/010.phpt @@ -0,0 +1,31 @@ +--TEST-- +Switch statement multiple conditions per case +--FILE-- + +--EXPECT-- +NULL +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +NULL diff --git a/Zend/tests/switch/011.phpt b/Zend/tests/switch/011.phpt new file mode 100644 index 0000000000000..d802fa9f0a265 --- /dev/null +++ b/Zend/tests/switch/011.phpt @@ -0,0 +1,40 @@ +--TEST-- +Pretty printing for switch expression +--FILE-- + false }); + +assert(switch ('foo') { + 'foo', 'bar' => false, + 'baz' => false, +}); + +assert((function () { + switch ('foo') { + case 'foo', 'bar': + return false; + case 'baz': + return false; + } +})()); + +?> +--EXPECTF-- +Warning: assert(): assert('foo' switch { + default => false, +}) failed in %s on line %d + +Warning: assert(): assert('foo' switch { + 'foo', 'bar' => false, + 'baz' => false, +}) failed in %s on line %d + +Warning: assert(): assert(function () { + switch ('foo') { + case 'foo', 'bar': + return false; + case 'baz': + return false; + } +}()) failed in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7cfc0450fd84e..47e7073416069 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1851,23 +1851,39 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appendc(str, '}'); break; case ZEND_AST_SWITCH: - smart_str_appends(str, "switch ("); - zend_ast_export_ex(str, ast->child[0], 0, indent); - smart_str_appends(str, ") {\n"); + if (ast->attr & ZEND_SWITCH_EXPRESSION) { + zend_ast_export_ex(str, ast->child[0], 0, indent); + smart_str_appends(str, " switch {\n"); + } else { + smart_str_appends(str, "switch ("); + zend_ast_export_ex(str, ast->child[0], 0, indent); + smart_str_appends(str, ") {\n"); + } zend_ast_export_ex(str, ast->child[1], 0, indent + 1); zend_ast_export_indent(str, indent); smart_str_appendc(str, '}'); break; case ZEND_AST_SWITCH_CASE: zend_ast_export_indent(str, indent); - if (ast->child[0]) { - smart_str_appends(str, "case "); - zend_ast_export_ex(str, ast->child[0], 0, indent); - smart_str_appends(str, ":\n"); + if (ast->attr & ZEND_SWITCH_EXPRESSION) { + if (ast->child[0]) { + zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); + smart_str_appends(str, " => "); + } else { + smart_str_appends(str, "default => "); + } + zend_ast_export_ex(str, ast->child[1], 0, 0); + smart_str_appends(str, ",\n"); } else { - smart_str_appends(str, "default:\n"); + if (ast->child[0]) { + smart_str_appends(str, "case "); + zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent); + smart_str_appends(str, ":\n"); + } else { + smart_str_appends(str, "default:\n"); + } + zend_ast_export_stmt(str, ast->child[1], indent + 1); } - zend_ast_export_stmt(str, ast->child[1], indent + 1); break; case ZEND_AST_DECLARE: smart_str_appends(str, "declare("); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 5b8aae6f96c25..ff012d888cc94 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -52,6 +52,7 @@ enum _zend_ast_kind { ZEND_AST_STMT_LIST, ZEND_AST_IF, ZEND_AST_SWITCH_LIST, + ZEND_AST_SWITCH_CASE_COND_LIST, ZEND_AST_CATCH_LIST, ZEND_AST_PARAM_LIST, ZEND_AST_CLOSURE_USES, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ff466c5ef5740..c38cc829a3f6d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4981,41 +4981,62 @@ void zend_compile_if(zend_ast *ast) /* {{{ */ } /* }}} */ +static uint32_t count_switch_conds(zend_ast_list *cases) +{ + uint32_t num_conds = 0; + + for (uint32_t i = 0; i < cases->children; i++) { + zend_ast *case_ast = cases->child[i]; + if (case_ast->child[0] == NULL) { + continue; + } + + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + num_conds += conds->children; + } + + return num_conds; +} + static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) { uint32_t i; zend_uchar common_type = IS_UNDEF; for (i = 0; i < cases->children; i++) { zend_ast *case_ast = cases->child[i]; - zend_ast **cond_ast = &case_ast->child[0]; zval *cond_zv; if (!case_ast->child[0]) { /* Skip default clause */ continue; } - zend_eval_const_expr(cond_ast); - if ((*cond_ast)->kind != ZEND_AST_ZVAL) { - /* Non-constant case */ - return IS_UNDEF; - } + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast **cond_ast = &conds->child[j]; - cond_zv = zend_ast_get_zval(case_ast->child[0]); - if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) { - /* We only optimize switched on integers and strings */ - return IS_UNDEF; - } + zend_eval_const_expr(cond_ast); + if ((*cond_ast)->kind != ZEND_AST_ZVAL) { + /* Non-constant case */ + return IS_UNDEF; + } - if (common_type == IS_UNDEF) { - common_type = Z_TYPE_P(cond_zv); - } else if (common_type != Z_TYPE_P(cond_zv)) { - /* Non-uniform case types */ - return IS_UNDEF; - } + cond_zv = zend_ast_get_zval(*cond_ast); + if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) { + /* We only optimize switched on integers and strings */ + return IS_UNDEF; + } - if (Z_TYPE_P(cond_zv) == IS_STRING - && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) { - /* Numeric strings cannot be compared with a simple hash lookup */ - return IS_UNDEF; + if (common_type == IS_UNDEF) { + common_type = Z_TYPE_P(cond_zv); + } else if (common_type != Z_TYPE_P(cond_zv)) { + /* Non-uniform case types */ + return IS_UNDEF; + } + + if (Z_TYPE_P(cond_zv) == IS_STRING + && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) { + /* Numeric strings cannot be compared with a simple hash lookup */ + return IS_UNDEF; + } } } @@ -5037,13 +5058,14 @@ static zend_bool should_use_jumptable(zend_ast_list *cases, zend_uchar jumptable } } -void zend_compile_switch(zend_ast *ast) /* {{{ */ +void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; zend_ast_list *cases = zend_ast_get_list(ast->child[1]); uint32_t i; zend_bool has_default_case = 0; + zend_bool is_switch_expr = (ast->attr & ZEND_SWITCH_EXPRESSION) != 0; znode expr_node, case_node; zend_op *opline; @@ -5051,6 +5073,12 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ zend_uchar jumptable_type; HashTable *jumptable = NULL; + // If the switch expression has no cases the result is never set + if (result != NULL) { + result->op_type = IS_CONST; + ZVAL_NULL(&result->u.constant); + } + zend_compile_expr(&expr_node, expr_ast); zend_begin_loop(ZEND_FREE, &expr_node, 1); @@ -5076,13 +5104,14 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ opnum_switch = opline - CG(active_op_array)->opcodes; } - jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0); + uint32_t num_conds = count_switch_conds(cases); + uint32_t cond_count = 0; + jmpnz_opnums = safe_emalloc(sizeof(uint32_t), num_conds, 0); for (i = 0; i < cases->children; ++i) { zend_ast *case_ast = cases->child[i]; - zend_ast *cond_ast = case_ast->child[0]; znode cond_node; - if (!cond_ast) { + if (case_ast->child[0] == NULL) { if (has_default_case) { CG(zend_lineno) = case_ast->lineno; zend_error_noreturn(E_COMPILE_ERROR, @@ -5092,49 +5121,63 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ continue; } - zend_compile_expr(&cond_node, cond_ast); + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast *cond_ast = conds->child[j]; + zend_compile_expr(&cond_node, cond_ast); - if (expr_node.op_type == IS_CONST - && Z_TYPE(expr_node.u.constant) == IS_FALSE) { - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0); - } else if (expr_node.op_type == IS_CONST - && Z_TYPE(expr_node.u.constant) == IS_TRUE) { - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0); - } else { - opline = zend_emit_op(NULL, - (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL, - &expr_node, &cond_node); - SET_NODE(opline->result, &case_node); - if (opline->op1_type == IS_CONST) { - Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1)); + if (expr_node.op_type == IS_CONST + && Z_TYPE(expr_node.u.constant) == IS_FALSE) { + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0); + } else if (expr_node.op_type == IS_CONST + && Z_TYPE(expr_node.u.constant) == IS_TRUE) { + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0); + } else { + opline = zend_emit_op(NULL, + (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL, + &expr_node, &cond_node); + SET_NODE(opline->result, &case_node); + if (opline->op1_type == IS_CONST) { + Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1)); + } + + jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0); } - jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0); + cond_count++; } } opnum_default_jmp = zend_emit_jump(0); + zend_bool is_first_case = 1; + cond_count = 0; for (i = 0; i < cases->children; ++i) { zend_ast *case_ast = cases->child[i]; - zend_ast *cond_ast = case_ast->child[0]; zend_ast *stmt_ast = case_ast->child[1]; - if (cond_ast) { - zend_update_jump_target_to_next(jmpnz_opnums[i]); + if (case_ast->child[0] != NULL) { + zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]); - if (jumptable) { - zval *cond_zv = zend_ast_get_zval(cond_ast); - zval jmp_target; - ZVAL_LONG(&jmp_target, get_next_op_number()); + for (uint32_t j = 0; j < conds->children; j++) { + zend_ast *cond_ast = conds->child[j]; + zend_update_jump_target_to_next(jmpnz_opnums[cond_count]); - ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type); - if (Z_TYPE_P(cond_zv) == IS_LONG) { - zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target); - } else { - ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING); - zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target); + if (jumptable) { + zval *cond_zv = zend_ast_get_zval(cond_ast); + zval jmp_target; + ZVAL_LONG(&jmp_target, get_next_op_number()); + + ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type); + if (Z_TYPE_P(cond_zv) == IS_LONG) { + zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target); + } else { + ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING); + zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target); + } } + + cond_count++; } } else { zend_update_jump_target_to_next(opnum_default_jmp); @@ -5146,7 +5189,23 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ } } - zend_compile_stmt(stmt_ast); + if (is_switch_expr) { + znode cond_stmt_node; + zend_compile_expr(&cond_stmt_node, stmt_ast); + + if (is_first_case) { + zend_emit_op_tmp(result, ZEND_QM_ASSIGN, &cond_stmt_node, NULL); + is_first_case = 0; + } else { + zend_op *opline_qm_assign = zend_emit_op(NULL, ZEND_QM_ASSIGN, &cond_stmt_node, NULL); + SET_NODE(opline_qm_assign->result, result); + } + + zend_ast *break_ast = zend_ast_create(ZEND_AST_BREAK, NULL); + zend_compile_break_continue(break_ast); + } else { + zend_compile_stmt(stmt_ast); + } } if (!has_default_case) { @@ -5156,6 +5215,20 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */ opline = &CG(active_op_array)->opcodes[opnum_switch]; opline->extended_value = get_next_op_number(); } + + // Generate default case for switch expression + if (is_switch_expr) { + zval exception_name; + ZVAL_STRING(&exception_name, "UnhandledSwitchCaseError"); + zend_ast *exception_name_ast = zend_ast_create_zval(&exception_name); + + zend_ast *exception_args_ast = zend_ast_create_list(0, ZEND_AST_ARG_LIST); + zend_ast *new_exception_ast = zend_ast_create(ZEND_AST_NEW, exception_name_ast, exception_args_ast); + zend_ast *throw_ast = zend_ast_create(ZEND_AST_THROW, new_exception_ast); + zend_compile_throw(throw_ast); + + zval_ptr_dtor(&exception_name); + } } zend_end_loop(get_next_op_number(), &expr_node); @@ -8782,7 +8855,7 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */ zend_compile_if(ast); break; case ZEND_AST_SWITCH: - zend_compile_switch(ast); + zend_compile_switch(NULL, ast); break; case ZEND_AST_TRY: zend_compile_try(ast); @@ -8965,6 +9038,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_ARROW_FUNC: zend_compile_func_decl(result, ast, 0); return; + case ZEND_AST_SWITCH: + zend_compile_switch(result, ast); + break; default: ZEND_ASSERT(0 /* not supported */); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8a64ed86c3d03..282537fbdc7ca 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1001,6 +1001,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, /* Attribute for ternary inside parentheses */ #define ZEND_PARENTHESIZED_CONDITIONAL 1 +/* Attribute for switch expression */ +#define ZEND_SWITCH_EXPRESSION 1 + /* For "use" AST nodes and the seen symbol table */ #define ZEND_SYMBOL_CLASS (1<<0) #define ZEND_SYMBOL_FUNCTION (1<<1) diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index fb48d34bdf61b..37ef72c943361 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -40,6 +40,7 @@ ZEND_API zend_class_entry *zend_ce_argument_count_error; ZEND_API zend_class_entry *zend_ce_value_error; ZEND_API zend_class_entry *zend_ce_arithmetic_error; ZEND_API zend_class_entry *zend_ce_division_by_zero_error; +ZEND_API zend_class_entry *zend_ce_unhandled_switch_case_error; ZEND_API void (*zend_throw_exception_hook)(zval *ex); @@ -867,6 +868,10 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(ce, "DivisionByZeroError", NULL); zend_ce_division_by_zero_error = zend_register_internal_class_ex(&ce, zend_ce_arithmetic_error); zend_ce_division_by_zero_error->create_object = zend_default_exception_new; + + INIT_CLASS_ENTRY(ce, "UnhandledSwitchCaseError", NULL); + zend_ce_unhandled_switch_case_error = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_ce_unhandled_switch_case_error->create_object = zend_default_exception_new; } /* }}} */ diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index ec6f0a0201111..af80b45f0a5e3 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -35,6 +35,7 @@ extern ZEND_API zend_class_entry *zend_ce_argument_count_error; extern ZEND_API zend_class_entry *zend_ce_value_error; extern ZEND_API zend_class_entry *zend_ce_arithmetic_error; extern ZEND_API zend_class_entry *zend_ce_division_by_zero_error; +extern ZEND_API zend_class_entry *zend_ce_unhandled_switch_case_error; ZEND_API void zend_exception_set_previous(zend_object *exception, zend_object *add_previous); ZEND_API void zend_exception_save(void); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index c1ced9a35aa21..6d06658676390 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -248,7 +248,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %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 %type echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list -%type implements_list case_list if_stmt_without_else +%type implements_list case_list case if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list %type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars @@ -257,6 +257,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type isset_variable type return_type type_expr type_without_static %type identifier type_expr_without_static union_type_without_static %type inline_function union_type +%type case_cond_list switch_expr_case_list switch_expr_case %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -576,17 +577,33 @@ declare_statement: switch_case_list: '{' case_list '}' { $$ = $2; } + | '{' '}' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | '{' ';' case_list '}' { $$ = $3; } + | '{' ';' '}' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | ':' case_list T_ENDSWITCH ';' { $$ = $2; } + | ':' T_ENDSWITCH ';' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } + | ':' ';' T_ENDSWITCH ';' { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } ; case_list: - %empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); } - | case_list T_CASE expr case_separator inner_statement_list - { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); } - | case_list T_DEFAULT case_separator inner_statement_list - { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); } + case { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_LIST, $1); } + | case_list case + { $$ = zend_ast_list_add($1, $2); } +; + +case: + T_CASE case_cond_list case_separator inner_statement_list + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $2, $4); } + | T_DEFAULT case_separator inner_statement_list + { $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); } +; + +case_cond_list: + expr + { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_CASE_COND_LIST, $1); } + | case_cond_list ',' expr + { $$ = zend_ast_list_add($1, $3); } ; case_separator: @@ -594,6 +611,23 @@ case_separator: | ';' ; +switch_expr_case_list: + switch_expr_case + { $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_LIST, $1); } + | switch_expr_case_list ',' switch_expr_case + { $$ = zend_ast_list_add($1, $3); } +; + +switch_expr_case: + case_cond_list T_DOUBLE_ARROW expr { + $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); + $$->attr = ZEND_SWITCH_EXPRESSION; + } + | T_DEFAULT T_DOUBLE_ARROW expr { + $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); + $$->attr = ZEND_SWITCH_EXPRESSION; + } +; while_statement: statement { $$ = $1; } @@ -1021,6 +1055,10 @@ expr: | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | inline_function { $$ = $1; } | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } + | T_SWITCH '(' expr ')' '{' switch_expr_case_list possible_comma '}' { + $$ = zend_ast_create(ZEND_AST_SWITCH, $3, $6); + $$->attr = ZEND_SWITCH_EXPRESSION; + } ;