Skip to content

Improve BC support of arginfo files generated by gen_stub.php #13705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 116 additions & 28 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,19 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
echo "Saved $arginfoFile\n";
}

if ($fileInfo->generateLegacyArginfoForPhpVersionId !== null && $fileInfo->generateLegacyArginfoForPhpVersionId < PHP_80_VERSION_ID) {
if ($fileInfo->shouldGenerateLegacyArginfo()) {
$legacyFileInfo = clone $fileInfo;
$legacyFileInfo->legacyArginfoGeneration = true;
$phpVersionIdMinimumCompatibility = $legacyFileInfo->getMinimumPhpVersionIdCompatibility();

foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) {
$funcInfo->discardInfoForOldPhpVersions();
$funcInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility);
}
foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) {
$constInfo->discardInfoForOldPhpVersions();
foreach ($legacyFileInfo->getAllClassInfos() as $classInfo) {
$classInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility);
}
foreach ($legacyFileInfo->getAllPropertyInfos() as $propertyInfo) {
$propertyInfo->discardInfoForOldPhpVersions();
foreach ($legacyFileInfo->getAllConstInfos() as $constInfo) {
$constInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility);
}

$arginfoCode = generateArgInfoCode(
Expand Down Expand Up @@ -1532,14 +1534,18 @@ public function getOptimizerInfo(): ?string {
return "\tF" . $this->return->refcount . '("' . addslashes($this->name->__toString()) . '", ' . $type->toOptimizerTypeMask() . "),\n";
}

public function discardInfoForOldPhpVersions(): void {
public function discardInfoForOldPhpVersions(?int $minimumPhpVersionIdCompatibility): void {
$this->attributes = [];
$this->return->type = null;
$this->framelessFunctionInfos = [];
$this->exposedDocComment = null;
$this->supportsCompileTimeEval = false;
foreach ($this->args as $arg) {
$arg->type = null;
$arg->defaultValue = null;
$arg->attributes = [];
}
$this->minimumPhpVersionIdCompatibility = $minimumPhpVersionIdCompatibility;
}

/** @return array<int, string[]> */
Expand Down Expand Up @@ -2127,6 +2133,15 @@ public function __clone()
$this->args[$key] = clone $argInfo;
}
$this->return = clone $this->return;
foreach ($this->attributes as $key => $attribute) {
$this->attributes[$key] = clone $attribute;
}
foreach ($this->framelessFunctionInfos as $key => $framelessFunctionInfo) {
$this->framelessFunctionInfos[$key] = clone $framelessFunctionInfo;
}
if ($this->exposedDocComment) {
$this->exposedDocComment = clone $this->exposedDocComment;
}
}
}

Expand Down Expand Up @@ -2355,7 +2370,7 @@ abstract protected function getFieldSynopsisName(): string;
/** @param array<string, ConstInfo> $allConstInfos */
abstract protected function getFieldSynopsisValueString(array $allConstInfos): ?string;

abstract public function discardInfoForOldPhpVersions(): void;
abstract public function discardInfoForOldPhpVersions(?int $minimumPhpVersionIdCompatibility): void;

protected function addTypeToFieldSynopsis(DOMDocument $doc, DOMElement $fieldsynopsisElement): void
{
Expand Down Expand Up @@ -2628,17 +2643,19 @@ public function getPredefinedConstantEntry(DOMDocument $doc, int $indentationLev
return $entryElement;
}

public function discardInfoForOldPhpVersions(): void {
public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibility): void {
$this->type = null;
$this->flags &= ~Modifiers::FINAL;
$this->isDeprecated = false;
$this->attributes = [];
$this->phpVersionIdMinimumCompatibility = $phpVersionIdMinimumCompatibility;
}

/** @param array<string, ConstInfo> $allConstInfos */
public function getDeclaration(array $allConstInfos): string
{
$simpleType = ($this->phpDocType ?? $this->type)->tryToSimpleType();
$type = $this->phpDocType ?? $this->type;
$simpleType = $type ? $type->tryToSimpleType() : null;
if ($simpleType && $simpleType->name === "mixed") {
$simpleType = null;
}
Expand Down Expand Up @@ -2909,10 +2926,11 @@ protected function getFieldSynopsisValueString(array $allConstInfos): ?string
return $this->defaultValueString;
}

public function discardInfoForOldPhpVersions(): void {
public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibility): void {
$this->type = null;
$this->flags &= ~Modifiers::READONLY;
$this->attributes = [];
$this->phpVersionIdMinimumCompatibility = $phpVersionIdMinimumCompatibility;
}

/** @param array<string, ConstInfo> $allConstInfos */
Expand Down Expand Up @@ -2940,7 +2958,6 @@ public function getDeclaration(array $allConstInfos): string {

$code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n";
$nameCode = "property_{$propertyName}_name";
$typeCode = $this->getTypeCode($propertyName, $code);

if ($this->exposedDocComment) {
$commentCode = "property_{$propertyName}_comment";
Expand All @@ -2956,7 +2973,14 @@ public function getDeclaration(array $allConstInfos): string {
} else {
$template = "\t";
}
$template .= "zend_declare_typed_property(class_entry, $nameCode, &$zvalName, %s, $commentCode, $typeCode);\n";

if ($this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_80_VERSION_ID) {
$typeCode = $this->getTypeCode($propertyName, $code);
$template .= "zend_declare_typed_property(class_entry, $nameCode, &$zvalName, %s, $commentCode, $typeCode);\n";
} else {
$template .= "zend_declare_property_ex(class_entry, $nameCode, &$zvalName, %s, $commentCode);\n";
}

$flagsCode = generateVersionDependentFlagCode(
$template,
$this->getFlagsByPhpVersion(),
Expand Down Expand Up @@ -3007,6 +3031,12 @@ public function __clone()
if ($this->type) {
$this->type = clone $this->type;
}
foreach ($this->attributes as $key => $attribute) {
$this->attributes[$key] = clone $attribute;
}
if ($this->exposedDocComment) {
$this->exposedDocComment = clone $this->exposedDocComment;
}
}
}

Expand Down Expand Up @@ -3383,6 +3413,19 @@ private function getFlagsByPhpVersion(): array
];
}

