Commit 324db3a77a9 for php.net
commit 324db3a77a978d02a13fe2a6039b9e552bc0560f
Merge: 4ed8fce4582 14d8e842366
Author: Máté Kocsis <kocsismate@woohoolabs.com>
Date: Sat Dec 27 13:00:29 2025 +0100
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
fix: Allow variadic syntax in PHPDoc parameter annotation in `gen_stub.php` (#20342)
diff --cc build/gen_stub.php
index 511407637bd,58b846d2cb5..e1e225dc201
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@@ -2199,31 -2154,6 +2199,31 @@@ OUPUT_EXAMPL
return $methodSynopsis;
}
+ /** @param FuncInfo[] $generatedFuncInfos */
+ public function findEquivalent(array $generatedFuncInfos): ?FuncInfo {
+ foreach ($generatedFuncInfos as $generatedFuncInfo) {
+ if ($generatedFuncInfo->equalsApartFromNameAndRefcount($this)) {
+ return $generatedFuncInfo;
+ }
+ }
+ 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) {
@@@ -4283,238 -4062,6 +4283,238 @@@ class FileInfo
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 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(self::getFileDocComments($stmts));
+ $fileInfo = new FileInfo($fileTags);
-
++
+ $fileInfo->handleStatements($stmts, $prettyPrinter);
+ return $fileInfo;
+ }
+
+ /** @return DocComment[] */
+ private static function getFileDocComments(array $stmts): array {
+ if (empty($stmts)) {
+ return [];
+ }
+
+ return array_filter(
+ $stmts[0]->getComments(),
+ static fn ($comment): bool => $comment instanceof DocComment
+ );
+ }
+
+ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPrinter): void {
+ $conds = [];
+ foreach ($stmts as $stmt) {
+ $cond = self::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(),
+ AttributeInfo::createFromGroups($stmt->attrGroups)
+ );
+ }
+ 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 = self::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");
+ }
+ }
+
+ 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);
+ }
+
+ /** @param array<string, ConstInfo> $allConstInfos */
+ public function generateClassEntryCode(array $allConstInfos): string {
+ $code = "";
+
+ foreach ($this->classInfos as $class) {
+ $code .= "\n" . $class->getRegistration($allConstInfos);
+ }
+
+ return $code;
+ }
}
class DocCommentTag {
@@@ -4562,7 -4109,7 +4562,7 @@@
if ($this->name === "param") {
// Allow for parsing extended types like callable(string):mixed in docblocks
- preg_match('/^\s*(?<type>[\w\|\\\\]+(?<parens>\((?<inparens>(?:(?&parens)|[^(){}[\]<>]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\]|<(?&inparens)>)*+(?::(?&type))?)\s*\$(?<name>\w+).*$/', $value, $matches);
- preg_match('/^\s*(?<type>[\w\|\\\\]+(?<parens>\((?<inparens>(?:(?&parens)|[^(){}[\]]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\])*+(?::(?&type))?)\s*(\.\.\.)?\$(?<name>\w+).*$/', $value, $matches);
++ preg_match('/^\s*(?<type>[\w\|\\\\]+(?<parens>\((?<inparens>(?:(?&parens)|[^(){}[\]<>]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\]|<(?&inparens)>)*+(?::(?&type))?)\s*(\.\.\.)?\$(?<name>\w+).*$/', $value, $matches);
} elseif ($this->name === "prefer-ref") {
preg_match('/^\s*\$(?<name>\w+).*$/', $value, $matches);
}