feat(parser): implement nullsafe operator temporary variable lifecycle management

- Add ownedTmpVars array to track temporary variables in nullsafe operations
- Register object temporary variables when creating nullsafe access chains
- Track intermediate temporary variables during nullsafe traversal
- Add cleanup logic to release C++ temporaries at PHP statement boundaries
- Generate unset calls for tracked temporaries in reverse order
- Create comprehensive test case for nullsafe receiver temporary lifetime
- Verify weak reference target release after nullsafe statement completion
master
韩天峰 2 weeks ago
parent 1456305ade
commit ea08344c92
  1. 8
      src/Parser/NullsafeAccessTrait.php
  2. 55
      tests/compiler/nullsafe/receiver-temporary-lifetime.phpt

@ -39,6 +39,7 @@ trait NullsafeAccessTrait
): string
{
$list = [];
$ownedTmpVars = [];
$comment = $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr]));
while (1) {
@ -66,6 +67,7 @@ trait NullsafeAccessTrait
}
}
$object = $this->addTmpVar(Type::OBJECT);
$ownedTmpVars[] = $object;
$this->context->beforeStmtLines[] = $this->getIndent() . $object . ' = ' . $this->parseIdentifier($expr) . ';';
break;
}
@ -80,6 +82,7 @@ trait NullsafeAccessTrait
foreach ($list as $key => $item) {
$tmpVar = $this->addTmpVar($key !== $last ? Type::OBJECT : Type::VAR);
$ownedTmpVars[] = $tmpVar;
if ($item[3]) {
$code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }';
}
@ -117,6 +120,11 @@ trait NullsafeAccessTrait
}
$code .= $this->getIndent() . "return {$object}; };";
$this->context->beforeStmtLines[] = $code;
// C++ temporaries are function-scoped; release their zvals at the PHP statement boundary.
foreach (array_reverse($ownedTmpVars) as $tmpVar) {
$this->context->afterStmtLines[] = $tmpVar . '.unset();';
}
return "{$tmpFn}()";
}

@ -0,0 +1,55 @@
--TEST--
Nullsafe receiver temporaries release weak targets after the statement
--FILE--
<?php
final class NullsafeWeakReceiver
{
public string $value = 'probe';
public function label(): string
{
return $this->value;
}
}
function methodReceiver(): array
{
$target = new NullsafeWeakReceiver();
$weak = WeakReference::create($target);
$observed = $weak->get()?->label();
unset($target);
gc_collect_cycles();
return [$observed, $weak->get() === null];
}
function propertyReceiver(): array
{
$target = new NullsafeWeakReceiver();
$weak = WeakReference::create($target);
$observed = $weak->get()?->value;
unset($target);
gc_collect_cycles();
return [$observed, $weak->get() === null];
}
function main(): void
{
var_dump(methodReceiver());
var_dump(propertyReceiver());
}
?>
--EXPECT--
array(2) {
[0]=>
string(5) "probe"
[1]=>
bool(true)
}
array(2) {
[0]=>
string(5) "probe"
[1]=>
bool(true)
}
Loading…
Cancel
Save