fix(optimizer): 修复含占位符参数的函数调用优化

在 FuncCallOptimizer 中遇到占位符表达式(如 First-class callable)时跳过优化,
避免参数遍历中的类型处理错误导致运行时异常。
pull/14/head
韩天峰 2 months ago
parent 1d2dab4875
commit 4fc0acc47f
  1. 6
      src/Php/Optimizer/FuncCallOptimizer.php
  2. 29
      tests/aot/symfony/array-map-first-class-builtin.phpt
  3. 33
      tests/aot/symfony/context-array-union.phpt
  4. 28
      tests/aot/symfony/dynamic-class-new-with-null-coalesce.phpt
  5. 20
      tests/aot/symfony/nested-static-closure-normalizer.phpt

@ -196,6 +196,9 @@ trait FuncCallOptimizer
protected function parseFuncCallWithOptimizer(string $name, Node\Expr\FuncCall $expr): string|false
{
foreach ($expr->args as $arg) {
if ($this->isPlaceholderExpr($arg)) {
return false;
}
if ($arg instanceof Node\Arg && $arg->name !== null) {
return false;
}
@ -209,6 +212,9 @@ trait FuncCallOptimizer
// 检测参数中使用的变量是否已定义,若变量不存在则回退到动态调用路径
// 动态路径中的 parseCallArgs() 会给出明确的错误信息
foreach ($expr->args as $arg) {
if (!$arg instanceof Node\Arg) {
continue;
}
if ($this->isVarExpr($arg->value) && is_string($arg->value->name) && !$this->hasVar($arg->value->name)) {
return false;
}

@ -0,0 +1,29 @@
--TEST--
Symfony pattern: array_map with first-class builtin callable
--FILE--
<?php
function normalizeExpectedTypes(array $expectedTypes): array
{
return $expectedTypes ? array_map(strval(...), $expectedTypes) : $expectedTypes;
}
function main(): void
{
var_dump(normalizeExpectedTypes([1, 2.5, true, 'name']));
var_dump(normalizeExpectedTypes([]));
}
?>
--EXPECT--
array(4) {
[0]=>
string(1) "1"
[1]=>
string(3) "2.5"
[2]=>
string(1) "1"
[3]=>
string(4) "name"
}
array(0) {
}

@ -0,0 +1,33 @@
--TEST--
Symfony pattern: context array union keeps left-hand keys
--FILE--
<?php
class SymfonyLikeSender
{
}
function logContext(object $message, string $alias, object $sender): array
{
$context = [
'class' => $message::class,
'alias' => 'existing',
];
return $context + ['alias' => $alias, 'sender' => $sender::class];
}
function main(): void
{
var_dump(logContext(new stdClass(), 'async', new SymfonyLikeSender()));
}
?>
--EXPECT--
array(3) {
["class"]=>
string(8) "stdClass"
["alias"]=>
string(8) "existing"
["sender"]=>
string(17) "SymfonyLikeSender"
}

@ -0,0 +1,28 @@
--TEST--
Symfony pattern: dynamic class instantiation with null coalesce default
--FILE--
<?php
class SymfonyLikeNamedService
{
public function __construct(public string $name = 'default')
{
}
}
function createService(?string $class, ?string $name = null): object
{
$class ??= SymfonyLikeNamedService::class;
return new $class($name ?? 'fallback');
}
function main(): void
{
var_dump(createService(null)->name);
var_dump(createService(SymfonyLikeNamedService::class, 'custom')->name);
}
?>
--EXPECT--
string(8) "fallback"
string(6) "custom"

@ -0,0 +1,20 @@
--TEST--
Symfony pattern: static closure normalizer returns another static closure
--FILE--
<?php
function buildTranslator(string $domain): Closure
{
$normalizer = static fn (string $message): Closure => static fn () => '['.$domain.'] '.$message;
return $normalizer('upload failed');
}
function main(): void
{
$message = buildTranslator('validators');
var_dump($message());
}
?>
--EXPECT--
string(26) "[validators] upload failed"
Loading…
Cancel
Save