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] 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());