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);
          }