From b89dd177b30a10ea13972c6647a9a2609d8f91e4 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 10 Jun 2026 13:04:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E5=8F=98=E5=8F=82=E6=95=B0=E8=81=94=E5=90=88=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 arginfo 生成中添加对可变参数的支持 - 实现可变参数联合类型的运行时类型检查逻辑 - 添加循环遍历可变参数并逐个验证类型的功能 - 生成适当的错误消息以指示可变参数中的具体错误位置 - 添加测试用例验证可变参数联合类型和可空类型的运行时检查功能 --- src/Php/Generator/TypeCheckGenerator.php | 58 +++++++++++++++++-- src/gen_stub.php | 10 +++- .../type_decl/variadic-union-param-check.phpt | 55 ++++++++++++++++++ 3 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 tests/aot/type_decl/variadic-union-param-check.phpt diff --git a/src/Php/Generator/TypeCheckGenerator.php b/src/Php/Generator/TypeCheckGenerator.php index 1c8e4843..ce7ac739 100644 --- a/src/Php/Generator/TypeCheckGenerator.php +++ b/src/Php/Generator/TypeCheckGenerator.php @@ -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; diff --git a/src/gen_stub.php b/src/gen_stub.php index 5aaaa0cd..3565b900 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -868,9 +868,15 @@ class ArgInfo { !$this->isVariadic ? ", " . $this->getDefaultValueAsArginfoString() : "" ); } + if ($this->isVariadic) { + return sprintf( + "\tZEND_ARG_VARIADIC_TYPE_INFO(%s, %s, IS_MIXED, 0)\n", + $this->sendBy, $this->name + ); + } return sprintf( - "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", - $argKind, $this->sendBy, $this->name, + "\tZEND_ARG_TYPE_MASK(%s, %s, %s, %s)\n", + $this->sendBy, $this->name, $arginfoType->toTypeMask(), $this->getDefaultValueAsArginfoString() ); diff --git a/tests/aot/type_decl/variadic-union-param-check.phpt b/tests/aot/type_decl/variadic-union-param-check.phpt new file mode 100644 index 00000000..0ad7a0d7 --- /dev/null +++ b/tests/aot/type_decl/variadic-union-param-check.phpt @@ -0,0 +1,55 @@ +--TEST-- +Variadic union and nullable parameter runtime type checking +--FILE-- +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"