From 0bc91a0d6ff618799a9985e5972ed099883532f3 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Sun, 2 Aug 2026 19:40:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Generator/ParamParser.php | 237 ++++++++++++++++++++++++++++++++++ src/Translator.php | 37 ++++++ 2 files changed, 274 insertions(+) create mode 100644 src/Generator/ParamParser.php diff --git a/src/Generator/ParamParser.php b/src/Generator/ParamParser.php new file mode 100644 index 00000000..c3d103b4 --- /dev/null +++ b/src/Generator/ParamParser.php @@ -0,0 +1,237 @@ + $argInfos + * @return string + */ + public function create(array $argInfos): string + { + if (empty($argInfos)) { + return sprintf("do { %s } while(false);", self::NONE_PARAMS_TEMPLATE).PHP_EOL; + } + + [$minNumArgs, $maxNumArgs] = $this->getRequiredAndOptionalParamCounts($argInfos); + $macros[] = $this->genStartParseCode($minNumArgs, $maxNumArgs); + + foreach ($argInfos as $index => $argInfo) { + if ($minNumArgs == 0 || $minNumArgs < $maxNumArgs && $index > $minNumArgs - 1) { + $macros[] = $this->genOptionalDirective(); + } + + $macros[] = $this->genBodyParserCode($argInfo); + } + + $macros[] = $this->genEndParseCode(); + return sprintf( + "do { %s %s } while(false);", + implode(PHP_EOL, $this->paramNames), + implode(PHP_EOL, $macros)); + } + + /** + * @return array + */ + public function getComplexTypes(): array + { + return $this->complexTypes; + } + + /** + * @param array $argInfos + * @return array + */ + private function getRequiredAndOptionalParamCounts(array $argInfos): array + { + if (count($argInfos) == 1 && $argInfos[0]->type === Type::REF) { + return [0, -1]; + } + + $hasRef = false; + foreach ($argInfos as $index => $argInfo) { + if ($argInfo->type === Type::REF) { + $hasRef = true; + break; + } + } + + $requiredParamCount = count(array_filter($argInfos, fn($argInfo) => $argInfo->defaultValue === null)); + return [$requiredParamCount, $hasRef ? -1 : count($argInfos)]; + } + + private function genStartParseCode(int $minNumArgs, int $maxNumArgs): string + { + return sprintf("ZEND_PARSE_PARAMETERS_START(%d, %d)", $minNumArgs, $maxNumArgs); + } + + private function genOptionalDirective(): string + { + return "Z_PARAM_OPTIONAL"; + } + + private function genBodyParserCode(ArgInfo $argInfo): string + { + return match ($argInfo->type) { + Type::BOOL => $this->parseBool($argInfo), + Type::INT => $this->parseInt($argInfo), + Type::FLOAT => $this->parseFloat($argInfo), + Type::ARRAY => $this->parseArray($argInfo), + Type::STR => $this->parseString($argInfo), + Type::ARGS => $this->parseArgs($argInfo), + Type::REF => $this->parseReference($argInfo), + Type::RESOURCE, Type::STREAM => $this->parseResource($argInfo), + Type::OBJECT => $this->parseObject($argInfo), + Type::VAR => $this->parseVar($argInfo), + default => '' + }; + } + + private function genEndParseCode(): string + { + return "ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());"; + } + + private function parseBool(ArgInfo $argInfo): string + { + $name = $argInfo->name; + $isNull = $name.'_is_null'; + + $this->paramNames[] = "zend_bool $name = false; zend_bool $isNull = false;"; + + return $argInfo->nullable + ? "Z_PARAM_BOOL_OR_NULL($name, $isNull)" + : "Z_PARAM_BOOL($name)"; + } + + private function parseInt(ArgInfo $argInfo): string + { + $name = $argInfo->name; + $isNull = $name.'_is_null'; + + $this->paramNames[] = "zend_long $name = 0; zend_bool $isNull = false;"; + + return $argInfo->nullable + ? "Z_PARAM_LONG_OR_NULL($name, $isNull)" + : "Z_PARAM_LONG($name)"; + } + + private function parseFloat(ArgInfo $argInfo): string + { + $name = $argInfo->name; + $isNull = $name.'_is_null'; + + $this->paramNames[] = "double $name = 0; zend_bool $isNull = false;"; + + return $argInfo->nullable + ? "Z_PARAM_DOUBLE_OR_NULL($name, $isNull)" + : "Z_PARAM_DOUBLE($name)"; + } + + private function parseArray(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + $this->paramNames[] = 'zval *'.$name.' = nullptr;'; + return $argInfo->nullable + ? "Z_PARAM_ARRAY_OR_NULL($name)" + : "Z_PARAM_ARRAY($name)"; + } + + private function parseString(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + $this->paramNames[] = "zend_string *".$name.' = nullptr;'; + return $argInfo->nullable + ? "Z_PARAM_STR_OR_NULL($name)" + : "Z_PARAM_STR($name)"; + } + + private function parseArgs(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + $this->paramNames[] = "zval *".$name.'_argv; int '.$name.'_argc = 0;'; + return sprintf("Z_PARAM_ARRAY(*, %s, %s)", $name.'_argv', $name.'_argc'); + } + + private function parseReference(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + $this->paramNames[] = "zval *".$name.' = nullptr;'; + return "Z_PARAM_ZVAL($name)".PHP_EOL; + } + + private function parseResource(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + $this->paramNames[] = "zval *".$name.' = nullptr;'; + return "Z_PARAM_RESOURCE($name)".PHP_EOL; + } + + private function parseObject(ArgInfo $argInfo): string + { + $name = $argInfo->name; + + if ($argInfo->class === 'callble') { + $this->paramNames[] = "zend_fcall_info ".$name.'_fci; zend_fcall_info_cache '.$name.'_fcc;'; + if ($argInfo->nullable) { + return sprintf("Z_PARAM_FUNC_OR_NULL(%s, %s)", $name.'_fci', $name.'_fcc'); + } else { + return sprintf("Z_PARAM_FUNC(%s, %s)", $name.'_fci', $name.'_fcc'); + } + } + + $this->paramNames[] = "zval *".$name.' = nullptr;'; + return $argInfo->nullable + ? "Z_PARAM_OBJECT_OR_NULL($argInfo->name)" + : "Z_PARAM_OBJECT($argInfo->name)"; + } + + private function parseVar(ArgInfo $argInfo): string + { + $name = $argInfo->name; + if ($argInfo->typeNode instanceof NullableType) { + return match ($argInfo->typeNode->type->name) { + "string" => $this->parseString($argInfo), + "array" => $this->parseArgs($argInfo), + "bool" => $this->parseBool($argInfo), + "resource" => $this->parseResource($argInfo), + "float" => $this->parseFloat($argInfo), + default => $this->parseObject($argInfo), + }; + } else { + /** + * Union types and intersection types cannot be resolved through macros. + * Although a few macros are provided for certain union types, their coverage is limited. + * Therefore, we need to adopt a specialized approach to handle them. For now, + * we will defer this implementation until after the current PR is merged, + * so as to keep the PR size manageable and avoid making it too difficult to review. + */ + $this->complexTypes[] = $argInfo; + } + + $this->paramNames[] = "zval *".$name.' = nullptr;'; + return $argInfo->nullable + ? "Z_PARAM_ZVAL_OR_NULL($name)" + : "Z_PARAM_ZVAL($name)"; + } +} diff --git a/src/Translator.php b/src/Translator.php index 52b80885..51155591 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -34,6 +34,7 @@ use TypePhp\Exception\Skip; use TypePhp\Exception\SyntaxError; use TypePhp\Generator\DefaultArgumentGenerator; use TypePhp\Generator\LibraryImportStubGenerator; +use TypePhp\Generator\ParamParser; use TypePhp\Generator\Symbol; use TypePhp\Metadata\Constants; use TypePhp\Platform\PlatformFactory; @@ -92,6 +93,7 @@ class Translator extends Preprocessor */ protected array $classCeList = []; protected array $classCeInfo = []; + private bool $declareStrictTypes = false; protected function isConstructorNativeFunction(FunctionDef $func): bool { @@ -2339,6 +2341,8 @@ CODE; $this->resetClass(); $this->resetMethod(); $this->resetFunction(); + // 重置$this->declareStrictTypes,并不是每份文件都设置了declare(strict_types=1),防止污染下一份文件 + $this->setDeclareStrictTypes(false); $cppCode = ''; foreach ($stmts as $v) { @@ -2494,6 +2498,9 @@ CODE; if (!($declare->value instanceof Node\Scalar\Int_) or $declare->value->value !== 1) { $this->fatalError($v, 'declare(strict_types=0) is not allowed, only strict_types=1 is supported'); } + + // 当前文件设置了declare(strict_types=1),我们需要保存这个状态,然后生成对应的代码。 + $this->setDeclareStrictTypes(true); } else { $this->fatalError($v, 'declare(' . $key . '=' . $value . ') is not supported'); } @@ -3170,6 +3177,15 @@ CODE; if ($functionDef->argCountRequired > 0) { $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); } + + /** + * 若当前文件声明了 declare(strict_types=1);,则需为当前函数标记 ZEND_ACC_STRICT_TYPES 标志。 + * 该标志用于确保函数在通过 ZendVM 调用其他函数时,能够严格按照声明模式校验参数类型。 + * 需要注意的是,此机制仅作用于 ZendVM 动态调用的函数, + * 若目标函数为编译后的本地代码,则不会触发类型检查——这是 ZendVM 的设计使然,其哲学是信任已编译内部函数的 C 代码实现。 + */ + $cppCode .= $this->genDeclareStrictTypesCode(); + foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { @@ -3524,6 +3540,10 @@ CODE; $this->indentLevel++; $code .= $this->genScopeVarDecl(); $code .= "\n"; + + $paramParser = new ParamParser(); + $code .= $paramParser->create($this->functionDef->argInfoList); + // Runtime union/nullable parameter type checks foreach ($this->functionDef->argInfoList as $i => $argInfo) { if (!empty($argInfo->typeCheck)) { @@ -4882,4 +4902,21 @@ CODE; return $code; } + + private function genDeclareStrictTypesCode(): string + { + /** + * 类型检查仅在二进制模式下启用。由于二进制模式作为独立可执行程序运行,需保持和直接运行的PHP代码一致的行为语义,因此必须保留类型检查以确保安全性。 + * 相反,当编译为扩展(如 ext/curl)时,编译端无需强制内置类型检查,其开启与否应由调用方PHP代码自行决定。 + */ + return $this->declareStrictTypes && $this->isBuildModeBin() + ? "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL + : ''; + } + + private function setDeclareStrictTypes(bool $type): void + { + $this->declareStrictTypes = $type; + } + } -- 2.34.1 From 233209a2531a40cdd4a4b9abe88c676566ccd783 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Fri, 7 Aug 2026 23:32:25 +0800 Subject: [PATCH 2/2] refactor code --- src/Generator/ParamParser.php | 237 ------------------- src/Generator/ParamTypes/ParamParser.php | 57 +++++ src/Generator/ParamTypes/ZendParamMacros.php | 198 ++++++++++++++++ src/Generator/ParamTypes/ZendVariables.php | 140 +++++++++++ src/Translator.php | 36 +-- 5 files changed, 414 insertions(+), 254 deletions(-) delete mode 100644 src/Generator/ParamParser.php create mode 100644 src/Generator/ParamTypes/ParamParser.php create mode 100644 src/Generator/ParamTypes/ZendParamMacros.php create mode 100644 src/Generator/ParamTypes/ZendVariables.php diff --git a/src/Generator/ParamParser.php b/src/Generator/ParamParser.php deleted file mode 100644 index c3d103b4..00000000 --- a/src/Generator/ParamParser.php +++ /dev/null @@ -1,237 +0,0 @@ - $argInfos - * @return string - */ - public function create(array $argInfos): string - { - if (empty($argInfos)) { - return sprintf("do { %s } while(false);", self::NONE_PARAMS_TEMPLATE).PHP_EOL; - } - - [$minNumArgs, $maxNumArgs] = $this->getRequiredAndOptionalParamCounts($argInfos); - $macros[] = $this->genStartParseCode($minNumArgs, $maxNumArgs); - - foreach ($argInfos as $index => $argInfo) { - if ($minNumArgs == 0 || $minNumArgs < $maxNumArgs && $index > $minNumArgs - 1) { - $macros[] = $this->genOptionalDirective(); - } - - $macros[] = $this->genBodyParserCode($argInfo); - } - - $macros[] = $this->genEndParseCode(); - return sprintf( - "do { %s %s } while(false);", - implode(PHP_EOL, $this->paramNames), - implode(PHP_EOL, $macros)); - } - - /** - * @return array - */ - public function getComplexTypes(): array - { - return $this->complexTypes; - } - - /** - * @param array $argInfos - * @return array - */ - private function getRequiredAndOptionalParamCounts(array $argInfos): array - { - if (count($argInfos) == 1 && $argInfos[0]->type === Type::REF) { - return [0, -1]; - } - - $hasRef = false; - foreach ($argInfos as $index => $argInfo) { - if ($argInfo->type === Type::REF) { - $hasRef = true; - break; - } - } - - $requiredParamCount = count(array_filter($argInfos, fn($argInfo) => $argInfo->defaultValue === null)); - return [$requiredParamCount, $hasRef ? -1 : count($argInfos)]; - } - - private function genStartParseCode(int $minNumArgs, int $maxNumArgs): string - { - return sprintf("ZEND_PARSE_PARAMETERS_START(%d, %d)", $minNumArgs, $maxNumArgs); - } - - private function genOptionalDirective(): string - { - return "Z_PARAM_OPTIONAL"; - } - - private function genBodyParserCode(ArgInfo $argInfo): string - { - return match ($argInfo->type) { - Type::BOOL => $this->parseBool($argInfo), - Type::INT => $this->parseInt($argInfo), - Type::FLOAT => $this->parseFloat($argInfo), - Type::ARRAY => $this->parseArray($argInfo), - Type::STR => $this->parseString($argInfo), - Type::ARGS => $this->parseArgs($argInfo), - Type::REF => $this->parseReference($argInfo), - Type::RESOURCE, Type::STREAM => $this->parseResource($argInfo), - Type::OBJECT => $this->parseObject($argInfo), - Type::VAR => $this->parseVar($argInfo), - default => '' - }; - } - - private function genEndParseCode(): string - { - return "ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());"; - } - - private function parseBool(ArgInfo $argInfo): string - { - $name = $argInfo->name; - $isNull = $name.'_is_null'; - - $this->paramNames[] = "zend_bool $name = false; zend_bool $isNull = false;"; - - return $argInfo->nullable - ? "Z_PARAM_BOOL_OR_NULL($name, $isNull)" - : "Z_PARAM_BOOL($name)"; - } - - private function parseInt(ArgInfo $argInfo): string - { - $name = $argInfo->name; - $isNull = $name.'_is_null'; - - $this->paramNames[] = "zend_long $name = 0; zend_bool $isNull = false;"; - - return $argInfo->nullable - ? "Z_PARAM_LONG_OR_NULL($name, $isNull)" - : "Z_PARAM_LONG($name)"; - } - - private function parseFloat(ArgInfo $argInfo): string - { - $name = $argInfo->name; - $isNull = $name.'_is_null'; - - $this->paramNames[] = "double $name = 0; zend_bool $isNull = false;"; - - return $argInfo->nullable - ? "Z_PARAM_DOUBLE_OR_NULL($name, $isNull)" - : "Z_PARAM_DOUBLE($name)"; - } - - private function parseArray(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - $this->paramNames[] = 'zval *'.$name.' = nullptr;'; - return $argInfo->nullable - ? "Z_PARAM_ARRAY_OR_NULL($name)" - : "Z_PARAM_ARRAY($name)"; - } - - private function parseString(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - $this->paramNames[] = "zend_string *".$name.' = nullptr;'; - return $argInfo->nullable - ? "Z_PARAM_STR_OR_NULL($name)" - : "Z_PARAM_STR($name)"; - } - - private function parseArgs(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - $this->paramNames[] = "zval *".$name.'_argv; int '.$name.'_argc = 0;'; - return sprintf("Z_PARAM_ARRAY(*, %s, %s)", $name.'_argv', $name.'_argc'); - } - - private function parseReference(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - $this->paramNames[] = "zval *".$name.' = nullptr;'; - return "Z_PARAM_ZVAL($name)".PHP_EOL; - } - - private function parseResource(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - $this->paramNames[] = "zval *".$name.' = nullptr;'; - return "Z_PARAM_RESOURCE($name)".PHP_EOL; - } - - private function parseObject(ArgInfo $argInfo): string - { - $name = $argInfo->name; - - if ($argInfo->class === 'callble') { - $this->paramNames[] = "zend_fcall_info ".$name.'_fci; zend_fcall_info_cache '.$name.'_fcc;'; - if ($argInfo->nullable) { - return sprintf("Z_PARAM_FUNC_OR_NULL(%s, %s)", $name.'_fci', $name.'_fcc'); - } else { - return sprintf("Z_PARAM_FUNC(%s, %s)", $name.'_fci', $name.'_fcc'); - } - } - - $this->paramNames[] = "zval *".$name.' = nullptr;'; - return $argInfo->nullable - ? "Z_PARAM_OBJECT_OR_NULL($argInfo->name)" - : "Z_PARAM_OBJECT($argInfo->name)"; - } - - private function parseVar(ArgInfo $argInfo): string - { - $name = $argInfo->name; - if ($argInfo->typeNode instanceof NullableType) { - return match ($argInfo->typeNode->type->name) { - "string" => $this->parseString($argInfo), - "array" => $this->parseArgs($argInfo), - "bool" => $this->parseBool($argInfo), - "resource" => $this->parseResource($argInfo), - "float" => $this->parseFloat($argInfo), - default => $this->parseObject($argInfo), - }; - } else { - /** - * Union types and intersection types cannot be resolved through macros. - * Although a few macros are provided for certain union types, their coverage is limited. - * Therefore, we need to adopt a specialized approach to handle them. For now, - * we will defer this implementation until after the current PR is merged, - * so as to keep the PR size manageable and avoid making it too difficult to review. - */ - $this->complexTypes[] = $argInfo; - } - - $this->paramNames[] = "zval *".$name.' = nullptr;'; - return $argInfo->nullable - ? "Z_PARAM_ZVAL_OR_NULL($name)" - : "Z_PARAM_ZVAL($name)"; - } -} diff --git a/src/Generator/ParamTypes/ParamParser.php b/src/Generator/ParamTypes/ParamParser.php new file mode 100644 index 00000000..264bc7da --- /dev/null +++ b/src/Generator/ParamTypes/ParamParser.php @@ -0,0 +1,57 @@ + $argInfos + * @return string + */ + public function createZendParamTypeCheck(array $argInfos): string + { + // Return the default macro if no parameter information is available. + if (empty($argInfos)) { + return sprintf("do { %s } while(false);", self::NONE_PARAMS_TEMPLATE); + } + + [$minNumArgs, $maxNumArgs] = $this->getRequiredAndAllParamCounts($argInfos); + $zendVariables = (new ZendVariables())->createZendVariables($argInfos); + $zendParamMarcos = (new ZendParamMacros())->createZendParamMacros($argInfos, $minNumArgs, $maxNumArgs); + + return sprintf( + "do { %s %s } while(false);", + implode(PHP_EOL, $zendVariables), + implode(PHP_EOL, $zendParamMarcos)); + } + + /** + * Calculate the count of required parameters and total parameters. + * @param array $argInfos + * @return array + */ + private function getRequiredAndAllParamCounts(array $argInfos): array + { + $argCount = count($argInfos); + $lastIndex = $argCount - 1; + + if ($argCount > 0 && $argInfos[$lastIndex]->variadic) { + return [$lastIndex, -1]; + } + + $requiredParamCount = count(array_filter($argInfos, fn($argInfo) => $argInfo->defaultValue === null)); + return [$requiredParamCount, $argCount]; + } +} diff --git a/src/Generator/ParamTypes/ZendParamMacros.php b/src/Generator/ParamTypes/ZendParamMacros.php new file mode 100644 index 00000000..82ce44fe --- /dev/null +++ b/src/Generator/ParamTypes/ZendParamMacros.php @@ -0,0 +1,198 @@ + $argInfos + * @param int $minNumArgs + * @param int $maxNumArgs + * @return array + */ + public function createZendParamMacros(array $argInfos, int $minNumArgs, int $maxNumArgs): array + { + $marcos[] = $this->genStartParseCode($minNumArgs, $maxNumArgs); + foreach ($argInfos as $index => $argInfo) { + if ($maxNumArgs > $minNumArgs && $index == $minNumArgs) { + $marcos[] = $this->genOptionalParseCode(); + } + + $marcos[] = match ($argInfo->type) { + Type::BOOL => $this->parseBool($argInfo), + Type::INT => $this->parseInt($argInfo), + Type::FLOAT => $this->parseFloat($argInfo), + Type::ARRAY => $this->parseArray($argInfo), + Type::STR => $this->parseString($argInfo), + Type::REF => $this->parseReference($argInfo), + Type::RESOURCE, Type::STREAM => $this->parseResource($argInfo), + Type::OBJECT => $this->parseObject($argInfo, $argInfo->class), + default => $this->parseVar($argInfo) + }; + } + + $marcos[] = $this->genEndParseCode(); + return $marcos; + } + + /** + * ZEND_PARAM_PARSE start + * @param int $minNumArgs + * @param int $maxNumArgs + * @return string + */ + private function genStartParseCode(int $minNumArgs, int $maxNumArgs): string + { + return sprintf("ZEND_PARSE_PARAMETERS_START(%d, %d)", $minNumArgs, $maxNumArgs); + } + + /** + * ZEND_PARAM_PARSE end + * @return string + */ + private function genEndParseCode(): string + { + return "ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());"; + } + + /** + * Z_PARAM_OPTIONAL + * @return string + */ + private function genOptionalParseCode(): string + { + return "Z_PARAM_OPTIONAL"; + } + + private function parseBool(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable + ? sprintf("Z_PARAM_BOOL_OR_NULL(%s, %s_is_null)", $name, $name) + : "Z_PARAM_BOOL($name)"; + } + + private function parseInt(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable + ? sprintf("Z_PARAM_LONG_OR_NULL(%s, %s_is_null)", $name, $name) + : "Z_PARAM_LONG($name)"; + } + + private function parseFloat(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable + ? sprintf("Z_PARAM_DOUBLE_OR_NULL(%s, %s_is_null)", $name, $name) + : "Z_PARAM_DOUBLE($name)"; + } + + private function parseArray(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable ? "Z_PARAM_ARRAY_OR_NULL($name)" : "Z_PARAM_ARRAY($name)"; + } + + private function parseString(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable ? "Z_PARAM_STR_OR_NULL($name)" : "Z_PARAM_STR($name)"; + } + + private function parseArgs(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return sprintf("Z_PARAM_VARIADIC(*, %s_argv, %s_argc)", $name, $name); + } + + private function parseReference(ArgInfo $argInfo): string + { + return "Z_PARAM_ZVAL($argInfo->name)"; + } + + private function parseResource(ArgInfo $argInfo): string + { + return "Z_PARAM_RESOURCE($argInfo->name)"; + } + + private function parseObject(ArgInfo $argInfo, string $className): string + { + $name = $argInfo->name; + if ($className) { + return $argInfo->nullable + ? sprintf("Z_PARAM_OBJECT_OF_CLASS_OR_NULL(%s_obj, %s_ce)", $name, $name) + : sprintf("Z_PARAM_OBJECT_OF_CLASS(%s_obj, %s_ce)", $name, $name); + } + + return $argInfo->nullable ? "Z_PARAM_OBJECT_OR_NULL($name)" : "Z_PARAM_OBJECT($name)"; + } + + /** + * The compiler does not currently support the callable type. + * For now, we'll implement the method first and fill in the callable support later. + * @param \TypePhp\Entity\ArgInfo $argInfo + * @return string + */ + private function parseCallable(ArgInfo $argInfo): string + { + $name = $argInfo->name; + return $argInfo->nullable + ? sprintf("Z_PARAM_FUNC_OR_NULL(%s_fci, %s_fcc)", $name, $name) + : sprintf("Z_PARAM_FUNC(%s_fci, %s_fcc)", $name, $name); + } + + /** + * For multiple types, we only handle plain types, mixed, and nullable variables. + * The remaining UnionType and IntersectionType are too complex to be processed via macros. + * @param \TypePhp\Entity\ArgInfo $argInfo + * @return string + */ + private function parseVar(ArgInfo $argInfo): string + { + if ($argInfo->variadic) { + return $this->parseArgs($argInfo); + } + + if ($argInfo->byRef) { + return $this->parseArgs($argInfo); + } + + $name = $argInfo->name; + if (!$argInfo->typeNode instanceof NullableType) { + return $argInfo->nullable ? "Z_PARAM_ZVAL_OR_NULL($name)" : "Z_PARAM_ZVAL($name)"; + } + + /** + * Nullable only contains Identifier, FullyQualified, and Relative. + * The Relative option is not supported by the compiler. + */ + $type = $argInfo->typeNode->type; + if ($type instanceof Identifier) { + return match ($type->name) { + "int" => $this->parseInt($argInfo), + "string" => $this->parseString($argInfo), + "array" => $this->parseArray($argInfo), + "bool" => $this->parseBool($argInfo), + "float" => $this->parseFloat($argInfo), + "object" => $this->parseObject($argInfo, $argInfo->class), + /** + * A default case is not strictly required here, since the PHP-supported types are already listed above. + * However, it is included anyway to keep the code robust in case new types are introduced in the future. + */ + default => "Z_PARAM_ZVAL_OR_NULL($name)" + }; + } + + // FullyQualified + return $this->parseObject($argInfo, $type->toString()); + } +} diff --git a/src/Generator/ParamTypes/ZendVariables.php b/src/Generator/ParamTypes/ZendVariables.php new file mode 100644 index 00000000..ab562e5e --- /dev/null +++ b/src/Generator/ParamTypes/ZendVariables.php @@ -0,0 +1,140 @@ + $argInfos + * @return array + */ + public function createZendVariables(array $argInfos): array + { + $variables = []; + foreach ($argInfos as $argInfo) { + $variables[] = match ($argInfo->type) { + Type::BOOL => $this->genBoolVariable($argInfo->name), + Type::INT => $this->genIntVariable($argInfo->name), + Type::FLOAT => $this->genFloatVariable($argInfo->name), + Type::ARRAY => $this->genArrayVariable($argInfo->name), + Type::STR => $this->genStringVariable($argInfo->name), + Type::RESOURCE, Type::STREAM => $this->genResourceVariable($argInfo->name), + Type::OBJECT => $this->genObjectVariable($argInfo->name, $argInfo->class), + default => $this->genVarVariable($argInfo), + }; + } + + return $variables; + } + + private function genBoolVariable(string $name): string + { + return sprintf("zend_bool %s = 0; zend_bool %s_is_null = false;", $name, $name); + } + + private function genIntVariable(string $name): string + { + return sprintf("zend_long %s = 0; zend_bool %s_is_null = false;", $name, $name); + } + + private function genFloatVariable(string $name): string + { + return sprintf("double %s = 0; zend_bool %s_is_null = false;", $name, $name); + } + + private function genArrayVariable(string $name): string + { + return sprintf("zval *%s = nullptr;", $name); + } + + private function genStringVariable(string $name): string + { + return sprintf("zend_string *%s = nullptr;", $name); + } + + private function genArgsVariable(string $name): string + { + return sprintf("zval *%s_argv = nullptr; int %s_argc = 0;", $name, $name); + } + + private function genReferenceVariable(string $name): string + { + return sprintf("zval *%s = nullptr;", $name); + } + + private function genResourceVariable(string $name): string + { + return sprintf("zval *%s = nullptr;", $name); + } + + private function genObjectVariable(string $name, string $className): string + { + if ($className) { + return sprintf(<<variadic) { + return $this->genArgsVariable($argInfo->name); + } + + if ($argInfo->byRef) { + return $this->genReferenceVariable($argInfo->name); + } + + if (!$argInfo->typeNode instanceof NullableType) { + return sprintf("zval *%s = nullptr;", $argInfo->name); + } + + /** @var NullableType $typeNode */ + $typeNode = $argInfo->typeNode; + $type = $typeNode->type; + $name = $argInfo->name; + + /** + * Nullable only contains Identifier, FullyQualified, and Relative. + * The Relative option is not supported by the compiler. + */ + if ($type instanceof Identifier) { + return match ($type->name) { + 'bool' => $this->genBoolVariable($name), + 'int' => $this->genIntVariable($name), + 'string' => $this->genStringVariable($name), + 'float' => $this->genFloatVariable($name), + 'array' => $this->genArrayVariable($name), + 'object' => $this->genObjectVariable($name, ''), + default => sprintf("zval *%s = nullptr;", $name), + }; + } + + // FullyQualified + return $this->genObjectVariable($name, $type->toString()); + } +} \ No newline at end of file diff --git a/src/Translator.php b/src/Translator.php index 51155591..f1cfd844 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -11,16 +11,21 @@ namespace TypePhp; use Ajaxray\AnsiKit\AnsiTerminal; use Ajaxray\AnsiKit\Components\Progressbar; use MJS\TopSort\Implementations\StringSort; +use PhpParser\Modifiers; +use PhpParser\Node; +use PhpParser\NodeAbstract; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; use TypePhp\Analysis\SsaBuilder; use TypePhp\Backend\CompilerFactory; use TypePhp\Build\FileScanner; -use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\NativeBuilder; +use TypePhp\Build\NativeCommandOptionsTrait; use TypePhp\Build\PrecompiledHeaderManager; +use TypePhp\Build\ResourceCompilationTrait; use TypePhp\Build\SourcePipelineTrait; use TypePhp\Config\ProjectYamlLoader; use TypePhp\Diagnostics\CompileTimeAttributeDiagnostic; -use TypePhp\Build\ResourceCompilationTrait; use TypePhp\Entity\ArgInfo; use TypePhp\Entity\ClassDef; use TypePhp\Entity\ClassLikeDef; @@ -34,22 +39,17 @@ use TypePhp\Exception\Skip; use TypePhp\Exception\SyntaxError; use TypePhp\Generator\DefaultArgumentGenerator; use TypePhp\Generator\LibraryImportStubGenerator; -use TypePhp\Generator\ParamParser; +use TypePhp\Generator\ParamTypes\ParamParser; use TypePhp\Generator\Symbol; use TypePhp\Metadata\Constants; use TypePhp\Platform\PlatformFactory; use TypePhp\Platform\Windows; -use TypePhp\Resolver\Reflection; use TypePhp\Resolver\ClassConstantValueTrait; -use TypePhp\Transform\Visitor; -use TypePhp\Transform\ConstructorLowering; +use TypePhp\Resolver\Reflection; use TypePhp\Transform\ConstantExpressionValidationVisitor; +use TypePhp\Transform\ConstructorLowering; use TypePhp\Transform\RuntimeAttributeFactoryLowering; -use PhpParser\Modifiers; -use PhpParser\Node; -use PhpParser\NodeAbstract; -use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor\NameResolver; +use TypePhp\Transform\Visitor; class Translator extends Preprocessor { @@ -3174,9 +3174,6 @@ CODE; { $cppCode = ''; $callParams = ''; - if ($functionDef->argCountRequired > 0) { - $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); - } /** * 若当前文件声明了 declare(strict_types=1);,则需为当前函数标记 ZEND_ACC_STRICT_TYPES 标志。 @@ -3314,6 +3311,10 @@ CODE; { $name = $classDef->getNamespacedName(); $cppCode = 'ZEND_METHOD(' . $name . ', ' . $methodDef->name . '){' . PHP_EOL; + + $paramParser = new ParamParser(); + $cppCode .= $paramParser->createZendParamTypeCheck($methodDef->functionDef->argInfoList); + $cppCode .= $this->getIndent() . Type::OBJECT . ' this_(&execute_data->This);' . PHP_EOL; $fn = self::PREFIX . $this->getNativeMethodName($classDef, $methodDef); $implicitMethodArgs = []; @@ -3541,9 +3542,6 @@ CODE; $code .= $this->genScopeVarDecl(); $code .= "\n"; - $paramParser = new ParamParser(); - $code .= $paramParser->create($this->functionDef->argInfoList); - // Runtime union/nullable parameter type checks foreach ($this->functionDef->argInfoList as $i => $argInfo) { if (!empty($argInfo->typeCheck)) { @@ -4800,6 +4798,10 @@ CODE; { $name = $this->escapeZendFnName($functionDef->getNamespacedName()); $cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL; + + $paramParser = new ParamParser(); + $cppCode .= $paramParser->createZendParamTypeCheck($functionDef->argInfoList); + $fn = self::PREFIX . $this->getNativeName($functionDef->name, $functionDef->namespace); $cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef, $functionDef->getNamespacedName()); -- 2.34.1