feat(parser): add continue statement support in switch cases within loops

- Add isContinueExpr method to AstNodeType for continue statement detection
- Introduce inContinuableLoop context flag to track for/foreach/while/do-while loops
- Update CompilerBase to maintain continuable loop state during compilation
- Modify LoopControlTrait to validate continue statements only within continuable loops
- Enhance SwitchTrait to allow continue as valid case ending expression
- Add comprehensive tests for continue in switch nested in various loop types
- Create test cases for invalid continue usage outside loops
- Update version number from 1094 to 1095
pull/43/head
韩天峰 4 weeks ago
parent c86e847c8f
commit 9a7de8ee31
  1. 53
      phpunit/code/control-flow/loop-switch-continue.php
  2. 11
      phpunit/code/control-flow/standalone-dynamic-switch-continue.php
  3. 13
      phpunit/code/control-flow/standalone-switch-continue.php
  4. 25
      phpunit/src/LoopControlTest.php
  5. 9
      src/CompilerBase.php
  6. 5
      src/Context/FunctionContext.php
  7. 5
      src/Parser/AstNodeType.php
  8. 2
      src/Parser/LoopControlTrait.php
  9. 3
      src/Parser/SwitchTrait.php
  10. 2
      version.txt

@ -0,0 +1,53 @@
<?php
use native_types;
function switch_continue_in_for(): void
{
for ($i = 0; $i < 2; $i++) {
switch ($i) {
case 0:
continue;
default:
break;
}
}
}
function switch_continue_in_foreach(array $values): void
{
foreach ($values as $value) {
switch ($value) {
case 0:
continue;
default:
break;
}
}
}
function switch_continue_in_while(): void
{
$i = 0;
while ($i++ < 2) {
switch ($i) {
case 1:
continue;
default:
break;
}
}
}
function switch_continue_in_do_while(): void
{
$i = 0;
do {
switch ($i++) {
case 0:
continue;
default:
break;
}
} while ($i < 2);
}

@ -0,0 +1,11 @@
<?php
function standalone_dynamic_switch_continue(mixed $value): void
{
switch ($value) {
case 1:
continue;
default:
break;
}
}

@ -0,0 +1,13 @@
<?php
use native_types;
function standalone_switch_continue(int $value): void
{
switch ($value) {
case 1:
continue;
default:
break;
}
}

@ -0,0 +1,25 @@
<?php
class LoopControlTest extends \BaseTest
{
public function testContinueInStandaloneSwitchIsRejected(): void
{
$this->exec(
'Cannot continue outside loop',
'control-flow/standalone-switch-continue.php',
);
}
public function testContinueInStandaloneDynamicSwitchIsRejected(): void
{
$this->exec(
'Cannot continue outside loop',
'control-flow/standalone-dynamic-switch-continue.php',
);
}
public function testContinueInSwitchNestedInLoopsIsAllowed(): void
{
$this->compile('control-flow/loop-switch-continue.php');
}
}

@ -1516,6 +1516,7 @@ class CompilerBase implements PropertyAccessContext
$this->context->enterScope();
$lines = [];
$inLoopTop = $this->context->inLoop;
$inContinuableLoopTop = $this->context->inContinuableLoop;
$last = array_key_last($stmts);
foreach ($stmts as $i => $v) {
$class = $v->getType();
@ -1545,13 +1546,17 @@ class CompilerBase implements PropertyAccessContext
break;
case 'Stmt_For':
$this->context->inLoop = true;
$this->context->inContinuableLoop = true;
$result = $this->parseFor($v);
$this->context->inLoop = $inLoopTop;
$this->context->inContinuableLoop = $inContinuableLoopTop;
break;
case 'Stmt_Foreach':
$this->context->inLoop = true;
$this->context->inContinuableLoop = true;
$result = $this->parseForeach($v);
$this->context->inLoop = $inLoopTop;
$this->context->inContinuableLoop = $inContinuableLoopTop;
break;
case 'Stmt_Switch':
$this->context->inLoop = true;
@ -1560,13 +1565,17 @@ class CompilerBase implements PropertyAccessContext
break;
case 'Stmt_While':
$this->context->inLoop = true;
$this->context->inContinuableLoop = true;
$result = $this->parseWhile($v);
$this->context->inLoop = $inLoopTop;
$this->context->inContinuableLoop = $inContinuableLoopTop;
break;
case 'Stmt_Do':
$this->context->inLoop = true;
$this->context->inContinuableLoop = true;
$result = $this->parseDo($v);
$this->context->inLoop = $inLoopTop;
$this->context->inContinuableLoop = $inContinuableLoopTop;
break;
case 'Stmt_If':
$result = $this->parseIf($v);

@ -55,7 +55,10 @@ class FunctionContext
public array $ceWrappers = [];
public int $tmpVarIndex = 0;
public array $arguments = [];
/** True while parsing a breakable loop or switch. */
public bool $inLoop = false;
/** True while parsing a for/foreach/while/do-while body. */
public bool $inContinuableLoop = false;
public bool $inClosure = false;
public ?array $closureReturnTypeCheck = null;
public string $closureReturnTypeStr = '';
@ -97,6 +100,7 @@ class FunctionContext
$this->scopeLayouts = [];
$this->scopeLevel = 0;
$this->inLoop = false;
$this->inContinuableLoop = false;
$this->inClosure = false;
$this->closureReturnTypeCheck = null;
$this->closureReturnTypeStr = '';
@ -127,5 +131,6 @@ class FunctionContext
$this->scopeLayouts = [];
$this->scopeLevel = 0;
$this->inLoop = false;
$this->inContinuableLoop = false;
}
}

@ -152,6 +152,11 @@ trait AstNodeType
return $expr instanceof Node\Stmt\Break_;
}
protected function isContinueExpr(NodeAbstract $expr): bool
{
return $expr instanceof Node\Stmt\Continue_;
}
protected function isThrowExpr(NodeAbstract $expr): bool
{
if ($expr instanceof Node\Stmt\Expression) {

@ -183,7 +183,7 @@ trait LoopControlTrait
protected function parseContinue(Node\Stmt\Continue_ $v): string
{
if (!$this->context->inLoop) {
if (!$this->context->inContinuableLoop) {
$this->fatalError($v, 'Cannot continue outside loop');
}
$num = $v->num;

@ -90,9 +90,10 @@ trait SwitchTrait
if (!$this->isReturnExpr($lastExpr)
and !$this->isExitExpr($lastExpr)
and !$this->isBreakExpr($lastExpr)
and !$this->isContinueExpr($lastExpr)
and !$this->isThrowExpr($lastExpr)
) {
$this->fatalError($case, 'switch case must end with return/break/exit/throw, ' . $lastExpr->getType() . ' given');
$this->fatalError($case, 'switch case must end with return/break/continue/exit/throw, ' . $lastExpr->getType() . ' given');
}
$target = count($caseGroups);
if ($hasDefault) {

@ -1 +1 @@
1094
1095

Loading…
Cancel
Save