fix(optimizer): handle first-class callable placeholders in property optimizer

- Add check for Node\Arg instance before accessing arg properties
- Skip non-Node\Arg arguments that represent callable placeholders
- Prevent errors when processing first-class callables like $object->method(...)
- Ensure property optimizer correctly handles variadic placeholders
- Maintain existing refval function call detection logic
- Add test case for first-class callable property optimization scenario
master
韩天峰 2 weeks ago
parent f79d40f8ee
commit bca77588b5
  1. 12
      src/Optimizer/SsaPropOptimizer.php
  2. 32
      tests/compiler/callable/first-class-callable-property-optimizer.phpt

@ -452,7 +452,8 @@ trait SsaPropOptimizer
if ($node instanceof Expr\FuncCall
&& $node->name instanceof Node\Name
&& $node->name->toLowerString() === 'refval'
&& !empty($node->args)) {
&& isset($node->args[0])
&& $node->args[0] instanceof Node\Arg) {
$propName = $this->getPropNameOfObj($node->args[0]->value, $objName);
if ($propName !== null) {
$events[] = ['kind' => 'danger', 'prop' => $propName];
@ -476,6 +477,12 @@ trait SsaPropOptimizer
$this->collectPropEvents($node->var, $objName, $events);
}
foreach ($node->args as $arg) {
// A first-class callable such as $object->method(...) stores a
// VariadicPlaceholder in args. It denotes callable creation,
// not an argument expression, and has no byRef/value fields.
if (!$arg instanceof Node\Arg) {
continue;
}
$propName = $arg->byRef ? $this->getPropNameOfObj($arg->value, $objName) : null;
if ($propName !== null) {
$events[] = ['kind' => 'danger', 'prop' => $propName];
@ -490,6 +497,9 @@ trait SsaPropOptimizer
$events[] = ['kind' => 'danger', 'prop' => '*'];
}
foreach ($node->args as $arg) {
if (!$arg instanceof Node\Arg) {
continue;
}
if ($this->exprMayExposeObject($arg->value, $objName)) {
$events[] = ['kind' => 'danger', 'prop' => '*'];
}

@ -0,0 +1,32 @@
--TEST--
First-class callable placeholder is not treated as a call argument by property optimizer
--FILE--
<?php
use native_types;
class FirstClassCallableTarget
{
public int $counter = 0;
public function tick(): void
{
$this->counter++;
}
public function invoke(callable $callback): void
{
$callback();
}
}
function main(): void
{
$target = new FirstClassCallableTarget();
$target->invoke($target->tick(...));
var_dump($target->counter);
}
?>
--EXPECT--
int(1)
Loading…
Cancel
Save