diff --git a/src/Parser/SwitchTrait.php b/src/Parser/SwitchTrait.php index 5e9829cf..82441a67 100644 --- a/src/Parser/SwitchTrait.php +++ b/src/Parser/SwitchTrait.php @@ -38,11 +38,29 @@ protected function parseSwitch(Node\Stmt\Switch_ $v): string $var_def .= $type . ' ' . $tmp_var . ' = ' . $condExpr . ';' . PHP_EOL; $var_def .= $this->formatCapturedStmtLines($condAfterStmts); - // Save the scope; switch parsing may fail partway and add variables in the process, so it must be reset - $localVars = $this->context->localVars; - $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; if ($type === Type::INT or $type === Type::BOOL) { + // Check all labels before lowering any bodies. C++ requires integer + // labels; for bool subjects, only 0 and 1 preserve PHP comparison. + $nativeCaseValues = []; + foreach ($v->cases as $case) { + $caseCond = $case->cond; + if ($caseCond === null) { + continue; + } + if (!$caseCond instanceof Node\Scalar\Int_ + || ($type === Type::BOOL && $caseCond->value !== 0 && $caseCond->value !== 1) + ) { + goto _fail; + } + if (isset($nativeCaseValues[$caseCond->value])) { + // PHP permits duplicate case values and selects the first + // matching label, while C++ rejects duplicate case labels. + goto _fail; + } + $nativeCaseValues[$caseCond->value] = true; + } $code .= 'do {' . PHP_EOL; $this->indentLevel++; $code .= $this->getIndent() . 'switch (' . $tmp_var . ') {' . PHP_EOL; @@ -51,12 +69,6 @@ protected function parseSwitch(Node\Stmt\Switch_ $v): string if (empty($case->cond)) { $code .= $this->getIndent() . 'default: {' . PHP_EOL; } else { - $condType = $case->cond->getType(); - if ($condType !== 'Scalar_Int' and $condType !== 'Scalar_Float') { - $this->context->localVars = $localVars; - $this->indentLevel -= 2; - goto _fail; - } $code .= $this->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL; } $code .= $this->parseBlockStmts($case->stmts); @@ -135,6 +147,9 @@ protected function parseSwitch(Node\Stmt\Switch_ $v): string $code .= $this->formatCapturedStmtLines($caseAfterStmts); $caseCondExpr = $caseTmpVar; } + if ($type === Type::BOOL) { + $caseCondExpr = 'php::toBool(' . $caseCondExpr . ')'; + } $code .= $this->getIndent() . $groupMatched . ' = php::equals(' . $tmp_var . ', ' . $caseCondExpr . ');' . PHP_EOL; $code .= $this->getIndent() . '}' . PHP_EOL; } diff --git a/tests/compiler/switch/bool-numeric-case.phpt b/tests/compiler/switch/bool-numeric-case.phpt new file mode 100644 index 00000000..252829a9 --- /dev/null +++ b/tests/compiler/switch/bool-numeric-case.phpt @@ -0,0 +1,40 @@ +--TEST-- +Boolean switch subjects use PHP loose comparison for numeric cases +--FILE-- + +--EXPECT-- +nonzero +zero +true +false diff --git a/tests/compiler/switch/duplicate-integer-case.phpt b/tests/compiler/switch/duplicate-integer-case.phpt new file mode 100644 index 00000000..881115be --- /dev/null +++ b/tests/compiler/switch/duplicate-integer-case.phpt @@ -0,0 +1,41 @@ +--TEST-- +Duplicate integer switch cases retain PHP first-match semantics +--FILE-- + +--EXPECT-- +first-int +default-int +first-bool +default-bool diff --git a/tests/compiler/switch/int-float-case.phpt b/tests/compiler/switch/int-float-case.phpt new file mode 100644 index 00000000..b53b8afc --- /dev/null +++ b/tests/compiler/switch/int-float-case.phpt @@ -0,0 +1,29 @@ +--TEST-- +Integer switch subjects accept floating-point case labels +--FILE-- + +--EXPECT-- +zero +default +two