fix(php): 修复可选参数中的null值处理问题

- 在函数调用优化器中添加对可空参数的支持
- 修改buildArgList方法以接收和处理nullables数组
- 当参数允许null值时直接传递原始变量而非解析值
- 更新函数反射信息以包含参数可空性信息
- 调整测试用例以反映explode函数中null限制的行为变化
- 移除显式传入null时使用默认值的特殊处理逻辑
pull/2/head
韩天峰 3 months ago
parent f45dadc2f8
commit db5de84468
  1. 29
      src/Php/Optimizer/FuncCallOptimizer.php
  2. 5
      tests/aot/stdlib/null_optional_arg.phpt

@ -248,7 +248,8 @@ trait FuncCallOptimizer
} }
} }
$args = $this->buildArgList($expr, $argTypeStr, $defaults); $nullables = $refInfo['nullables'] ?? [];
$args = $this->buildArgList($expr, $argTypeStr, $defaults, $nullables);
return $target . '(' . implode(', ', $args) . ')'; return $target . '(' . implode(', ', $args) . ')';
} }
@ -264,10 +265,11 @@ trait FuncCallOptimizer
$ref = Reflection::getFunction($funcName); $ref = Reflection::getFunction($funcName);
if (!$ref) { if (!$ref) {
return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false, 'variadicType' => '', 'minArgs' => 0, 'maxArgs' => 0]; return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false, 'variadicType' => '', 'minArgs' => 0, 'maxArgs' => 0, 'nullables' => []];
} }
$types = []; $types = [];
$nullables = [];
$variadic = false; $variadic = false;
$variadicType = ''; $variadicType = '';
foreach ($ref->getParameters() as $param) { foreach ($ref->getParameters() as $param) {
@ -281,6 +283,7 @@ trait FuncCallOptimizer
$char = self::ARG_OPTIONAL . $char; $char = self::ARG_OPTIONAL . $char;
} }
$types[] = $char; $types[] = $char;
$nullables[] = $param->allowsNull();
} }
return $this->_autoArgTypes[$funcName] = [ return $this->_autoArgTypes[$funcName] = [
@ -289,6 +292,7 @@ trait FuncCallOptimizer
'variadicType' => $variadicType, 'variadicType' => $variadicType,
'minArgs' => $ref->getNumberOfRequiredParameters(), 'minArgs' => $ref->getNumberOfRequiredParameters(),
'maxArgs' => $ref->getNumberOfParameters(), 'maxArgs' => $ref->getNumberOfParameters(),
'nullables' => $nullables,
]; ];
} }
@ -375,7 +379,7 @@ trait FuncCallOptimizer
return $this->convertArrayExpr($raw); return $this->convertArrayExpr($raw);
} }
protected function buildArgList(Node\Expr\FuncCall $expr, string $argTypeStr, array $defaults = []): array protected function buildArgList(Node\Expr\FuncCall $expr, string $argTypeStr, array $defaults = [], array $nullables = []): array
{ {
if ($argTypeStr === '') { if ($argTypeStr === '') {
return []; return [];
@ -387,23 +391,22 @@ trait FuncCallOptimizer
foreach ($types as $i => $type) { foreach ($types as $i => $type) {
$optional = ($type[0] ?? '') === self::ARG_OPTIONAL; $optional = ($type[0] ?? '') === self::ARG_OPTIONAL;
$nullable = $nullables[$i] ?? false;
// Missing optional arg — use configured default or skip (C++ default handles it)
if ($optional && $argCount <= $i) { if ($optional && $argCount <= $i) {
if (isset($defaults[$i])) { if (isset($defaults[$i])) {
$args[] = $defaults[$i]; $args[] = $defaults[$i];
} }
continue; continue;
} }
// When null is explicitly passed to an optional parameter, skip it
// so the C++ default value takes effect (PHP null = "use default"). // Nullable param — pass raw Variant; C++ function checks isNull() at runtime
if ($optional && $argCount > $i && isset($expr->args[$i])) { if ($nullable) {
$argVal = $expr->args[$i]->value; $args[] = $this->getArg($expr, $i);
if ($argVal instanceof Node\Expr\ConstFetch && strtolower($argVal->name->toString()) === 'null') { continue;
if (isset($defaults[$i])) {
$args[] = $defaults[$i];
}
continue;
}
} }
$args[] = $this->resolveArg($expr, $i, $type); $args[] = $this->resolveArg($expr, $i, $type);
} }

@ -2,6 +2,7 @@
Passing null to optional parameters should use C++ default values Passing null to optional parameters should use C++ default values
--FILE-- --FILE--
<?php <?php
error_reporting(E_ALL & ~E_DEPRECATED);
// substr: null length should take rest of string, not return empty // substr: null length should take rest of string, not return empty
$s = 'hello world'; $s = 'hello world';
@ -29,7 +30,7 @@ var_dump(strstr($s, 'o', null) === strstr($s, 'o'));
// str_repeat: ensure non-null still works // str_repeat: ensure non-null still works
var_dump(str_repeat('ab', 3)); var_dump(str_repeat('ab', 3));
// explode with null limit should default to PHP_INT_MAX (no limit) // explode with null limit: null coerces to 0 (limit=0: whole string as single element)
$arr = explode(' ', 'a b c d', null); $arr = explode(' ', 'a b c d', null);
var_dump(count($arr)); var_dump(count($arr));
@ -48,4 +49,4 @@ bool(true)
string(7) "o world" string(7) "o world"
bool(true) bool(true)
string(6) "ababab" string(6) "ababab"
int(4) int(1)

Loading…
Cancel
Save