Merge pull request '修复三元表达式 issue' (#45) from fix-bool-ternary-expr into master

Reviewed-on: #45
pull/47/head
韩天峰 3 weeks ago
commit dbd12d249d
  1. 35
      src/Parser/SelectionExpressionTrait.php
  2. 27
      tests/compiler/ternary-ref-captured.phpt
  3. 29
      tests/compiler/ternary-typed-arg.phpt
  4. 26
      tests/compiler/ternary-void-captured.phpt

@ -22,6 +22,9 @@ trait SelectionExpressionTrait
$this->assertExprCanBeUsedAsCondition($expr->cond, 'ternary condition');
$this->assertExprCanBeUsedAsValue($expr->if, 'ternary branch');
$this->assertExprCanBeUsedAsValue($expr->else, 'ternary branch');
$ifType = $this->detectTypeOfExpr($expr->if);
$elseType = $this->detectTypeOfExpr($expr->else);
$typeChanged = $ifType !== $elseType;
[$cond, $condBeforeStmts, $condAfterStmts] = $this->parseExprWithCapturedStmts($expr->cond);
$ifBeforeStmtCount = count($this->context->beforeStmtLines);
$ifAfterStmtCount = count($this->context->afterStmtLines);
@ -40,13 +43,15 @@ trait SelectionExpressionTrait
$this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $elseAfterStmtCount);
$hasBranchStmts = $condBeforeStmts || $condAfterStmts || $ifBeforeStmts || $ifAfterStmts || $elseBeforeStmts || $elseAfterStmts;
$typeChanged = $this->detectTypeOfExpr($expr->if) !== $this->detectTypeOfExpr($expr->else);
if (!$hasBranchStmts && $typeChanged) {
$if = 'php::Var(' . $if . ')';
$else = 'php::Var(' . $else . ')';
}
if ($hasBranchStmts) {
$code = '[&]() -> ' . Type::VAR . '{';
// REF and VOID are expression implementation types, not valid
// by-value result types for the materializing lambda.
$ternaryType = $this->getNormalAssignType($typeChanged ? Type::VAR : $ifType);
$code = '[&]() -> ' . $ternaryType . '{';
$code .= $this->formatCapturedStmtLines($condBeforeStmts);
if ($condAfterStmts) {
$condTmpVar = $this->addTmpVar(Type::VAR);
@ -56,9 +61,9 @@ trait SelectionExpressionTrait
}
$cond = $this->convertConditionExpr($expr->cond, $cond);
$code .= $this->getIndent() . 'if (' . $cond . ') {';
$code .= $this->formatTernaryReturn($if, $ifBeforeStmts, $ifAfterStmts);
$code .= $this->formatTernaryReturn($expr->if, $if, $ifBeforeStmts, $ifAfterStmts, $ternaryType, $ifType);
$code .= $this->getIndent() . '} else {';
$code .= $this->formatTernaryReturn($else, $elseBeforeStmts, $elseAfterStmts);
$code .= $this->formatTernaryReturn($expr->else, $else, $elseBeforeStmts, $elseAfterStmts, $ternaryType, $elseType);
$code .= $this->getIndent() . '}';
$code .= $this->getIndent() . '}()';
return $code;
@ -67,16 +72,30 @@ trait SelectionExpressionTrait
return '(' . $cond . ') ? (' . $if . ') : (' . $else . ')';
}
protected function formatTernaryReturn(string $value, array $beforeStmts, array $afterStmts): string
protected function formatTernaryReturn(
NodeAbstract $valueExpr,
string $value,
array $beforeStmts,
array $afterStmts,
string $returnType,
string $valueType,
): string
{
$code = $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) {
$tmpVar = $this->addTmpVar(Type::VAR);
$returnsReference = $valueType === Type::REF
|| ($valueExpr instanceof Expr\CallLike && $this->resolveRefReturningCall($valueExpr) !== false);
if ($returnType !== Type::VAR && ($beforeStmts || $afterStmts)) {
$value = $this->convertExprFromType($returnType, $value);
}
if ($afterStmts || $returnsReference) {
$tmpVar = $this->addTmpVar($returnType);
$code .= $this->getIndent() . "{$tmpVar} = {$value};";
$code .= $this->formatCapturedStmtLines($afterStmts);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
} else {
$code .= $this->getIndent() . 'return php::Var(' . $value . ');';
$code .= $returnType === Type::VAR
? $this->getIndent() . 'return php::Var(' . $value . ');'
: $this->getIndent() . 'return ' . $value . ';';
}
return $code;
}

@ -0,0 +1,27 @@
--TEST--
Ternary with captured statements materializes reference returns as values
--FILE--
<?php
function &ternary_ref_value(mixed &$value): mixed
{
return $value;
}
function main(): void
{
$first = 1;
$second = 2;
$values = [1];
$result = count($values) > 0
? ternary_ref_value($first)
: ternary_ref_value($second);
$result = 9;
var_dump($first, $second, $result);
}
?>
--EXPECT--
int(1)
int(2)
int(9)

@ -0,0 +1,29 @@
--TEST--
Ternary with captured statements keeps its static type for typed arguments
--FILE--
<?php
declare(strict_types=1);
class TernaryBoolArg
{
public function takeBool(bool $flag): bool
{
return $flag;
}
}
function main(): void
{
$range = [1, 2, 3];
$obj = new TernaryBoolArg();
// The condition contains a function call, which forces the ternary into a
// captured-statement lambda. The lambda must still yield php::Bool so it
// can feed the typed parameter.
var_dump($obj->takeBool(count($range) > 1 ? true : false));
var_dump($obj->takeBool(count($range) > 5 ? true : false));
}
?>
--EXPECT--
bool(true)
bool(false)

@ -0,0 +1,26 @@
--TEST--
Ternary with captured statements preserves void expression semantics
--FILE--
<?php
function ternary_void_side_effect(string $value): void
{
echo $value, "\n";
}
function main(): void
{
$values = [1, 2];
var_dump(count($values) > 1
? ternary_void_side_effect('if')
: ternary_void_side_effect('else'));
var_dump(count($values) > 5
? ternary_void_side_effect('if')
: ternary_void_side_effect('else'));
}
?>
--EXPECT--
if
NULL
else
NULL
Loading…
Cancel
Save