public function discardInfoForOldPhpVersions(?int $phpVersionIdMinimumCompatibility): void {
$this->attributes = [];
$this->flags &= ~Modifiers::READONLY;
$this->exposedDocComment = null;
$this->isStrictProperties = false;
$this->isNotSerializable = false;

foreach ($this->propertyInfos as $propertyInfo) {
$propertyInfo->discardInfoForOldPhpVersions($phpVersionIdMinimumCompatibility);
}
$this->phpVersionIdMinimumCompatibility = $phpVersionIdMinimumCompatibility;
}

/**
* @param array<string, ClassInfo> $classMap
* @param array<string, ConstInfo> $allConstInfos
Expand Down Expand Up @@ -3801,13 +3844,25 @@ private function createIncludeElement(DOMDocument $doc, string $query): DOMEleme

public function __clone()
{
foreach ($this->constInfos as $key => $constInfo) {
$this->constInfos[$key] = clone $constInfo;
}

foreach ($this->propertyInfos as $key => $propertyInfo) {
$this->propertyInfos[$key] = clone $propertyInfo;
}

foreach ($this->funcInfos as $key => $funcInfo) {
$this->funcInfos[$key] = clone $funcInfo;
}

foreach ($this->attributes as $key => $attribute) {
$this->attributes[$key] = clone $attribute;
}

if ($this->exposedDocComment) {
$this->exposedDocComment = clone $this->exposedDocComment;
}
}

/**
Expand Down Expand Up @@ -3848,9 +3903,10 @@ class FileInfo {
public array $classInfos = [];
public bool $generateFunctionEntries = false;
public string $declarationPrefix = "";
public ?int $generateLegacyArginfoForPhpVersionId = null;
public bool $generateClassEntries = false;
public bool $isUndocumentable = false;
public bool $legacyArginfoGeneration = false;
private ?int $minimumPhpVersionIdCompatibility = null;

/**
* @return iterable<FuncInfo>
Expand Down Expand Up @@ -3880,16 +3936,20 @@ public function getAllConstInfos(): array {
}

/**
* @return iterable<PropertyInfo>
* @return iterable<ClassInfo>
*/
public function getAllPropertyInfos(): iterable {
public function getAllClassInfos(): iterable {
foreach ($this->classInfos as $classInfo) {
yield from $classInfo->propertyInfos;
yield $classInfo;
}
}

public function __clone()
{
foreach ($this->constInfos as $key => $constInfo) {
$this->constInfos[$key] = clone $constInfo;
}

foreach ($this->funcInfos as $key => $funcInfo) {
$this->funcInfos[$key] = clone $funcInfo;
}
Expand All @@ -3898,6 +3958,26 @@ public function __clone()
$this->classInfos[$key] = clone $classInfo;
}
}

public function setMinimumPhpVersionIdCompatibility(?int $minimumPhpVersionIdCompatibility) {
$this->minimumPhpVersionIdCompatibility = $minimumPhpVersionIdCompatibility;
}

public function getMinimumPhpVersionIdCompatibility(): ?int {
// Non-legacy arginfo files are always PHP 8.0+ compatible
if (!$this->legacyArginfoGeneration &&
$this->minimumPhpVersionIdCompatibility !== null &&
$this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID
) {
return PHP_80_VERSION_ID;
}

return $this->minimumPhpVersionIdCompatibility;
}

public function shouldGenerateLegacyArginfo(): bool {
return $this->minimumPhpVersionIdCompatibility !== null && $this->minimumPhpVersionIdCompatibility < PHP_80_VERSION_ID;
}
}

