diff --git a/build/gen_stub.php b/build/gen_stub.php index 4715f7a401310..e5645f233f371 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -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( @@ -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 */ @@ -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; + } } } @@ -2355,7 +2370,7 @@ abstract protected function getFieldSynopsisName(): string; /** @param array $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 { @@ -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 $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; } @@ -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 $allConstInfos */ @@ -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"; @@ -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(), @@ -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; + } } } @@ -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 $classMap * @param array $allConstInfos @@ -3801,6 +3844,10 @@ 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; } @@ -3808,6 +3855,14 @@ public function __clone() 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; + } } /** @@ -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 @@ -3880,16 +3936,20 @@ public function getAllConstInfos(): array { } /** - * @return iterable + * @return iterable */ - 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; } @@ -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 { @@ -4541,7 +4621,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $stmt->getComments(), $cond, $fileInfo->isUndocumentable, - $fileInfo->generateLegacyArginfoForPhpVersionId, + $fileInfo->getMinimumPhpVersionIdCompatibility(), [] ); } @@ -4557,7 +4637,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $stmt, $cond, $fileInfo->isUndocumentable, - $fileInfo->generateLegacyArginfoForPhpVersionId + $fileInfo->getMinimumPhpVersionIdCompatibility() ); continue; } @@ -4588,7 +4668,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $classStmt->getComments(), $cond, $fileInfo->isUndocumentable, - $fileInfo->generateLegacyArginfoForPhpVersionId, + $fileInfo->getMinimumPhpVersionIdCompatibility(), createAttributes($classStmt->attrGroups) ); } @@ -4604,7 +4684,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $classStmt->type, $classStmt->getComments(), $prettyPrinter, - $fileInfo->generateLegacyArginfoForPhpVersionId, + $fileInfo->getMinimumPhpVersionIdCompatibility(), createAttributes($classStmt->attrGroups) ); } @@ -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( @@ -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; } @@ -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 . " " : ""; @@ -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) { @@ -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( @@ -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"; } diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 736dcfd8b2549..1f846e830525b 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,6 +1,9 @@ /* This is a generated file, edit the .stub.php file instead. * Stub hash: dd3f852ea9f8e3a356ed226380edf5cc336f8a4e */ +ZEND_STATIC_ASSERT(PHP_VERSION_ID >= 80000, "test_arginfo.h only supports PHP version ID 80000 or newer, " + "but it is included on an older PHP version"); + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO()