From bca77588b55b4bdc9dfb8ea010ac2e3a802537e3 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 12 Aug 2026 21:35:20 +0800 Subject: [PATCH] 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 --- src/Optimizer/SsaPropOptimizer.php | 12 ++++++- ...rst-class-callable-property-optimizer.phpt | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/compiler/callable/first-class-callable-property-optimizer.phpt diff --git a/src/Optimizer/SsaPropOptimizer.php b/src/Optimizer/SsaPropOptimizer.php index 789ffcf3..6795f6b6 100644 --- a/src/Optimizer/SsaPropOptimizer.php +++ b/src/Optimizer/SsaPropOptimizer.php @@ -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' => '*']; } diff --git a/tests/compiler/callable/first-class-callable-property-optimizer.phpt b/tests/compiler/callable/first-class-callable-property-optimizer.phpt new file mode 100644 index 00000000..7e009fb0 --- /dev/null +++ b/tests/compiler/callable/first-class-callable-property-optimizer.phpt @@ -0,0 +1,32 @@ +--TEST-- +First-class callable placeholder is not treated as a call argument by property optimizer +--FILE-- +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)