Skip to content

Commit 77bd887

Browse files
committed
Allow multiple conditions per switch case
1 parent a1b2dc6 commit 77bd887

File tree

6 files changed

+155
-56
lines changed

6 files changed

+155
-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/tests/switch/010.phpt

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

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: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4981,41 +4981,62 @@ 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+
49845001
static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) {
49855002
uint32_t i;
49865003
zend_uchar common_type = IS_UNDEF;
49875004
for (i = 0; i < cases->children; i++) {
49885005
zend_ast *case_ast = cases->child[i];
4989-
zend_ast **cond_ast = &case_ast->child[0];
49905006
zval *cond_zv;
49915007
if (!case_ast->child[0]) {
49925008
/* Skip default clause */
49935009
continue;
49945010
}
49955011

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

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-
}
5016+
zend_eval_const_expr(cond_ast);
5017+
if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
5018+
/* Non-constant case */
5019+
return IS_UNDEF;
5020+
}
50075021

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-
}
5022+
cond_zv = zend_ast_get_zval(*cond_ast);
5023+
if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
5024+
/* We only optimize switched on integers and strings */
5025+
return IS_UNDEF;
5026+
}
50145027

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;
5028+
if (common_type == IS_UNDEF) {
5029+
common_type = Z_TYPE_P(cond_zv);
5030+
} else if (common_type != Z_TYPE_P(cond_zv)) {
5031+
/* Non-uniform case types */
5032+
return IS_UNDEF;
5033+
}
5034+
5035+
if (Z_TYPE_P(cond_zv) == IS_STRING
5036+
&& is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
5037+
/* Numeric strings cannot be compared with a simple hash lookup */
5038+
return IS_UNDEF;
5039+
}
50195040
}
50205041
}
50215042

@@ -5083,13 +5104,14 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */
50835104
opnum_switch = opline - CG(active_op_array)->opcodes;
50845105
}
50855106

5086-
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
5107+
uint32_t num_conds = count_switch_conds(cases);
5108+
uint32_t cond_count = 0;
5109+
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), num_conds, 0);
50875110
for (i = 0; i < cases->children; ++i) {
50885111
zend_ast *case_ast = cases->child[i];
5089-
zend_ast *cond_ast = case_ast->child[0];
50905112
znode cond_node;
50915113

5092-
if (!cond_ast) {
5114+
if (case_ast->child[0] == NULL) {
50935115
if (has_default_case) {
50945116
CG(zend_lineno) = case_ast->lineno;
50955117
zend_error_noreturn(E_COMPILE_ERROR,
@@ -5099,50 +5121,63 @@ void zend_compile_switch(znode *result, zend_ast *ast) /* {{{ */
50995121
continue;
51005122
}
51015123

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

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));
5144+
jmpnz_opnums[cond_count] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0);
51175145
}
51185146

5119-
jmpnz_opnums[i] = zend_emit_cond_jump(ZEND_JMPNZ, &case_node, 0);
5147+
cond_count++;
51205148
}
51215149
}
51225150

51235151
opnum_default_jmp = zend_emit_jump(0);
51245152
zend_bool is_first_case = 1;
5153+
cond_count = 0;
51255154

51265155
for (i = 0; i < cases->children; ++i) {
51275156
zend_ast *case_ast = cases->child[i];
5128-
zend_ast *cond_ast = case_ast->child[0];
51295157
zend_ast *stmt_ast = case_ast->child[1];
51305158

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

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());
5162+
for (uint32_t j = 0; j < conds->children; j++) {
5163+
zend_ast *cond_ast = conds->child[j];
5164+
zend_update_jump_target_to_next(jmpnz_opnums[cond_count]);
51385165

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);
5166+
if (jumptable) {
5167+
zval *cond_zv = zend_ast_get_zval(cond_ast);
5168+
zval jmp_target;
5169+
ZVAL_LONG(&jmp_target, get_next_op_number());
5170+
5171+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
5172+
if (Z_TYPE_P(cond_zv) == IS_LONG) {
5173+
zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
5174+
} else {
5175+
ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
5176+
zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
5177+
}
51455178
}
5179+
5180+
cond_count++;
51465181
}
51475182
} else {
51485183
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> case_cond_list switch_expr_case_list non_empty_switch_expr_case_list switch_expr_case
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
@@ -585,12 +585,19 @@ switch_case_list:
585585

586586
case_list:
587587
%empty { $$ = zend_ast_create_list(0, ZEND_AST_SWITCH_LIST); }
588-
| case_list T_CASE expr case_separator inner_statement_list
588+
| case_list T_CASE case_cond_list case_separator inner_statement_list
589589
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, $3, $5)); }
590590
| case_list T_DEFAULT case_separator inner_statement_list
591591
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $4)); }
592592
;
593593

594+
case_cond_list:
595+
expr
596+
{ $$ = zend_ast_create_list(1, ZEND_AST_SWITCH_CASE_COND_LIST, $1); }
597+
| case_cond_list ',' expr
598+
{ $$ = zend_ast_list_add($1, $3); }
599+
;
600+
594601
case_separator:
595602
':'
596603
| ';'
@@ -609,7 +616,7 @@ non_empty_switch_expr_case_list:
609616
;
610617

611618
switch_expr_case:
612-
expr T_DOUBLE_ARROW expr
619+
case_cond_list T_DOUBLE_ARROW expr
613620
{ $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, $1, $3); }
614621
| T_DEFAULT T_DOUBLE_ARROW expr
615622
{ $$ = zend_ast_create(ZEND_AST_SWITCH_CASE, NULL, $3); }

0 commit comments

Comments
 (0)