Skip to content

Commit 184a6e7

Browse files
committed
Allow multiple conditions per switch case
1 parent beff5ef commit 184a6e7

File tree

5 files changed

+125
-56
lines changed

5 files changed

+125
-56
lines changed

Zend/tests/switch/009.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Switch expression multiple conditions per case
3+
--FILE--
4+
<?php
5+
6+
function is_working_day($day) {
7+
return $day switch {
8+
1, 7 => false,
9+
2, 3, 4, 5, 6 => true,
10+
};
11+
}
12+
13+
for ($i = 1; $i <= 7; $i++) {
14+
var_dump(is_working_day($i));
15+
}
16+
17+
?>
18+
--EXPECT--
19+
bool(false)
20+
bool(true)
21+
bool(true)
22+
bool(true)
23+
bool(true)
24+
bool(true)
25+
bool(false)

Zend/zend_ast.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1862,7 +1862,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
18621862
zend_ast_export_indent(str, indent);
18631863
if (ast->child[0]) {
18641864
smart_str_appends(str, "case ");
1865-
zend_ast_export_ex(str, ast->child[0], 0, indent);
1865+
zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent);
18661866
smart_str_appends(str, ":\n");
18671867
} else {
18681868
smart_str_appends(str, "default:\n");

Zend/zend_ast.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ enum _zend_ast_kind {
5252
ZEND_AST_STMT_LIST,
5353
ZEND_AST_IF,
5454
ZEND_AST_SWITCH_LIST,
55+
ZEND_AST_SWITCH_CASE_COND_LIST,
5556
ZEND_AST_CATCH_LIST,
5657
ZEND_AST_PARAM_LIST,
5758
ZEND_AST_CLOSURE_USES,

Zend/zend_compile.c

Lines changed: 88 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4981,41 +4981,63 @@ void zend_compile_if(zend_ast *ast) /* {{{ */
49814981
}
49824982
/* }}} */
49834983

4984+
static uint32_t count_switch_conds(zend_ast_list *cases) /* {{{ */
4985+
{
4986+
uint32_t num_conds = 0;
4987+
4988+
for (uint32_t i = 0; i < cases->children; i++) {
4989+
zend_ast *case_ast = cases->child[i];
4990+
if (case_ast->child[0] == NULL) {
4991+
continue;
4992+
}
4993+
4994+
zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]);
4995+
num_conds += conds->children;
4996+
}
4997+
4998+
return num_conds;
4999+
}
5000+
/* }}} */
5001+
49845002
static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) {
49855003
uint32_t i;
49865004
zend_uchar common_type = IS_UNDEF;
49875005
for (i = 0; i < cases->children; i++) {
49885006
zend_ast *case_ast = cases->child[i];
4989-
zend_ast **cond_ast = &case_ast->child[0];
49905007
zval *cond_zv;
49915008
if (!case_ast->child[0]) {
49925009
/* Skip default clause */
49935010
continue;
49945011
}
49955012

4996-
zend_eval_const_expr(cond_ast);
4997-
if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
4998-
/* Non-constant case */
4999-
return IS_UNDEF;
5000-
}
5013+
zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]);
5014+
for (uint32_t j = 0; j < conds->children; j++) {
5015+
zend_ast **cond_ast = &conds->child[j];
50015016

5002-
cond_zv = zend_ast_get_zval(case_ast->child[0]);
5003-
if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
5004-
/* We only optimize switched on integers and strings */
5005-
return IS_UNDEF;
5006-
}
5017+
zend_eval_const_expr(cond_ast);
5018+
if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
5019+
/* Non-constant case */
5020+
return IS_UNDEF;
5021+
}
50075022

5008-
if (common_type == IS_UNDEF) {
5009-
common_type = Z_TYPE_P(cond_zv);
5010-
} else if (common_type != Z_TYPE_P(cond_zv)) {
5011-
/* Non-uniform case types */
5012-
return IS_UNDEF;
5013-
}
5023+
cond_zv = zend_ast_get_zval(*cond_ast);
5024+
if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
5025+
/* We only optimize switched on integers and strings */
5026+
return IS_UNDEF;
5027+
}
50145028

5015-
if (Z_TYPE_P(cond_zv) == IS_STRING
5016-
&& is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
5017-
/* Numeric strings cannot be compared with a simple hash lookup */
5018-
return IS_UNDEF;
5029+
if (common_type == IS_UNDEF) {
5030+
common_type = Z_TYPE_P(cond_zv);
5031+
} else if (common_type != Z_TYPE_P(cond_zv)) {
5032+
/* Non-uniform case types */
5033+
return IS_UNDEF;
5034+
}
5035+
5036+
if (Z_TYPE_P(cond_zv) == IS_STRING
5037+
&& is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
5038+
/* Numeric strings cannot be compared with a simple hash lookup */
5039+
return IS_UNDEF;
5040+
}
50195041
}
50205042
}
50215043

@@ -5083,13 +5105,14 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */
50835105
opnum_switch = opline - CG(active_op_array)->opcodes;
50845106
}
50855107

