Skip to content

Commit 7175190

Browse files
committed
RFC: Explicit fallthrough for switch()
RFC: https://wiki.php.net/rfc/switch-case-fallthrough
1 parent 95a17af commit 7175190

6 files changed

Lines changed: 101 additions & 11 deletions

File tree

Zend/tests/switch/continue_targeting_switch_warning.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ function test() {
2626
case 0:
2727
while ($xyz) {
2828
continue 2; // INVALID
29-
}
29+
} fallthrough;
3030
case 1:
3131
while ($xyz) {
3232
continue;
33-
}
33+
} fallthrough;
3434
case 2:
3535
while ($xyz) {
3636
break 2;
@@ -42,11 +42,11 @@ function test() {
4242
case 0:
4343
while ($xyz) {
4444
continue 2; // INVALID
45-
}
45+
} fallthrough;
4646
case 1:
4747
while ($xyz) {
4848
continue 3;
49-
}
49+
} fallthrough;
5050
case 2:
5151
while ($xyz) {
5252
break 2;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
Warning on switch case falling through
3+
--FILE--
4+
<?php
5+
6+
function test($foo) {
7+
switch ($foo) {
8+
case 0:
9+
case 1:
10+
echo "a", PHP_EOL;
11+
break;
12+
case 2:
13+
echo "b", PHP_EOL;
14+
case 3:
15+
echo "c", PHP_EOL;
16+
break;
17+
case 4:
18+
echo "d", PHP_EOL;
19+
fallthrough;
20+
case 5:
21+
echo "e", PHP_EOL;
22+
break;
23+
case 6:
24+
echo "f", PHP_EOL;
25+
return;
26+
case 7:
27+
echo "g", PHP_EOL;
28+
continue;
29+
case 8:
30+
echo "h", PHP_EOL;
31+
goto end;
32+
case 9:
33+
echo "i", PHP_EOL;
34+
throw new \Exception("exception");
35+
}
36+
end:
37+
}
38+
39+
for ($i = 0; $i <= 9; $i++) {
40+
try {
41+
test($i);
42+
} catch (\Throwable $e) {
43+
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
44+
}
45+
}
46+
47+
?>
48+
--EXPECTF--
49+
Warning: Non-empty case falls through to the next case, terminate the case with "fallthrough;" if this is intentional in %s on line 9
50+
51+
Warning: "continue" targeting switch is equivalent to "break" in %s on line 25
52+
a
53+
a
54+
b
55+
c
56+
c
57+
d
58+
e
59+
e
60+
f
61+
g
62+
h
63+
i
64+
Exception: exception

Zend/zend_compile.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6616,6 +6616,7 @@ static void zend_compile_switch(zend_ast *ast) /* {{{ */
66166616
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
66176617
for (i = 0; i < cases->children; ++i) {
66186618
zend_ast *case_ast = cases->child[i];
6619+
ZEND_ASSERT(case_ast->kind == ZEND_AST_SWITCH_CASE);
66196620
zend_ast *cond_ast = case_ast->child[0];
66206621
znode cond_node;
66216622

@@ -6659,8 +6660,9 @@ static void zend_compile_switch(zend_ast *ast) /* {{{ */
66596660

66606661
for (i = 0; i < cases->children; ++i) {
66616662
zend_ast *case_ast = cases->child[i];
6663+
ZEND_ASSERT(case_ast->kind == ZEND_AST_SWITCH_CASE);
66626664
zend_ast *cond_ast = case_ast->child[0];
6663-
zend_ast *stmt_ast = case_ast->child[1];
6665+
zend_ast_list *stmt_ast = zend_ast_get_list(case_ast->child[1]);
66646666

66656667
if (cond_ast) {
66666668
zend_update_jump_target_to_next(jmpnz_opnums[i]);
@@ -6688,7 +6690,27 @@ static void zend_compile_switch(zend_ast *ast) /* {{{ */
66886690
}
66896691
}
66906692

6691-
zend_compile_stmt(stmt_ast);
6693+
if (stmt_ast->children > 0 && i < (cases->children - 1)) {
6694+
zend_ast *last_stmt = stmt_ast->child[stmt_ast->children - 1];
6695+
while (last_stmt && last_stmt->kind == ZEND_AST_STMT_LIST) {
6696+
zend_ast_list *list = zend_ast_get_list(last_stmt);
6697+
last_stmt = list->child[list->children - 1];
6698+
}
6699+
if (last_stmt == NULL
6700+
|| (last_stmt->kind != ZEND_AST_BREAK
6701+
&& last_stmt->kind != ZEND_AST_CONTINUE
6702+
&& last_stmt->kind != ZEND_AST_RETURN
6703+
&& last_stmt->kind != ZEND_AST_THROW
6704+
&& last_stmt->kind != ZEND_AST_GOTO)) {
6705+
if (!(last_stmt->kind == ZEND_AST_CONST && zend_string_equals_literal(zend_ast_get_str(last_stmt->child[0]), "fallthrough"))) {
6706+
CG(zend_lineno) = case_ast->lineno;
6707+
zend_error(E_WARNING,
6708+
"Non-empty case falls through to the next case, terminate the case with \"fallthrough;\" if this is intentional");
6709+
}
6710+
}
6711+
}
6712+
6713+
zend_compile_stmt((zend_ast*)stmt_ast);
66926714
}
66936715

66946716
if (!has_default_case) {

Zend/zend_constants.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,5 @@
141141
* @undocumentable
142142
*/
143143
const NULL = null;
144+
145+
const fallthrough = null;

Zend/zend_constants_arginfo.h

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

run-tests.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ function main(): void
438438
break;
439439
}
440440
$i--;
441-
// no break
441+
fallthrough;
442442
case 'w':
443443
$failed_tests_file = fopen($argv[++$i], 'w+t');
444444
break;
@@ -602,10 +602,11 @@ function main(): void
602602
case '--version':
603603
echo '$Id$' . "\n";
604604
exit(1);
605+
break;
605606

606607
default:
607608
echo "Illegal switch '$switch' specified!\n";
608-
// no break
609+
fallthrough;
609610
case 'h':
610611
case '-help':
611612
case '--help':
@@ -1520,7 +1521,7 @@ function run_all_tests_parallel(array $test_files, array $env, ?string $redir_te
15201521
}
15211522
}
15221523
$junit->mergeResults($message["junit"]);
1523-
// no break
1524+
fallthrough;
15241525
case "ready":
15251526
// Schedule sequential tests only once we are down to one worker.
15261527
if (count($workerProcs) === 1 && $sequentialTests) {
@@ -1616,7 +1617,7 @@ function run_all_tests_parallel(array $test_files, array $env, ?string $redir_te
16161617
];
16171618
$error_consts = array_combine(array_map('constant', $error_consts), $error_consts);
16181619
error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]");
1619-
// no break
1620+
fallthrough;
16201621
default:
16211622
kill_children($workerProcs);
16221623
error("Unrecognised message type '$message[type]' from worker $i");

0 commit comments

Comments
 (0)