feat(generator): 添加可变参数联合类型检查支持

- 在 arginfo 生成中添加对可变参数的支持
- 实现可变参数联合类型的运行时类型检查逻辑
- 添加循环遍历可变参数并逐个验证类型的功能
- 生成适当的错误消息以指示可变参数中的具体错误位置
- 添加测试用例验证可变参数联合类型和可空类型的运行时检查功能
pull/1/head
韩天峰 3 months ago
parent 8c411085d1
commit b89dd177b3
  1. 58
      src/Php/Generator/TypeCheckGenerator.php
  2. 10
      src/gen_stub.php
  3. 55
      tests/aot/type_decl/variadic-union-param-check.phpt

@ -126,6 +126,10 @@ trait TypeCheckGenerator
}
$varName = $argInfo->name;
if ($argInfo->variadic) {
return $this->genUnionVariadicParamCheck($argInfo, $argIndex);
}
$conditions = [];
foreach ($argInfo->typeCheck as $entry) {
$cond = $this->genSingleTypeCondition($varName, $entry);
@ -138,10 +142,7 @@ trait TypeCheckGenerator
}
$orExpr = implode(' || ', $conditions);
$fnName = $this->functionDef->getNamespacedName();
$msgExpr = 'php::concat(php::concat(php::Str(' . $this->genCharPtr($fnName, true) . ' "(): Argument #' . ($argIndex + 1)
. ' ($' . $varName . ') must be of type " ' . $this->genCharPtr($argInfo->typeStr, true) . ' ", "), '
. $varName . '.typeStr()), php::Str(" given"))';
$msgExpr = $this->genUnionParamTypeErrorExpr($argInfo, $varName, (string) ($argIndex + 1));
$code = $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL;
$this->indentLevel++;
@ -152,6 +153,55 @@ trait TypeCheckGenerator
return $code;
}
protected function genUnionVariadicParamCheck(ArgInfo $argInfo, int $argIndex): string
{
$valueVar = $this->genTmpVarName();
$iterVar = $this->genTmpVarName();
$argNoVar = $this->genTmpVarName();
$conditions = [];
foreach ($argInfo->typeCheck as $entry) {
$cond = $this->genSingleTypeCondition($valueVar, $entry);
if ($cond !== '') {
$conditions[] = $cond;
}
}
if (empty($conditions)) {
return '';
}
$orExpr = implode(' || ', $conditions);
$msgExpr = $this->genUnionParamTypeErrorExpr($argInfo, $valueVar, $argNoVar);
$code = $this->getIndent() . 'for (auto ' . $iterVar . ' = ' . $argInfo->name . '.begin(); ' . $iterVar . ' != ' . $argInfo->name . '.end(); ++' . $iterVar . ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = ' . $iterVar . '.value();' . PHP_EOL;
$code .= $this->getIndent() . self::TYPE_INT . ' ' . $argNoVar . ' = ' . ($argIndex + 1) . ' + ' . $iterVar . '.index();' . PHP_EOL;
$code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'php::throwException(zend_ce_type_error, (' . $msgExpr . ').toCString());' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
}
protected function genUnionParamTypeErrorExpr(ArgInfo $argInfo, string $valueExpr, string $argNoExpr): string
{
$fnName = $this->functionDef->getNamespacedName();
return 'php::concat({'
. 'php::Str(' . $this->genCharPtr($fnName . '(): Argument #', true) . '), '
. 'php::toString(' . $argNoExpr . '), '
. 'php::Str(' . $this->genCharPtr(' ($' . $argInfo->name . ') must be of type ', true) . '), '
. 'php::Str(' . $this->genCharPtr($argInfo->typeStr, true) . '), '
. 'php::Str(", "), '
. $valueExpr . '.typeStr(), '
. 'php::Str(" given")'
. '})';
}
protected function genUnionReturnCheck(string $varName): string
{
$typeCheck = $this->functionDef->returnTypeCheck;

@ -868,9 +868,15 @@ class ArgInfo {
!$this->isVariadic ? ", " . $this->getDefaultValueAsArginfoString() : ""
);
}
if ($this->isVariadic) {
return sprintf(
"\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n",
$argKind, $this->sendBy, $this->name,
"\tZEND_ARG_VARIADIC_TYPE_INFO(%s, %s, IS_MIXED, 0)\n",
$this->sendBy, $this->name
);
}
return sprintf(
"\tZEND_ARG_TYPE_MASK(%s, %s, %s, %s)\n",
$this->sendBy, $this->name,
$arginfoType->toTypeMask(),
$this->getDefaultValueAsArginfoString()
);

@ -0,0 +1,55 @@
--TEST--
Variadic union and nullable parameter runtime type checking
--FILE--
<?php
function collect_scalars(int|string ...$values): array
{
return $values;
}
function collect_nullable(?int ...$values): array
{
return $values;
}
function main(): void
{
var_dump(collect_scalars(1, "two", named: 3));
var_dump(collect_nullable(1, null, 3));
$errors = [];
try {
collect_scalars(1, "two", []);
} catch (\TypeError $e) {
$errors[] = $e->getMessage();
}
try {
collect_nullable(ok: 1, bad: "x");
} catch (\TypeError $e) {
$errors[] = $e->getMessage();
}
foreach ($errors as $error) {
var_dump($error);
}
}
?>
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
string(3) "two"
["named"]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
NULL
[2]=>
int(3)
}
string(80) "collect_scalars(): Argument #3 ($values) must be of type int|string, array given"
string(76) "collect_nullable(): Argument #2 ($values) must be of type ?int, string given"
Loading…
Cancel
Save