5086-
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
5108+
uint32_t num_conds = count_switch_conds(cases);
5109+
uint32_t case_cond_count = 0;
5110+
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), num_conds, 0);
50875111
for (i = 0; i < cases->children; ++i) {
50885112
zend_ast *case_ast = cases->child[i];
5089-
zend_ast *cond_ast = case_ast->child[0];
50905113
znode cond_node;
50915114

5092-
if (!cond_ast) {
5115+
if (case_ast->child[0] == NULL) {
50935116
if (has_default_case) {
50945117
CG(zend_lineno) = case_ast->lineno;
50955118
zend_error_noreturn(E_COMPILE_ERROR,
@@ -5099,50 +5122,63 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */
50995122
continue;
51005123
}
51015124

5102-
zend_compile_expr(&cond_node, cond_ast);
5125+
zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]);
5126+
for (uint32_t j = 0; j < conds->children; j++) {
5127+
zend_ast *cond_ast = conds->child[j];
5128+
zend_compile_expr(&cond_node, cond_ast);
5129+
5130+
if (expr_node.op_type == IS_CONST
5131+
&& Z_TYPE(expr_node.u.constant) == IS_FALSE) {
5132+
jmpnz_opnums[case_cond_count] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0);
5133+
} else if (expr_node.op_type == IS_CONST
5134+
&& Z_TYPE(expr_node.u.constant) == IS_TRUE) {
5135+
jmpnz_opnums[case_cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0);
5136+
} else {
5137+
opline = zend_emit_op(NULL,
5138+
(expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL,
5139+
&expr_node, &cond_node);
5140+
SET_NODE(opline->result, &case_node);
5141+
if (opline->op1_type == IS_CONST) {
5142+
Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1));
5143+
}
51035144

5104-
if (expr_node.op_type == IS_CONST
5105-
&& Z_TYPE(expr_node.u.constant) == IS_FALSE) {
5106-
jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0);
5107-
} else if (expr_node.op_type == IS_CONST
5108-
&& Z_TYPE(expr_node.u.constant) == IS_TRUE) {
5109-
jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &cond_node, 0);
5110-
} else {
5111-
opline = zend_emit_op(NULL,
5112-
(expr_node.op_type & (IS_VAR|IS_TMP_VAR)) ? ZEND_CASE : ZEND_IS_EQUAL,
5113-
&expr_node, &cond_node);
5114-
SET_NODE(opline->result, &case_node);
5115-
if (opline->op1_type == IS_CONST) {
5116-
Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1));
5145+
jmpnz_opnums[case_cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0);
51175146
}
51185147

5119-
jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0);
5148+
case_cond_count++;
51205149
}
51215150
}
51225151

51235152
opnum_default_jmp = zend_emit_jump(0);
51245153
zend_bool is_first_case = 1;
5154+
case_cond_count = 0;
51255155

51265156
for (i = 0; i < cases->children; ++i) {
51275157
zend_ast *case_ast = cases->child[i];
5128-
zend_ast *cond_ast = case_ast->child[0];
51295158
zend_ast *stmt_ast = case_ast->child[1];
51305159

5131-
if (cond_ast) {
5132-
zend_update_jump_target_to_next(jmpnz_opnums[i]);
5160+
if (case_ast->child[0] != NULL) {
5161+
zend_ast_list *conds = zend_ast_get_list(case_ast->child[0]);
51335162

5134-
if (jumptable) {
5135-
zval *cond_zv = zend_ast_get_zval(cond_ast);
5136-
zval jmp_target;
5137-
ZVAL_LONG(&jmp_target, get_next_op_number());
5163+
for (uint32_t j = 0; j < conds->children; j++) {
5164+
zend_ast *cond_ast = conds->child[j];
5165+
zend_update_jump_target_to_next(jmpnz_opnums[case_cond_count]);
51385166

5139-
ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
5140-
if (Z_TYPE_P(cond_zv) == IS_LONG) {
5141-
zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
5142-
} else {
5143-
ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
5144-
zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
5167+
if (jumptable) {
5168+
zval *cond_zv = zend_ast_get_zval(cond_ast);
5169+
zval jmp_target;
5170+
ZVAL_LONG(&jmp_target, get_next_op_number());
5171+
5172+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
5173+
if (Z_TYPE_P(cond_zv) == IS_LONG) {
5174+
zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
5175+
} else {
5176+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
5177+
zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
5178+
}
51455179
}
5180+
5181+
case_cond_count++;
51465182
}
51475183
} else {
51485184
zend_update_jump_target_to_next(opnum_default_jmp);

Zend/zend_language_parser.y

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
258258
%type <ast> isset_variable type return_type type_expr type_without_static
259259
%type <ast> identifier type_expr_without_static union_type_without_static
260260
%type <ast> inline_function union_type
261-
%type <ast> switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case
261+
%type <ast> switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case switch_expr_case_cond_list
262262

263263
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
264264
%type <num> method_modifiers non_empty_member_modifiers member_modifier
@@ -586,7 +586,8 @@ switch_case_list:
586586
case_list:
587587
%empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); }
588588
| case_list T_CASE expr case_separator inner_statement_list
589-
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); }
589+
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE,
590+
zend_ast_create_list(1, ZEND_AST_SWITCH_CASE_COND_LIST, $3), $5)); }
590591
| case_list T_DEFAULT case_separator inner_statement_list
591592
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); }
592593
;
@@ -607,11 +608,17 @@ non_empty_switch_expr_case_list:
607608
{ $$ = zend_ast_list_add($1, $3); }
608609

609610
switch_expr_case:
610-
expr T_DOUBLE_ARROW expr
611+
switch_expr_case_cond_list T_DOUBLE_ARROW expr
611612
{ $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); }
612613
| T_DEFAULT T_DOUBLE_ARROW expr
613614
{ $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); }
614615

616+
switch_expr_case_cond_list:
617+
expr
618+
{ $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_CASE_COND_LIST, $1); }
619+
| switch_expr_case_cond_list ',' expr
620+
{ $$ = zend_ast_list_add($1, $3); }
621+
615622
while_statement:
616623
statement { $$ = $1; }
617624
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }

0 commit comments

Comments
 (0)