fix(compiler): complete captured ternary type handling

pull/45/head
韩天峰 3 weeks ago
parent cee9ee6e1b
commit c4404c15c5
  1. 31
      src/Parser/SelectionExpressionTrait.php
  2. 27
      tests/compiler/ternary-ref-captured.phpt
  3. 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,16 +43,14 @@ 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) {
// The materialized temporary (see getOrderedOperandTmpType) is
// declared with the ternary's static type, so the lambda return
// type must match it instead of always being php::Var.
$ternaryType = $this->detectTypeOfExpr($expr);
// 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) {
@ -60,9 +61,9 @@ trait SelectionExpressionTrait
}
$cond = $this->convertConditionExpr($expr->cond, $cond);
$code .= $this->getIndent() . 'if (' . $cond . ') {';
$code .= $this->formatTernaryReturn($if, $ifBeforeStmts, $ifAfterStmts, $ternaryType);
$code .= $this->formatTernaryReturn($expr->if, $if, $ifBeforeStmts, $ifAfterStmts, $ternaryType, $ifType);
$code .= $this->getIndent() . '} else {';
$code .= $this->formatTernaryReturn($else, $elseBeforeStmts, $elseAfterStmts, $ternaryType);
$code .= $this->formatTernaryReturn($expr->else, $else, $elseBeforeStmts, $elseAfterStmts, $ternaryType, $elseType);
$code .= $this->getIndent() . '}';
$code .= $this->getIndent() . '}()';
return $code;
@ -71,10 +72,22 @@ trait SelectionExpressionTrait
return '(' . $cond . ') ? (' . $if . ') : (' . $else . ')';
}
protected function formatTernaryReturn(string $value, array $beforeStmts, array $afterStmts, string $returnType): string
protected function formatTernaryReturn(
NodeAbstract $valueExpr,
string $value,
array $beforeStmts,
array $afterStmts,
string $returnType,
string $valueType,
): string
{
$code = $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) {
$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);

@ -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,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