class DocCommentTag {
Expand Down Expand Up @@ -4541,7 +4621,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$stmt->getComments(),
$cond,
$fileInfo->isUndocumentable,
$fileInfo->generateLegacyArginfoForPhpVersionId,
$fileInfo->getMinimumPhpVersionIdCompatibility(),
[]
);
}
Expand All @@ -4557,7 +4637,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$stmt,
$cond,
$fileInfo->isUndocumentable,
$fileInfo->generateLegacyArginfoForPhpVersionId
$fileInfo->getMinimumPhpVersionIdCompatibility()
);
continue;
}
Expand Down Expand Up @@ -4588,7 +4668,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$classStmt->getComments(),
$cond,
$fileInfo->isUndocumentable,
$fileInfo->generateLegacyArginfoForPhpVersionId,
$fileInfo->getMinimumPhpVersionIdCompatibility(),
createAttributes($classStmt->attrGroups)
);
}
Expand All @@ -4604,7 +4684,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$classStmt->type,
$classStmt->getComments(),
$prettyPrinter,
$fileInfo->generateLegacyArginfoForPhpVersionId,
$fileInfo->getMinimumPhpVersionIdCompatibility(),
createAttributes($classStmt->attrGroups)
);
}
Expand All @@ -4620,7 +4700,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
$classStmt,
$cond,
$fileInfo->isUndocumentable,
$fileInfo->generateLegacyArginfoForPhpVersionId
$fileInfo->getMinimumPhpVersionIdCompatibility()
);
} else if ($classStmt instanceof Stmt\EnumCase) {
$enumCaseInfos[] = new EnumCaseInfo(
Expand All @@ -4631,7 +4711,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
}

$fileInfo->classInfos[] = parseClass(
$className, $stmt, $constInfos, $propertyInfos, $methodInfos, $enumCaseInfos, $cond, $fileInfo->generateLegacyArginfoForPhpVersionId, $fileInfo->isUndocumentable
$className, $stmt, $constInfos, $propertyInfos, $methodInfos, $enumCaseInfos, $cond, $fileInfo->getMinimumPhpVersionIdCompatibility(), $fileInfo->isUndocumentable
);
continue;
}
Expand Down Expand Up @@ -4678,11 +4758,11 @@ protected function pName_FullyQualified(Name\FullyQualified $node): string {
throw new Exception(
"Legacy PHP version must be one of: \"" . PHP_70_VERSION_ID . "\" (PHP 7.0), \"" . PHP_80_VERSION_ID . "\" (PHP 8.0), " .
"\"" . PHP_81_VERSION_ID . "\" (PHP 8.1), \"" . PHP_82_VERSION_ID . "\" (PHP 8.2), \"" . PHP_83_VERSION_ID . "\" (PHP 8.3), " .
"\"" . $tag->value . "\" provided"
"\"" . PHP_84_VERSION_ID . "\" (PHP 8.4), \"" . $tag->value . "\" provided"
);
}

$fileInfo->generateLegacyArginfoForPhpVersionId = $tag->value ? (int) $tag->value : PHP_70_VERSION_ID;
$fileInfo->setMinimumPhpVersionIdCompatibility($tag->value ? (int) $tag->value : PHP_70_VERSION_ID);
} else if ($tag->name === 'generate-class-entries') {
$fileInfo->generateClassEntries = true;
$fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
Expand All @@ -4705,7 +4785,7 @@ function funcInfoToCode(FileInfo $fileInfo, FuncInfo $funcInfo): string {
$code = '';
$returnType = $funcInfo->return->type;
$isTentativeReturnType = $funcInfo->return->tentativeReturnType;
$php81MinimumCompatibility = $fileInfo->generateLegacyArginfoForPhpVersionId === null || $fileInfo->generateLegacyArginfoForPhpVersionId >= PHP_81_VERSION_ID;
$php81MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_81_VERSION_ID;

if ($returnType !== null) {
if ($isTentativeReturnType && !$php81MinimumCompatibility) {
Expand Down Expand Up @@ -4864,6 +4944,14 @@ function generateArgInfoCode(
$code = "/* This is a generated file, edit the .stub.php file instead.\n"
. " * Stub hash: $stubHash */\n";

$minimumPhpVersionIdCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility();
if ($minimumPhpVersionIdCompatibility !== null) {
$code .= "\nZEND_STATIC_ASSERT(PHP_VERSION_ID >= $minimumPhpVersionIdCompatibility, ";
$code .= "\"{$stubFilenameWithoutExtension}_arginfo.h only supports ";
$code .= "PHP version ID $minimumPhpVersionIdCompatibility or newer, \"\n";
$code .= "\t\"but it is included on an older PHP version\");\n";
}

$generatedFuncInfos = [];

$argInfoCode = generateCodeWithConditions(
Expand Down Expand Up @@ -4924,10 +5012,10 @@ static function (FuncInfo $funcInfo) use ($fileInfo, &$generatedFunctionDeclarat
}
}

$php80MinimumCompatibility = $fileInfo->generateLegacyArginfoForPhpVersionId === null || $fileInfo->generateLegacyArginfoForPhpVersionId >= PHP_80_VERSION_ID;
$php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID;

if ($fileInfo->generateClassEntries) {
if ($attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->generateLegacyArginfoForPhpVersionId, null)) {
if ($attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null)) {
if (!$php80MinimumCompatibility) {
$attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n";
}
Expand Down
3 changes: 3 additions & 0 deletions ext/zend_test/test_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.