fix(compiler): preserve PHP switch comparison semantics (#89)

master
yavon007 1 month ago committed by GitHub
parent 5488805baf
commit 6aae5f76d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 33
      src/Parser/SwitchTrait.php
  2. 40
      tests/compiler/switch/bool-numeric-case.phpt
  3. 41
      tests/compiler/switch/duplicate-integer-case.phpt
  4. 29
      tests/compiler/switch/int-float-case.phpt

@ -38,11 +38,29 @@ trait SwitchTrait
$var_def .= $type . ' ' . $tmp_var . ' = ' . $condExpr . ';' . PHP_EOL; $var_def .= $type . ' ' . $tmp_var . ' = ' . $condExpr . ';' . PHP_EOL;
$var_def .= $this->formatCapturedStmtLines($condAfterStmts); $var_def .= $this->formatCapturedStmtLines($condAfterStmts);
// Save the scope; switch parsing may fail partway and add variables in the process, so it must be reset $code = $this->parseBeforeStmtLines() . PHP_EOL;
$localVars = $this->context->localVars;
$code = $this->parseBeforeStmtLines() . PHP_EOL;
if ($type === Type::INT or $type === Type::BOOL) { 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; $code .= 'do {' . PHP_EOL;
$this->indentLevel++; $this->indentLevel++;
$code .= $this->getIndent() . 'switch (' . $tmp_var . ') {' . PHP_EOL; $code .= $this->getIndent() . 'switch (' . $tmp_var . ') {' . PHP_EOL;
@ -51,12 +69,6 @@ trait SwitchTrait
if (empty($case->cond)) { if (empty($case->cond)) {
$code .= $this->getIndent() . 'default: {' . PHP_EOL; $code .= $this->getIndent() . 'default: {' . PHP_EOL;
} else { } 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->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL;
} }
$code .= $this->parseBlockStmts($case->stmts); $code .= $this->parseBlockStmts($case->stmts);
@ -135,6 +147,9 @@ trait SwitchTrait
$code .= $this->formatCapturedStmtLines($caseAfterStmts); $code .= $this->formatCapturedStmtLines($caseAfterStmts);
$caseCondExpr = $caseTmpVar; $caseCondExpr = $caseTmpVar;
} }
if ($type === Type::BOOL) {
$caseCondExpr = 'php::toBool(' . $caseCondExpr . ')';
}
$code .= $this->getIndent() . $groupMatched . ' = php::equals(' . $tmp_var . ', ' . $caseCondExpr . ');' . PHP_EOL; $code .= $this->getIndent() . $groupMatched . ' = php::equals(' . $tmp_var . ', ' . $caseCondExpr . ');' . PHP_EOL;
$code .= $this->getIndent() . '}' . PHP_EOL; $code .= $this->getIndent() . '}' . PHP_EOL;
} }

@ -0,0 +1,40 @@
--TEST--
Boolean switch subjects use PHP loose comparison for numeric cases
--FILE--
<?php
function select_bool(bool $value): string
{
switch ($value) {
case 2:
return 'nonzero';
case 0:
return 'zero';
default:
return 'default';
}
}
function select_binary_bool(bool $value): string
{
switch ($value) {
case 0:
return 'false';
case 1:
return 'true';
}
return 'default';
}
function main(): void
{
echo select_bool(true), "\n";
echo select_bool(false), "\n";
echo select_binary_bool(true), "\n";
echo select_binary_bool(false), "\n";
}
?>
--EXPECT--
nonzero
zero
true
false

@ -0,0 +1,41 @@
--TEST--
Duplicate integer switch cases retain PHP first-match semantics
--FILE--
<?php
function select_duplicate_int(int $value): string
{
switch ($value) {
case 1:
return 'first-int';
case 1:
return 'second-int';
default:
return 'default-int';
}
}
function select_duplicate_bool(bool $value): string
{
switch ($value) {
case 1:
return 'first-bool';
case 1:
return 'second-bool';
default:
return 'default-bool';
}
}
function main(): void
{
echo select_duplicate_int(1), "\n";
echo select_duplicate_int(2), "\n";
echo select_duplicate_bool(true), "\n";
echo select_duplicate_bool(false), "\n";
}
?>
--EXPECT--
first-int
default-int
first-bool
default-bool

@ -0,0 +1,29 @@
--TEST--
Integer switch subjects accept floating-point case labels
--FILE--
<?php
function select_number(int $value): string
{
switch ($value) {
case 0:
return 'zero';
case 1.5:
return 'fraction';
case 2.0:
return 'two';
default:
return 'default';
}
}
function main(): void
{
echo select_number(0), "\n";
echo select_number(1), "\n";
echo select_number(2), "\n";
}
?>
--EXPECT--
zero
default
two
Loading…
Cancel
Save