diff --git a/phpunit/code/control-flow/loop-switch-continue.php b/phpunit/code/control-flow/loop-switch-continue.php new file mode 100644 index 00000000..44f7faf7 --- /dev/null +++ b/phpunit/code/control-flow/loop-switch-continue.php @@ -0,0 +1,53 @@ +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'); + } +} diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 5a2f709a..37922f79 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.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); diff --git a/src/Context/FunctionContext.php b/src/Context/FunctionContext.php index f966c933..3b979f67 100644 --- a/src/Context/FunctionContext.php +++ b/src/Context/FunctionContext.php @@ -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; } } diff --git a/src/Parser/AstNodeType.php b/src/Parser/AstNodeType.php index f01d5047..1d01a80f 100644 --- a/src/Parser/AstNodeType.php +++ b/src/Parser/AstNodeType.php @@ -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) { diff --git a/src/Parser/LoopControlTrait.php b/src/Parser/LoopControlTrait.php index de32676e..8819e6d4 100644 --- a/src/Parser/LoopControlTrait.php +++ b/src/Parser/LoopControlTrait.php @@ -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; diff --git a/src/Parser/SwitchTrait.php b/src/Parser/SwitchTrait.php index 33d2b6a1..293cf324 100644 --- a/src/Parser/SwitchTrait.php +++ b/src/Parser/SwitchTrait.php @@ -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) { diff --git a/version.txt b/version.txt index 87e831c3..fc15416f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1094 \ No newline at end of file +1095