From 73833e4fd900f4d74d3ffad5933117caa7a121ac Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:08:18 -0700 Subject: [PATCH 01/17] gen_stub: break up closing tag in DOMCdataSection Otherwise GitHub's syntax highlighting treats it as the end of the code and stops highlighting --- build/gen_stub.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 1919d2e702a27..d15313d5c097d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1975,12 +1975,16 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { $prog = $doc->createElement('programlisting'); $prog->setAttribute('role', 'php'); + // So that GitHub syntax highlighting doesn't treat the closing tag + // in the DOMCdataSection as indication that it should stop syntax + // highlighting, break it up + $empty = ''; $code = new DOMCdataSection( << +?$empty> CODE_EXAMPLE ); From d57324d73d9d3e388dbfb19f1bf55e62231a513e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:14:22 -0700 Subject: [PATCH 02/17] gen_stub: add `ExposedDocComment::getInitCode()` Deduplicates the setting up of the `zend_string_init_interned()` call, removes the need for `ExposedDocComment::getLength()` and so that method is removed. --- build/gen_stub.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index d15313d5c097d..dd29d6ff539f8 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2728,9 +2728,8 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst if ($this->exposedDocComment) { $commentCode = "const_{$constName}_comment"; - $escapedComment = $this->exposedDocComment->escape(); - $escapedCommentLength = $this->exposedDocComment->getLength(); - $code .= "\tzend_string *$commentCode = zend_string_init_interned(\"$escapedComment\", $escapedCommentLength, 1);\n"; + $escapedCommentInit = $this->exposedDocComment->getInitCode(); + $code .= "\tzend_string *$commentCode = $escapedCommentInit\n"; } else { $commentCode = "NULL"; } @@ -3056,9 +3055,8 @@ public function getDeclaration(array $allConstInfos): string { if ($this->exposedDocComment) { $commentCode = "property_{$propertyName}_comment"; - $escapedComment = $this->exposedDocComment->escape(); - $escapedCommentLength = $this->exposedDocComment->getLength(); - $code .= "\tzend_string *$commentCode = zend_string_init_interned(\"$escapedComment\", $escapedCommentLength, 1);\n"; + $escapedCommentInit = $this->exposedDocComment->getInitCode(); + $code .= "\tzend_string *$commentCode = $escapedCommentInit\n"; } else { $commentCode = "NULL"; } @@ -3450,7 +3448,7 @@ public function getRegistration(array $allConstInfos): string $code .= "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n"; } - $code .= "\tclass_entry->doc_comment = zend_string_init_interned(\"" . $this->exposedDocComment->escape() . "\", " . $this->exposedDocComment->getLength() . ", 1);\n"; + $code .= "\tclass_entry->doc_comment = " . $this->exposedDocComment->getInitCode() . "\n"; if (!$php84MinimumCompatibility) { $code .= "#endif\n"; @@ -4278,8 +4276,8 @@ public function escape(): string { return str_replace("\n", '\n', addslashes($this->docComment)); } - public function getLength(): int { - return strlen($this->docComment); + public function getInitCode(): string { + return "zend_string_init_interned(\"" . $this->escape() . "\", " . strlen($this->docComment) . ", 1);"; } /** @param array $comments */ From 79d51a673cf28831083c06f7d2d3369ff2f561e3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:33:41 -0700 Subject: [PATCH 03/17] gen_stub: simplify `generateVersionDependentFlagCode()` * Return a string rather than an array, all callers just immediately used `implode()` to join the elements in the array with nothing between them * In the callers, inline some single-use variables with the template for the version-dependent code * Remove the callback to `array_filter` specifying that only items that are not `empty()` be removed - this is the default behavior --- build/gen_stub.php | 51 ++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index dd29d6ff539f8..9d21269af5e3a 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1396,7 +1396,7 @@ public function getFunctionEntry(): string { $flagsByPhpVersions, $this->minimumPhpVersionIdCompatibility ); - $functionEntryCode = rtrim(implode("", $flagsCode)); + $functionEntryCode = rtrim($flagsCode); } } } @@ -1439,25 +1439,21 @@ public function getFunctionEntry(): string { $docComment = $this->exposedDocComment ? '"' . $this->exposedDocComment->escape() . '"' : "NULL"; $framelessFuncInfosName = !empty($this->framelessFunctionInfos) ? $this->getFramelessFunctionInfosName() : "NULL"; - $template = "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s, $framelessFuncInfosName, $docComment)\n"; - $flagsCode = generateVersionDependentFlagCode( - $template, + $code .= generateVersionDependentFlagCode( + "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s, $framelessFuncInfosName, $docComment)\n", $php84AndAboveFlags, PHP_84_VERSION_ID ); - $code .= implode("", $flagsCode); if (!$php84MinimumCompatibility) { $code .= "#else\n"; $flags = array_slice($flagsByPhpVersions, 0, 4, true); - $template = "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s)\n"; - $flagsCode = generateVersionDependentFlagCode( - $template, + $code .= generateVersionDependentFlagCode( + "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s)\n", $flags, $this->minimumPhpVersionIdCompatibility ); - $code .= implode("", $flagsCode); $code .= "#endif\n"; } @@ -2750,12 +2746,11 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst } $template .= "zend_declare_typed_class_constant(class_entry, $nameCode, &const_{$constName}_value, %s, $commentCode, $typeCode);\n"; - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); } if ($this->type && !$php83MinimumCompatibility) { @@ -2769,12 +2764,11 @@ private function getClassConstDeclaration(EvaluatedValue $value, array $allConst $template = "\t"; } $template .= "zend_declare_class_constant_ex(class_entry, $nameCode, &const_{$constName}_value, %s, $commentCode);\n"; - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); } if ($this->type && !$php83MinimumCompatibility) { @@ -3074,12 +3068,11 @@ public function getDeclaration(array $allConstInfos): string { $template .= "zend_declare_property_ex(class_entry, $nameCode, &$zvalName, %s, $commentCode);\n"; } - $flagsCode = generateVersionDependentFlagCode( + $code .= generateVersionDependentFlagCode( $template, $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility ); - $code .= implode("", $flagsCode); $code .= $stringRelease; @@ -3396,8 +3389,7 @@ public function getRegistration(array $allConstInfos): string $code .= "{\n"; - $flagCodes = generateVersionDependentFlagCode("%s", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); - $flags = implode("", $flagCodes); + $flags = generateVersionDependentFlagCode("%s", $this->getFlagsByPhpVersion(), $this->phpVersionIdMinimumCompatibility); $classMethods = ($this->funcInfos === []) ? 'NULL' : "class_{$escapedName}_methods"; if ($this->type === "enum") { @@ -5403,9 +5395,9 @@ function generateOptimizerInfo(array $funcMap): string { /** * @param array $flagsByPhpVersions - * @return string[] + * @return string */ -function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPhpVersions, ?int $phpVersionIdMinimumCompatibility): array +function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPhpVersions, ?int $phpVersionIdMinimumCompatibility): string { $phpVersions = ALL_PHP_VERSION_IDS; sort($phpVersions); @@ -5414,10 +5406,10 @@ function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPh // No version compatibility is needed if ($phpVersionIdMinimumCompatibility === null) { if (empty($flagsByPhpVersions[$currentPhpVersion])) { - return []; + return ''; } - return [sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion]))]; + return sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion])); } // Remove flags which depend on a PHP version below the minimally supported one @@ -5429,15 +5421,11 @@ function generateVersionDependentFlagCode(string $codeTemplate, array $flagsByPh $flagsByPhpVersions = array_slice($flagsByPhpVersions, $index, null, true); // Remove empty version-specific flags - $flagsByPhpVersions = array_filter( - $flagsByPhpVersions, - static function (array $value): bool { - return !empty($value); - }); + $flagsByPhpVersions = array_filter($flagsByPhpVersions); // There are no version-specific flags if (empty($flagsByPhpVersions)) { - return []; + return ''; } // Remove version-specific flags which don't differ from the previous one @@ -5457,16 +5445,14 @@ static function (array $value): bool { reset($flagsByPhpVersions); $firstVersion = key($flagsByPhpVersions); if ($firstVersion === $phpVersionIdMinimumCompatibility) { - return [sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions)))]; + return sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions))); } } // Add the necessary conditions around the code using the version-specific flags - $result = []; + $code = ''; $i = 0; foreach (array_reverse($flagsByPhpVersions, true) as $version => $versionFlags) { - $code = ""; - $if = $i === 0 ? "#if" : "#elif"; $endif = $i === $flagCount - 1 ? "#endif\n" : ""; @@ -5475,11 +5461,10 @@ static function (array $value): bool { $code .= sprintf($codeTemplate, implode("|", $versionFlags)); $code .= $endif; - $result[] = $code; $i++; } - return $result; + return $code; } /** From e6938a3115030a4a4335b13d084003506f70e11b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:39:00 -0700 Subject: [PATCH 04/17] gen_stub: simplify `ArgInfo::getDefaultValueAsMethodSynopsisString()` There is no need to add special handling for the default value of `null`, since it is not loosely-equals to any of the strings 'UNKNOWN', 'false', 'true', or 'null' it will just be returned directly anyway. --- build/gen_stub.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9d21269af5e3a..4643a424f9356 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -848,10 +848,6 @@ public function getDefaultValueAsArginfoString(): string { } public function getDefaultValueAsMethodSynopsisString(): ?string { - if ($this->defaultValue === null) { - return null; - } - switch ($this->defaultValue) { case 'UNKNOWN': return null; From 03a9153982b0417847a87eb8c673209184dadf76 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 21:55:03 -0700 Subject: [PATCH 05/17] gen_stub: add `ArgInfo::toZendInfo()` Move the logic out of `funcInfoToCode()` and update it. In the process, make `ArgInfo::getDefaultValueAsArginfoString()` private. --- build/gen_stub.php | 92 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 4643a424f9356..81fd0ccece072 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -839,7 +839,7 @@ public function hasProperDefaultValue(): bool { return $this->defaultValue !== null && $this->defaultValue !== "UNKNOWN"; } - public function getDefaultValueAsArginfoString(): string { + private function getDefaultValueAsArginfoString(): string { if ($this->hasProperDefaultValue()) { return '"' . addslashes($this->defaultValue) . '"'; } @@ -859,6 +859,50 @@ public function getDefaultValueAsMethodSynopsisString(): ?string { return $this->defaultValue; } + + public function toZendInfo(): string { + $argKind = $this->isVariadic ? "ARG_VARIADIC" : "ARG"; + $argDefaultKind = $this->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : ""; + $argType = $this->type; + if ($argType !== null) { + if (null !== $simpleArgType = $argType->tryToSimpleType()) { + if ($simpleArgType->isBuiltin) { + return sprintf( + "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $simpleArgType->toTypeCode(), $argType->isNullable(), + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + return sprintf( + "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $simpleArgType->toEscapedName(), $argType->isNullable(), + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + $arginfoType = $argType->toArginfoType(); + if ($arginfoType->hasClassType()) { + return sprintf( + "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s%s)\n", + $argKind, $this->sendBy, $this->name, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(), + !$this->isVariadic ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } + return sprintf( + "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", + $argKind, $this->sendBy, $this->name, + $arginfoType->toTypeMask(), + $this->getDefaultValueAsArginfoString() + ); + } + return sprintf( + "\tZEND_%s_INFO%s(%s, %s%s)\n", + $argKind, $argDefaultKind, $this->sendBy, $this->name, + $this->hasProperDefaultValue() ? ", " . $this->getDefaultValueAsArginfoString() : "" + ); + } } interface VariableLikeName { @@ -5029,51 +5073,7 @@ function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { } foreach ($funcInfo->args as $argInfo) { - $argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG"; - $argDefaultKind = $argInfo->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : ""; - $argType = $argInfo->type; - if ($argType !== null) { - if (null !== $simpleArgType = $argType->tryToSimpleType()) { - if ($simpleArgType->isBuiltin) { - $code .= sprintf( - "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $simpleArgType->toTypeCode(), $argType->isNullable(), - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } else { - $code .= sprintf( - "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $simpleArgType->toEscapedName(), $argType->isNullable(), - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } - } else { - $arginfoType = $argType->toArginfoType(); - if ($arginfoType->hasClassType()) { - $code .= sprintf( - "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s%s)\n", - $argKind, $argInfo->sendBy, $argInfo->name, - $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(), - !$argInfo->isVariadic ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } else { - $code .= sprintf( - "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", - $argKind, $argInfo->sendBy, $argInfo->name, - $arginfoType->toTypeMask(), - $argInfo->getDefaultValueAsArginfoString() - ); - } - } - } else { - $code .= sprintf( - "\tZEND_%s_INFO%s(%s, %s%s)\n", - $argKind, $argDefaultKind, $argInfo->sendBy, $argInfo->name, - $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : "" - ); - } + $code .= $argInfo->toZendInfo(); } $code .= "ZEND_END_ARG_INFO()"; From d133adfb1ac5f6bde3f7af071d03f9e900971825 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 22:44:45 -0700 Subject: [PATCH 06/17] gen_stub: add `ReturnInfo::beginArgInfo()` The vast majority of the decisions about the use of `ZEND_BEGIN_ARG_INFO_EX` or one of its variations are based on the return information of the function - is the type builtin, is the return information tentative, does it include an object mask, etc. Accordingly, move the logic into the `ReturnInfo` class. The logic is actually moved into two methods, `ReturnInfo::beginArgInfo()`, which needs to handle the case of tentative returns being used when PHP < 8.1 is supported, and `::beginArgInfoCompatible()`, which can assume that PHP 8.1+ is supported and thus make use of early returns and guard clauses. Further improvements to the logic will be made in a subsequent commit. In the process, make `ReturnInfo::$byRef` private. --- build/gen_stub.php | 129 ++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 81fd0ccece072..c8a2e7a3fb48d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1146,7 +1146,7 @@ class ReturnInfo { self::REFCOUNT_N, ]; - public /* readonly */ bool $byRef; + private /* readonly */ bool $byRef; // NOT readonly - gets removed when discarding info for older PHP versions public ?Type $type; public /* readonly */ ?Type $phpDocType; @@ -1193,6 +1193,69 @@ private function setRefcount(?string $refcount): void $this->refcount = $refcount; } + + public function beginArgInfo(string $funcInfoName, int $minArgs, bool $php81MinimumCompatibility): string { + $code = $this->beginArgInfoCompatible($funcInfoName, $minArgs); + if ($this->type !== null && $this->tentativeReturnType && !$php81MinimumCompatibility) { + $realCode = "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; + $realCode .= $code; + $realCode .= sprintf( + "#else\nZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n#endif\n", + $funcInfoName, $this->byRef, $minArgs + ); + return $realCode; + } + return $code; + } + + /** + * Assumes PHP 8.1 compatibility, if that is not the case the caller is + * responsible for making the use of a tentative return type conditional + * based on the PHP version. Separate to allow using early returns + */ + private function beginArgInfoCompatible(string $funcInfoName, int $minArgs): string { + if ($this->type !== null) { + if (null !== $simpleReturnType = $this->type->tryToSimpleType()) { + if ($simpleReturnType->isBuiltin) { + return sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX", + $funcInfoName, $this->byRef, + $minArgs, + $simpleReturnType->toTypeCode(), $this->type->isNullable() + ); + } + return sprintf( + "%s(%s, %d, %d, %s, %d)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX", + $funcInfoName, $this->byRef, + $minArgs, + $simpleReturnType->toEscapedName(), $this->type->isNullable() + ); + } + $arginfoType = $this->type->toArginfoType(); + if ($arginfoType->hasClassType()) { + return sprintf( + "%s(%s, %d, %d, %s, %s)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX", + $funcInfoName, $this->byRef, + $minArgs, + $arginfoType->toClassTypeString(), $arginfoType->toTypeMask() + ); + } + return sprintf( + "%s(%s, %d, %d, %s)\n", + $this->tentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX", + $funcInfoName, $this->byRef, + $minArgs, + $arginfoType->toTypeMask() + ); + } + return sprintf( + "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n", + $funcInfoName, $this->byRef, $minArgs + ); + } } class FuncInfo { @@ -5012,65 +5075,11 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { } function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { - $code = ''; - $returnType = $funcInfo->return->type; - $isTentativeReturnType = $funcInfo->return->tentativeReturnType; - $php81MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID; - - if ($returnType !== null) { - if ($isTentativeReturnType && !$php81MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; - } - if (null !== $simpleReturnType = $returnType->tryToSimpleType()) { - if ($simpleReturnType->isBuiltin) { - $code .= sprintf( - "%s(%s, %d, %d, %s, %d)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $simpleReturnType->toTypeCode(), $returnType->isNullable() - ); - } else { - $code .= sprintf( - "%s(%s, %d, %d, %s, %d)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $simpleReturnType->toEscapedName(), $returnType->isNullable() - ); - } - } else { - $arginfoType = $returnType->toArginfoType(); - if ($arginfoType->hasClassType()) { - $code .= sprintf( - "%s(%s, %d, %d, %s, %s)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $arginfoType->toClassTypeString(), $arginfoType->toTypeMask() - ); - } else { - $code .= sprintf( - "%s(%s, %d, %d, %s)\n", - $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, - $funcInfo->numRequiredArgs, - $arginfoType->toTypeMask() - ); - } - } - if ($isTentativeReturnType && !$php81MinimumCompatibility) { - $code .= sprintf( - "#else\nZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n#endif\n", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs - ); - } - } else { - $code .= sprintf( - "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n", - $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs - ); - } + $code = $funcInfo->return->beginArgInfo( + $funcInfo->getArgInfoName(), + $funcInfo->numRequiredArgs, + $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID + ); foreach ($funcInfo->args as $argInfo) { $code .= $argInfo->toZendInfo(); From 791163d530baacac94951e8833b10903c0d337de Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 22:51:16 -0700 Subject: [PATCH 07/17] gen_stub: move `funcInfoToCode()` into `FuncInfo` Reduce the number of global functions by moving it to instance method `FuncInfo::toArgInfoCode()`. In the process, make `FuncInfo::$numRequiredArgs` private. --- build/gen_stub.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index c8a2e7a3fb48d..2c08c564c3523 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1270,7 +1270,7 @@ class FuncInfo { /** @var ArgInfo[] */ public /* readonly */ array $args; public /* readonly */ ReturnInfo $return; - public /* readonly */ int $numRequiredArgs; + private /* readonly */ int $numRequiredArgs; public /* readonly */ ?string $cond; public bool $isUndocumentable; private ?int $minimumPhpVersionIdCompatibility; @@ -2228,6 +2228,21 @@ public function findEquivalent(array $generatedFuncInfos): ?FuncInfo { return null; } + public function toArgInfoCode(?int $minPHPCompatability): string { + $code = $this->return->beginArgInfo( + $this->getArgInfoName(), + $this->numRequiredArgs, + $minPHPCompatability === null || $minPHPCompatability >= PHP_81_VERSION_ID + ); + + foreach ($this->args as $argInfo) { + $code .= $argInfo->toZendInfo(); + } + + $code .= "ZEND_END_ARG_INFO()"; + return $code . "\n"; + } + public function __clone() { foreach ($this->args as $key => $argInfo) { @@ -5074,21 +5089,6 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { return $fileInfo; } -function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string { - $code = $funcInfo->return->beginArgInfo( - $funcInfo->getArgInfoName(), - $funcInfo->numRequiredArgs, - $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID - ); - - foreach ($funcInfo->args as $argInfo) { - $code .= $argInfo->toZendInfo(); - } - - $code .= "ZEND_END_ARG_INFO()"; - return $code . "\n"; -} - /** * @template T * @param iterable $infos @@ -5168,7 +5168,7 @@ static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName() ); } else { - $code = funcInfoToCode($fileInfo, $funcInfo); + $code = $funcInfo->toArgInfoCode($fileInfo->getMinimumPhpVersionIdCompatibility()); } $generatedFuncInfos[] = $funcInfo; From 0fc5dda32ce85dc725345ea86b6a6fa880e618e6 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 16 Mar 2025 23:35:20 -0700 Subject: [PATCH 08/17] gen_stub: inline some single-use variables --- build/gen_stub.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 2c08c564c3523..adcdfd5b2607d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -710,9 +710,7 @@ public function getTypeForDoc(DOMDocument $doc): DOMElement { } } else { $type = $this->types[0]; - $name = $type->name; - - $typeElement = $doc->createElement('type', $name); + $typeElement = $doc->createElement('type', $type->name); } return $typeElement; @@ -1937,9 +1935,8 @@ private function getReturnValueSection(DOMDocument $doc): DOMElement { $returnDescriptionPara->appendChild(new DOMText("Description.")); } else if (count($returnType->types) === 1) { $type = $returnType->types[0]; - $name = $type->name; - switch ($name) { + switch ($type->name) { case 'void': $descriptionNode = $doc->createEntityReference('return.void'); break; From 1f1ebb8d6ec02135e5a8b4e4bf3bd65c4ae2626c Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 17 Mar 2025 00:27:58 -0700 Subject: [PATCH 09/17] gen_stub: drop unused parameters The following parameters were either unused before this commit or became unused as part of updating callers to stop passing unused parameters to other functions updated in this commit: * `FuncInfo::getMethodSynopsisDocument()` - `$funcMap`, `$aliasMap` * `FuncInfo::getMethodSynopsisElement()` - `$funcMap`, `$aliasMap` * `ConstInfo::getGlobalConstDeclaration()` - `$allConstInfos` * `generateMethodSynopses()` - `$aliasMap` * `replaceMethodSynopses()` - `$aliasMap` --- build/gen_stub.php | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index adcdfd5b2607d..dfbd2d6a92f63 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1684,11 +1684,9 @@ private function generateRefSect1(DOMDocument $doc, string $role): DOMElement { } /** - * @param array $funcMap - * @param array $aliasMap * @throws Exception */ - public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string { + public function getMethodSynopsisDocument(): ?string { $REFSEC1_SEPERATOR = "\n\n "; $doc = new DOMDocument("1.0", "utf-8"); @@ -1733,7 +1731,7 @@ public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?str /* Creation of */ $descriptionRefSec = $this->generateRefSect1($doc, 'description'); - $methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + $methodSynopsis = $this->getMethodSynopsisElement($doc); if (!$methodSynopsis) { return null; } @@ -2117,11 +2115,9 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { } /** - * @param array $funcMap - * @param array $aliasMap * @throws Exception */ - public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDocument $doc): ?DOMElement { + public function getMethodSynopsisElement(DOMDocument $doc): ?DOMElement { if ($this->hasParamWithUnknownDefaultValue()) { return null; } @@ -2773,7 +2769,7 @@ public function getDeclaration(array $allConstInfos): string if ($this->name->isClassConst()) { $code .= $this->getClassConstDeclaration($value, $allConstInfos); } else { - $code .= $this->getGlobalConstDeclaration($value, $allConstInfos); + $code .= $this->getGlobalConstDeclaration($value); } $code .= $this->getValueAssertion($value); @@ -2784,8 +2780,7 @@ public function getDeclaration(array $allConstInfos): string return $code; } - /** @param array $allConstInfos */ - private function getGlobalConstDeclaration(EvaluatedValue $value, array $allConstInfos): string + private function getGlobalConstDeclaration(EvaluatedValue $value): string { $constName = str_replace('\\', '\\\\', $this->name->__toString()); $constValue = $value->value; @@ -5783,14 +5778,13 @@ function getReplacedSynopsisXml(string $xml): string /** * @param array $funcMap - * @param array $aliasMap * @return array */ -function generateMethodSynopses(array $funcMap, array $aliasMap): array { +function generateMethodSynopses(array $funcMap): array { $result = []; foreach ($funcMap as $funcInfo) { - $methodSynopsis = $funcInfo->getMethodSynopsisDocument($funcMap, $aliasMap); + $methodSynopsis = $funcInfo->getMethodSynopsisDocument(); if ($methodSynopsis !== null) { $result[$funcInfo->name->getMethodSynopsisFilename() . ".xml"] = $methodSynopsis; } @@ -5801,7 +5795,6 @@ function generateMethodSynopses(array $funcMap, array $aliasMap): array { /** * @param array $funcMap - * @param array $aliasMap * @param array $methodSynopsisWarnings * @param array $undocumentedFuncMap * @return array @@ -5809,7 +5802,6 @@ function generateMethodSynopses(array $funcMap, array $aliasMap): array { function replaceMethodSynopses( string $targetDirectory, array $funcMap, - array $aliasMap, bool $isVerifyManual, array &$methodSynopsisWarnings, array &$undocumentedFuncMap @@ -5908,7 +5900,7 @@ function replaceMethodSynopses( $funcInfo = $funcMap[$funcName]; $documentedFuncMap[$funcInfo->name->__toString()] = $funcInfo->name->__toString(); - $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($funcMap, $aliasMap, $doc); + $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($doc); if ($newMethodSynopsis === null) { continue; } @@ -6326,7 +6318,7 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc } if ($generateMethodSynopses) { - $methodSynopses = generateMethodSynopses($funcMap, $aliasMap); + $methodSynopses = generateMethodSynopses($funcMap); if (!file_exists($manualTarget)) { mkdir($manualTarget); } @@ -6345,7 +6337,7 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc } if ($replaceMethodSynopses || $verifyManual) { - $methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $aliasMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap); + $methodSynopses = replaceMethodSynopses($manualTarget, $funcMap, $verifyManual, $methodSynopsisWarnings, $undocumentedFuncMap); if ($replaceMethodSynopses) { foreach ($methodSynopses as $filename => $content) { From cb62f2ad825d520554dae7f88e103dcdc8ac77b2 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:10:28 -0700 Subject: [PATCH 10/17] gen_stub: documentation updates * Use `@param` instead of `@var` for parameters * Fix type of `$attributeGroups` in `AttributeInfo::createFromGroups()` * Remove extra documentation of `$allConstInfo` for `ClassInfo::getClassSynopsisDocument()`, it is already documented under the correct name `$allConstInfos` * Remove unneeded `@throws` --- build/gen_stub.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index dfbd2d6a92f63..9d4a9138d0502 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1683,9 +1683,6 @@ private function generateRefSect1(DOMDocument $doc, string $role): DOMElement { return $refSec; } - /** - * @throws Exception - */ public function getMethodSynopsisDocument(): ?string { $REFSEC1_SEPERATOR = "\n\n "; @@ -2114,9 +2111,6 @@ private function getExampleSection(DOMDocument $doc, string $id): DOMElement { return $refSec; } - /** - * @throws Exception - */ public function getMethodSynopsisElement(DOMDocument $doc): ?DOMElement { if ($this->hasParamWithUnknownDefaultValue()) { return null; @@ -2438,7 +2432,7 @@ abstract class VariableLike protected /* readonly */ ?ExposedDocComment $exposedDocComment; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( int $flags, @@ -2621,7 +2615,7 @@ class ConstInfo extends VariableLike private /* readonly */ bool $isFileCacheAllowed; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( AbstractConstName $name, @@ -3075,7 +3069,7 @@ class PropertyInfo extends VariableLike ]; /** - * @var AttributeInfo[] $attributes + * @param AttributeInfo[] $attributes */ public function __construct( PropertyName $name, @@ -3373,7 +3367,7 @@ public function generateCode(string $invocation, string $nameSuffix, array $allC } /** - * @param array> $attributeGroups + * @param AttributeGroup[] $attributeGroups * @return AttributeInfo[] */ public static function createFromGroups(array $attributeGroups): array { @@ -3733,7 +3727,6 @@ public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibil /** * @param array $classMap * @param array $allConstInfos - * @param iterable $allConstInfo */ public function getClassSynopsisDocument(array $classMap, array $allConstInfos): ?string { $doc = new DOMDocument(); From 9a5a76632f09e3bff6dea15eb8542cf7a64249b1 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:56:29 -0700 Subject: [PATCH 11/17] gen_stub: move `handleStatements()` into `FileInfo` Reduce the number of global functions by moving it to instance method `FileInfo::handleStatements()`. --- build/gen_stub.php | 290 ++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 145 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9d4a9138d0502..b31af1d58c908 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4298,6 +4298,150 @@ public function getMinimumPhpVersionIdCompatibility(): ?int { public function shouldGenerateLegacyArginfo(): bool { return $this->minimumPhpVersionIdCompatibility !== null && $this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID; } + + public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { + $conds = []; + foreach ($stmts as $stmt) { + $cond = handlePreprocessorConditions($conds, $stmt); + + if ($stmt instanceof Stmt\Nop) { + continue; + } + + if ($stmt instanceof Stmt\Namespace_) { + $this->handleStatements($stmt->stmts, $prettyPrinter); + continue; + } + + if ($stmt instanceof Stmt\Const_) { + foreach ($stmt->consts as $const) { + $this->constInfos[] = parseConstLike( + $prettyPrinter, + new ConstName($const->namespacedName, $const->name->toString()), + $const, + 0, + null, + $stmt->getComments(), + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility(), + [] + ); + } + continue; + } + + if ($stmt instanceof Stmt\Function_) { + $this->funcInfos[] = parseFunctionLike( + $prettyPrinter, + new FunctionName($stmt->namespacedName), + 0, + 0, + $stmt, + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility() + ); + continue; + } + + if ($stmt instanceof Stmt\ClassLike) { + $className = $stmt->namespacedName; + $constInfos = []; + $propertyInfos = []; + $methodInfos = []; + $enumCaseInfos = []; + foreach ($stmt->stmts as $classStmt) { + $cond = handlePreprocessorConditions($conds, $classStmt); + if ($classStmt instanceof Stmt\Nop) { + continue; + } + + $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; + $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; + + if ($classStmt instanceof Stmt\ClassConst) { + foreach ($classStmt->consts as $const) { + $constInfos[] = parseConstLike( + $prettyPrinter, + new ClassConstName($className, $const->name->toString()), + $const, + $classStmt->flags, + $classStmt->type, + $classStmt->getComments(), + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility(), + AttributeInfo::createFromGroups($classStmt->attrGroups) + ); + } + } else if ($classStmt instanceof Stmt\Property) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + foreach ($classStmt->props as $property) { + $propertyInfos[] = parseProperty( + $className, + $classFlags, + $classStmt->flags, + $property, + $classStmt->type, + $classStmt->getComments(), + $prettyPrinter, + $this->getMinimumPhpVersionIdCompatibility(), + AttributeInfo::createFromGroups($classStmt->attrGroups) + ); + } + } else if ($classStmt instanceof Stmt\ClassMethod) { + if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { + throw new Exception("Visibility modifier is required"); + } + $methodInfos[] = parseFunctionLike( + $prettyPrinter, + new MethodName($className, $classStmt->name->toString()), + $classFlags, + $classStmt->flags | $abstractFlag, + $classStmt, + $cond, + $this->isUndocumentable, + $this->getMinimumPhpVersionIdCompatibility() + ); + } else if ($classStmt instanceof Stmt\EnumCase) { + $enumCaseInfos[] = new EnumCaseInfo( + $classStmt->name->toString(), $classStmt->expr); + } else { + throw new Exception("Not implemented {$classStmt->getType()}"); + } + } + + $this->classInfos[] = parseClass( + $className, + $stmt, + $constInfos, + $propertyInfos, + $methodInfos, + $enumCaseInfos, + $cond, + $this->getMinimumPhpVersionIdCompatibility(), + $this->isUndocumentable + ); + continue; + } + + if ($stmt instanceof Stmt\Expression) { + $expr = $stmt->expr; + if ($expr instanceof Expr\Include_) { + $this->dependencies[] = (string)EvaluatedValue::createFromExpression($expr->expr, null, null, [])->value; + continue; + } + } + + throw new Exception("Unexpected node {$stmt->getType()}"); + } + if (!empty($conds)) { + throw new Exception("Unterminated preprocessor conditions"); + } + } } class DocCommentTag { @@ -4910,150 +5054,6 @@ function getFileDocComments(array $stmts): array { ); } -function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstract $prettyPrinter) { - $conds = []; - foreach ($stmts as $stmt) { - $cond = handlePreprocessorConditions($conds, $stmt); - - if ($stmt instanceof Stmt\Nop) { - continue; - } - - if ($stmt instanceof Stmt\Namespace_) { - handleStatements($fileInfo, $stmt->stmts, $prettyPrinter); - continue; - } - - if ($stmt instanceof Stmt\Const_) { - foreach ($stmt->consts as $const) { - $fileInfo->constInfos[] = parseConstLike( - $prettyPrinter, - new ConstName($const->namespacedName, $const->name->toString()), - $const, - 0, - null, - $stmt->getComments(), - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - [] - ); - } - continue; - } - - if ($stmt instanceof Stmt\Function_) { - $fileInfo->funcInfos[] = parseFunctionLike( - $prettyPrinter, - new FunctionName($stmt->namespacedName), - 0, - 0, - $stmt, - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility() - ); - continue; - } - - if ($stmt instanceof Stmt\ClassLike) { - $className = $stmt->namespacedName; - $constInfos = []; - $propertyInfos = []; - $methodInfos = []; - $enumCaseInfos = []; - foreach ($stmt->stmts as $classStmt) { - $cond = handlePreprocessorConditions($conds, $classStmt); - if ($classStmt instanceof Stmt\Nop) { - continue; - } - - $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0; - $abstractFlag = $stmt instanceof Stmt\Interface_ ? Modifiers::ABSTRACT : 0; - - if ($classStmt instanceof Stmt\ClassConst) { - foreach ($classStmt->consts as $const) { - $constInfos[] = parseConstLike( - $prettyPrinter, - new ClassConstName($className, $const->name->toString()), - $const, - $classStmt->flags, - $classStmt->type, - $classStmt->getComments(), - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - AttributeInfo::createFromGroups($classStmt->attrGroups) - ); - } - } else if ($classStmt instanceof Stmt\Property) { - if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { - throw new Exception("Visibility modifier is required"); - } - foreach ($classStmt->props as $property) { - $propertyInfos[] = parseProperty( - $className, - $classFlags, - $classStmt->flags, - $property, - $classStmt->type, - $classStmt->getComments(), - $prettyPrinter, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - AttributeInfo::createFromGroups($classStmt->attrGroups) - ); - } - } else if ($classStmt instanceof Stmt\ClassMethod) { - if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) { - throw new Exception("Visibility modifier is required"); - } - $methodInfos[] = parseFunctionLike( - $prettyPrinter, - new MethodName($className, $classStmt->name->toString()), - $classFlags, - $classStmt->flags | $abstractFlag, - $classStmt, - $cond, - $fileInfo->isUndocumentable, - $fileInfo->getMinimumPhpVersionIdCompatibility() - ); - } else if ($classStmt instanceof Stmt\EnumCase) { - $enumCaseInfos[] = new EnumCaseInfo( - $classStmt->name->toString(), $classStmt->expr); - } else { - throw new Exception("Not implemented {$classStmt->getType()}"); - } - } - - $fileInfo->classInfos[] = parseClass( - $className, - $stmt, - $constInfos, - $propertyInfos, - $methodInfos, - $enumCaseInfos, - $cond, - $fileInfo->getMinimumPhpVersionIdCompatibility(), - $fileInfo->isUndocumentable - ); - continue; - } - - if ($stmt instanceof Stmt\Expression) { - $expr = $stmt->expr; - if ($expr instanceof Expr\Include_) { - $fileInfo->dependencies[] = (string)EvaluatedValue::createFromExpression($expr->expr, null, null, [])->value; - continue; - } - } - - throw new Exception("Unexpected node {$stmt->getType()}"); - } - if (!empty($conds)) { - throw new Exception("Unterminated preprocessor conditions"); - } -} - function parseStubFile(string $code): FileInfo { $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); $nodeTraverser = new PhpParser\NodeTraverser; @@ -5070,7 +5070,7 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { $fileTags = parseDocComments(getFileDocComments($stmts)); $fileInfo = new FileInfo($fileTags); - handleStatements($fileInfo, $stmts, $prettyPrinter); + $fileInfo->handleStatements($stmts, $prettyPrinter); return $fileInfo; } From a63d365bbba37dc030fd9cd52ebc6c11337ca210 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 12:58:27 -0700 Subject: [PATCH 12/17] gen_stub: move `handlePreprocessorConditions()` into `FileInfo()` Reduce the number of global functions by moving it to static method `FileInfo::handlePreprocessorConditions()`. Since it is only used by `FileInfo::handleStatements()`, also make it private. --- build/gen_stub.php | 60 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index b31af1d58c908..d95c32a549c59 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4302,7 +4302,7 @@ public function shouldGenerateLegacyArginfo(): bool { public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { - $cond = handlePreprocessorConditions($conds, $stmt); + $cond = self::handlePreprocessorConditions($conds, $stmt); if ($stmt instanceof Stmt\Nop) { continue; @@ -4352,7 +4352,7 @@ public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrin $methodInfos = []; $enumCaseInfos = []; foreach ($stmt->stmts as $classStmt) { - $cond = handlePreprocessorConditions($conds, $classStmt); + $cond = self::handlePreprocessorConditions($conds, $classStmt); if ($classStmt instanceof Stmt\Nop) { continue; } @@ -4442,6 +4442,34 @@ public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrin throw new Exception("Unterminated preprocessor conditions"); } } + + private static function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string { + foreach ($stmt->getComments() as $comment) { + $text = trim($comment->getText()); + if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { + $conds[] = $matches[1]; + } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { + $conds[] = "defined($matches[1])"; + } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { + $conds[] = "!defined($matches[1])"; + } else if (preg_match('/^#\s*else$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered else without corresponding #if"); + } + $cond = array_pop($conds); + $conds[] = "!($cond)"; + } else if (preg_match('/^#\s*endif$/', $text)) { + if (empty($conds)) { + throw new Exception("Encountered #endif without corresponding #if"); + } + array_pop($conds); + } else if ($text[0] === '#') { + throw new Exception("Unrecognized preprocessor directive \"$text\""); + } + } + + return empty($conds) ? null : implode(' && ', $conds); + } } class DocCommentTag { @@ -5014,34 +5042,6 @@ function parseClass( ); } -function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string { - foreach ($stmt->getComments() as $comment) { - $text = trim($comment->getText()); - if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { - $conds[] = $matches[1]; - } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { - $conds[] = "defined($matches[1])"; - } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { - $conds[] = "!defined($matches[1])"; - } else if (preg_match('/^#\s*else$/', $text)) { - if (empty($conds)) { - throw new Exception("Encountered else without corresponding #if"); - } - $cond = array_pop($conds); - $conds[] = "!($cond)"; - } else if (preg_match('/^#\s*endif$/', $text)) { - if (empty($conds)) { - throw new Exception("Encountered #endif without corresponding #if"); - } - array_pop($conds); - } else if ($text[0] === '#') { - throw new Exception("Unrecognized preprocessor directive \"$text\""); - } - } - - return empty($conds) ? null : implode(' && ', $conds); -} - /** @return DocComment[] */ function getFileDocComments(array $stmts): array { if (empty($stmts)) { From 2211d52291918bc66896b160d5576f7c9c6daa9a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 13:06:46 -0700 Subject: [PATCH 13/17] gen_stub: move `parseDocComments()` into `DocCommentTag` Reduce the number of global functions by moving it to static method `DocCommentTag::parseDocComments()`. --- build/gen_stub.php | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index d95c32a549c59..64c22f1873aac 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4528,6 +4528,25 @@ public function getVariableName(): string { return $matches["name"]; } + + /** @return DocCommentTag[] */ + public static function parseDocComments(array $comments): array { + $tags = []; + foreach ($comments as $comment) { + if (!($comment instanceof DocComment)) { + continue; + } + $commentText = substr($comment->getText(), 2, -2); + foreach (explode("\n", $commentText) as $commentLine) { + $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/'; + if (preg_match($regex, trim($commentLine), $matches)) { + $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null); + } + } + } + + return $tags; + } } // Instances of ExposedDocComment are immutable and do not need to be cloned @@ -4571,25 +4590,6 @@ public static function extractExposedComment(array $comments): ?ExposedDocCommen } } -/** @return DocCommentTag[] */ -function parseDocComments(array $comments): array { - $tags = []; - foreach ($comments as $comment) { - if (!($comment instanceof DocComment)) { - continue; - } - $commentText = substr($comment->getText(), 2, -2); - foreach (explode("\n", $commentText) as $commentLine) { - $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/'; - if (preg_match($regex, trim($commentLine), $matches)) { - $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null); - } - } - } - - return $tags; -} - // Instances of FramelessFunctionInfo are immutable and do not need to be cloned // when held by an object that is cloned class FramelessFunctionInfo { @@ -4628,7 +4628,7 @@ function parseFunctionLike( $framelessFunctionInfos = []; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { switch ($tag->name) { @@ -4817,7 +4817,7 @@ function parseConstLike( $link = null; $isFileCacheAllowed = true; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'var') { $phpDocType = $tag->getType(); @@ -4891,7 +4891,7 @@ function parseProperty( $link = null; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'var') { $phpDocType = $tag->getType(); @@ -4962,7 +4962,7 @@ function parseClass( $allowsDynamicProperties = false; if ($comments) { - $tags = parseDocComments($comments); + $tags = DocCommentTag::parseDocComments($comments); foreach ($tags as $tag) { if ($tag->name === 'alias') { $alias = $tag->getValue(); @@ -5067,7 +5067,7 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string { $stmts = $parser->parse($code); $nodeTraverser->traverse($stmts); - $fileTags = parseDocComments(getFileDocComments($stmts)); + $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); $fileInfo = new FileInfo($fileTags); $fileInfo->handleStatements($stmts, $prettyPrinter); From deadeb8ecbc8f4feb72290bab330b50ae5e28bd3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 14:14:45 -0700 Subject: [PATCH 14/17] gen_stub: deduplicate and simplify DocCommentTag processing For a lot of the structures, the parsing of doc comment tags is based on if a specific tag is present, or the value that it has if it is. Add a new helper method, `DocCommentTag::makeTagMap()`, that turns an array of tag instances into a map from tag name to value (the last value, if there are multiple uses of the same tag name). Then, for the simple cases where just a tag's presence is all that is checked, or just the (last) value is used, check the map instead of using a loop through all of the tags present. --- build/gen_stub.php | 125 +++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 72 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 64c22f1873aac..b9a8941d7c99d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -4547,6 +4547,19 @@ public static function parseDocComments(array $comments): array { return $tags; } + + /** + * @param DocCommentTag[] $tags + * @return array Mapping tag names to the value (or null), + * if a tag is present multiple times the last value is used + */ + public static function makeTagMap(array $tags): array { + $map = []; + foreach ($tags as $tag) { + $map[$tag->name] = $tag->value; + } + return $map; + } } // Instances of ExposedDocComment are immutable and do not need to be cloned @@ -4629,6 +4642,13 @@ function parseFunctionLike( if ($comments) { $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDeprecated = array_key_exists('deprecated', $tagMap); + $verify = !array_key_exists('no-verify', $tagMap); + $tentativeReturnType = array_key_exists('tentative-return-type', $tagMap); + $supportsCompileTimeEval = array_key_exists('compile-time-eval', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); foreach ($tags as $tag) { switch ($tag->name) { @@ -4643,18 +4663,6 @@ function parseFunctionLike( } break; - case 'deprecated': - $isDeprecated = true; - break; - - case 'no-verify': - $verify = false; - break; - - case 'tentative-return-type': - $tentativeReturnType = true; - break; - case 'return': $docReturnType = $tag->getType(); break; @@ -4667,10 +4675,6 @@ function parseFunctionLike( $refcount = $tag->getValue(); break; - case 'compile-time-eval': - $supportsCompileTimeEval = true; - break; - case 'prefer-ref': $varName = $tag->getVariableName(); if (!isset($paramMeta[$varName])) { @@ -4679,10 +4683,6 @@ function parseFunctionLike( $paramMeta[$varName][$tag->name] = true; break; - case 'undocumentable': - $isUndocumentable = true; - break; - case 'frameless-function': $framelessFunctionInfos[] = new FramelessFunctionInfo($tag->getValue()); break; @@ -4812,26 +4812,19 @@ function parseConstLike( array $attributes ): ConstInfo { $phpDocType = null; - $deprecated = false; - $cValue = null; - $link = null; - $isFileCacheAllowed = true; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'var') { - $phpDocType = $tag->getType(); - } elseif ($tag->name === 'deprecated') { - $deprecated = true; - } elseif ($tag->name === 'cvalue') { - $cValue = $tag->value; - } elseif ($tag->name === 'undocumentable') { - $isUndocumentable = true; - } elseif ($tag->name === 'link') { - $link = $tag->value; - } elseif ($tag->name === 'no-file-cache') { - $isFileCacheAllowed = false; - } + + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $deprecated = array_key_exists('deprecated', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); + $isFileCacheAllowed = !array_key_exists('no-file-cache', $tagMap); + $cValue = $tagMap['cvalue'] ?? null; + $link = $tagMap['link'] ?? null; + + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); } } @@ -4886,22 +4879,17 @@ function parseProperty( array $attributes ): PropertyInfo { $phpDocType = null; - $isDocReadonly = false; - $isVirtual = false; - $link = null; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'var') { - $phpDocType = $tag->getType(); - } elseif ($tag->name === 'readonly') { - $isDocReadonly = true; - } elseif ($tag->name === 'link') { - $link = $tag->value; - } elseif ($tag->name === 'virtual') { - $isVirtual = true; - } + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDocReadonly = array_key_exists('readonly', $tagMap); + $link = $tagMap['link'] ?? null; + $isVirtual = array_key_exists('virtual', $tagMap); + + foreach ($tags as $tag) { + if ($tag->name === 'var') { + $phpDocType = $tag->getType(); } } @@ -4956,25 +4944,18 @@ function parseClass( ): ClassInfo { $comments = $class->getComments(); $alias = null; - $isDeprecated = false; - $isStrictProperties = false; - $isNotSerializable = false; $allowsDynamicProperties = false; - if ($comments) { - $tags = DocCommentTag::parseDocComments($comments); - foreach ($tags as $tag) { - if ($tag->name === 'alias') { - $alias = $tag->getValue(); - } else if ($tag->name === 'deprecated') { - $isDeprecated = true; - } else if ($tag->name === 'strict-properties') { - $isStrictProperties = true; - } else if ($tag->name === 'not-serializable') { - $isNotSerializable = true; - } else if ($tag->name === 'undocumentable') { - $isUndocumentable = true; - } + $tags = DocCommentTag::parseDocComments($comments); + $tagMap = DocCommentTag::makeTagMap($tags); + + $isDeprecated = array_key_exists('deprecated', $tagMap); + $isStrictProperties = array_key_exists('strict-properties', $tagMap); + $isNotSerializable = array_key_exists('not-serializable', $tagMap); + $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); + foreach ($tags as $tag) { + if ($tag->name === 'alias') { + $alias = $tag->getValue(); } } From c9432f757afb61545b68982386ed030b3221e289 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 8 May 2025 12:11:40 -0700 Subject: [PATCH 15/17] gen_stub: add and use `FileInfo::getLegacyVersion()` Separate out the creation of a legacy version of a FileInfo object, which has information for old versions of PHP discarded, from its subsequent use in `processStubFile()`. In the process, make `FileInfo::$legacyArginfoGeneration` private, and inline the single use of `FileInfo::getAllClassInfos()`, removing that method. --- build/gen_stub.php | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index b9a8941d7c99d..9754e6bf673f2 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -133,19 +133,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = } if ($fileInfo->shouldGenerateLegacyArginfo()) { - $legacyFileInfo = clone $fileInfo; - $legacyFileInfo->legacyArginfoGeneration = true; - $phpVersionIdMinimumCompatibility = $legacyFileInfo->getMinimumPhpVersionIdCompatibility(); - - foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) { - $funcInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } - foreach ($legacyFileInfo->getAllClassInfos() as $classInfo) { - $classInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } - foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) { - $constInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); - } + $legacyFileInfo = $fileInfo->getLegacyVersion(); $arginfoCode = generateArgInfoCode( basename($stubFilenameWithoutExtension), @@ -4199,7 +4187,7 @@ class FileInfo { public string $declarationPrefix = ""; public bool $generateClassEntries = false; public bool $isUndocumentable = false; - public bool $legacyArginfoGeneration = false; + private bool $legacyArginfoGeneration = false; private ?int $minimumPhpVersionIdCompatibility = null; /** @param array $fileTags */ @@ -4259,15 +4247,6 @@ public function getAllConstInfos(): array { return $result; } - /** - * @return iterable - */ - public function getAllClassInfos(): iterable { - foreach ($this->classInfos as $classInfo) { - yield $classInfo; - } - } - public function __clone() { foreach ($this->constInfos as $key => $constInfo) { @@ -4299,6 +4278,23 @@ public function shouldGenerateLegacyArginfo(): bool { return $this->minimumPhpVersionIdCompatibility !== null && $this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID; } + public function getLegacyVersion(): FileInfo { + $legacyFileInfo = clone $this; + $legacyFileInfo->legacyArginfoGeneration = true; + $phpVersionIdMinimumCompatibility = $legacyFileInfo->getMinimumPhpVersionIdCompatibility(); + + foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) { + $funcInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + foreach ($legacyFileInfo->classInfos as $classInfo) { + $classInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) { + $constInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility); + } + return $legacyFileInfo; + } + public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { From 57f98b97b49dc8d49f391a8a1b721851ce4070c3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 18 Mar 2025 14:35:40 -0700 Subject: [PATCH 16/17] gen_stub: further reduce the number of public properties The following properties are made private: * `ArgInfo::$phpDocType` * `ClassInfo::$flags`, `::$attributes`, `::$extends`, `::$implements` * `FileInfo::$isUndocumentable` The following are made protected: * `VariableLike::$flags` --- build/gen_stub.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 9754e6bf673f2..6ec974c05a22d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -775,7 +775,7 @@ class ArgInfo { public /* readonly */ string $sendBy; public /* readonly */ bool $isVariadic; public ?Type $type; - public /* readonly */ ?Type $phpDocType; + private /* readonly */ ?Type $phpDocType; public ?string $defaultValue; /** @var AttributeInfo[] */ public array $attributes; @@ -2410,7 +2410,7 @@ public function getCExpr(): ?string abstract class VariableLike { - public int $flags; + protected int $flags; public ?Type $type; public /* readonly */ ?Type $phpDocType; private /* readonly */ ?string $link; @@ -3373,20 +3373,20 @@ public static function createFromGroups(array $attributeGroups): array { class ClassInfo { public /* readonly */ Name $name; - public int $flags; + private int $flags; public string $type; public /* readonly */ ?string $alias; private /* readonly */ ?SimpleType $enumBackingType; private /* readonly */ bool $isDeprecated; private bool $isStrictProperties; /** @var AttributeInfo[] */ - public array $attributes; + private array $attributes; private ?ExposedDocComment $exposedDocComment; private bool $isNotSerializable; /** @var Name[] */ - public /* readonly */ array $extends; + private /* readonly */ array $extends; /** @var Name[] */ - public /* readonly */ array $implements; + private /* readonly */ array $implements; /** @var ConstInfo[] */ public /* readonly */ array $constInfos; /** @var PropertyInfo[] */ @@ -4186,7 +4186,7 @@ class FileInfo { public bool $generateFunctionEntries = false; public string $declarationPrefix = ""; public bool $generateClassEntries = false; - public bool $isUndocumentable = false; + private bool $isUndocumentable = false; private bool $legacyArginfoGeneration = false; private ?int $minimumPhpVersionIdCompatibility = null; From 87d7cefb30341b1c3de01b34c097deefa027704a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 8 May 2025 12:12:12 -0700 Subject: [PATCH 17/17] gen_stub: move `parseStubFile()` into `FileInfo` Reduce the number of global functions by moving it to static method `FileInfo::parseStubFile()`. Additionally, make `FileInfo::handleStatements()` private now that the only caller is part of the class. --- build/gen_stub.php | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 6ec974c05a22d..1d2fa79a0666c 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -95,7 +95,7 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = if (!$fileInfo = $context->parsedFiles[$stubFile] ?? null) { initPhpParser(); $stubContent = $stubCode ?? file_get_contents($stubFile); - $fileInfo = parseStubFile($stubContent); + $fileInfo = FileInfo::parseStubFile($stubContent); $context->parsedFiles[$stubFile] = $fileInfo; foreach ($fileInfo->dependencies as $dependency) { @@ -4295,7 +4295,27 @@ public function getLegacyVersion(): FileInfo { return $legacyFileInfo; } - public function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { + public static function parseStubFile(string $code): FileInfo { + $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); + $nodeTraverser = new PhpParser\NodeTraverser; + $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); + $prettyPrinter = new class extends Standard { + protected function pName_FullyQualified(Name\FullyQualified $node): string { + return implode('\\', $node->getParts()); + } + }; + + $stmts = $parser->parse($code); + $nodeTraverser->traverse($stmts); + + $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); + $fileInfo = new FileInfo($fileTags); + + $fileInfo->handleStatements($stmts, $prettyPrinter); + return $fileInfo; + } + + private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void { $conds = []; foreach ($stmts as $stmt) { $cond = self::handlePreprocessorConditions($conds, $stmt); @@ -5031,26 +5051,6 @@ function getFileDocComments(array $stmts): array { ); } -function parseStubFile(string $code): FileInfo { - $parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative()); - $nodeTraverser = new PhpParser\NodeTraverser; - $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); - $prettyPrinter = new class extends Standard { - protected function pName_FullyQualified(Name\FullyQualified $node): string { - return implode('\\', $node->getParts()); - } - }; - - $stmts = $parser->parse($code); - $nodeTraverser->traverse($stmts); - - $fileTags = DocCommentTag::parseDocComments(getFileDocComments($stmts)); - $fileInfo = new FileInfo($fileTags); - - $fileInfo->handleStatements($stmts, $prettyPrinter); - return $fileInfo; -} - /** * @template T * @param iterable $infos