From af0b6f958214d461eb5712316e1a67d3cc18efb7 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 14 Aug 2026 17:53:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=208.2=E3=80=818.3=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- completions/tpc.bash | 2 +- docs/COMPILER_CLI.md | 4 +- docs/INCOMPATIBLE_PHP_FEATURES.md | 2 +- docs/INTERFACE_PROPERTY_HOOKS.md | 23 ++-- docs/PROPERTY_HOOKS.md | 18 +-- phpunit/BashCompletionTest.php | 4 + phpunit/README.md | 2 +- phpunit/TEST_REPORT.md | 6 +- phpunit/src/CompilerBaseApiTest.php | 26 +++- .../src/ConstantExpressionValidatorTest.php | 9 +- src/Build/SourcePipelineTrait.php | 39 ++++++ src/Cli/CompletionMetadata.php | 2 +- src/CompilerBase.php | 8 +- src/Metadata/Constants.php | 2 +- src/Parser/NullsafeAccessTrait.php | 4 +- src/Preprocessor.php | 6 - .../ConstantExpressionValidationVisitor.php | 15 +-- src/Translator.php | 11 +- src/gen_stub.php | 124 +----------------- 20 files changed, 110 insertions(+), 199 deletions(-) diff --git a/README.md b/README.md index 8d183bf9..6fa44b43 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # 依赖 -- 编译器需要 PHP 8.4 以上版本;生成的扩展仍可面向 PHP 8.2~8.5 +- 编译器和生成程序均需要 PHP 8.4 以上版本,支持 PHP 8.4~8.5 - 需要 GCC-9 以上版本,支持 C++17 标准 - 需要 CMake-3.24 以上版本 - 需要高精度数学库:`GMP`、`MPFR`、`libmpdec` diff --git a/completions/tpc.bash b/completions/tpc.bash index f6ad74ca..9e4eaf81 100644 --- a/completions/tpc.bash +++ b/completions/tpc.bash @@ -68,7 +68,7 @@ _typephp_tpc() return ;; --php-version) - COMPREPLY=( $(compgen -W '8.2 8.3 8.4 8.5' -- "$current") ) + COMPREPLY=( $(compgen -W '8.4 8.5' -- "$current") ) return ;; --cxx-std) diff --git a/docs/COMPILER_CLI.md b/docs/COMPILER_CLI.md index 7e5c2cf4..f6d6b464 100644 --- a/docs/COMPILER_CLI.md +++ b/docs/COMPILER_CLI.md @@ -82,7 +82,7 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build | 选项 | 说明 | |---|---| -| `--php-version <8.2|8.3|8.4|8.5>` | 限制接受的 PHP 语法版本,默认 `8.5`。 | +| `--php-version <8.4|8.5>` | 限制接受的 PHP 语法版本,默认 `8.5`。 | | `--cxx-std ` | C++ 标准,例如 `c++17`、`c++20`。 | | `--march ` | 目标指令集,例如 `native`、`x86-64-v3`。 | | `--target-platform ` | 交叉编译目标 triple。 | @@ -93,6 +93,8 @@ bin/tpc.php app.php --dry --build-dir /tmp/typephp-build `--php-version` 控制解析器接受的源码语法,也用于 `project.yml` 中依据 `PHP_VERSION` / `PHP_VERSION_ID` 选择源文件。它不负责选择链接的 PHP 安装目录。 +TypePHP 和 PHPX 的最低运行时版本均为 PHP 8.4。`--php-version` 与实际链接的 `libphp.so` 不要求小版本完全相同,但两者都必须为 PHP 8.4 或更高版本。 + ## C++ 编译和链接参数 这些参数均可重复: diff --git a/docs/INCOMPATIBLE_PHP_FEATURES.md b/docs/INCOMPATIBLE_PHP_FEATURES.md index fa3cfdc7..d3b182f3 100644 --- a/docs/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/INCOMPATIBLE_PHP_FEATURES.md @@ -16,7 +16,7 @@ - 不支持可变变量 `$$var`。 - PHP 8.4 property hooks 会编译为 AOT getter/setter,并注册对应的 Zend hook 元数据;直接属性读写、Reflection 和对象遍历均受支持。当前不支持对 hook 属性取引用。 - PHP 8.4 Reflection Lazy Object 不能用于 TypePHP AOT 类。AOT 类以 persistent internal class 注册,而 Zend 的 `zend_object_make_lazy()` 明确拒绝 internal class;运行时动态加载的 ZendPHP user class 不受此限制。 -- 支持 `private(set)` 与 `protected(set)` 非对称属性可见性;在 PHP 8.2~8.4 后端通过自定义属性写 handler 执行同等作用域检查。 +- 支持 `private(set)` 与 `protected(set)` 非对称属性可见性,并通过 PHP 8.4+ 的类级对象 handler 执行同等作用域检查。 - 不支持闭包或箭头函数按引用返回。 - `__construct()` 不允许返回值。 - 参数默认值不允许出现在必填参数之前(`PHP`允许,但会直接丢弃此默认参数)。 diff --git a/docs/INTERFACE_PROPERTY_HOOKS.md b/docs/INTERFACE_PROPERTY_HOOKS.md index d5e792a6..59ae901a 100644 --- a/docs/INTERFACE_PROPERTY_HOOKS.md +++ b/docs/INTERFACE_PROPERTY_HOOKS.md @@ -20,8 +20,7 @@ interface Named - Interface 不持有属性槽,不生成 getter/setter 实现,也不产生访问时的契约检查。 - TypePHP 在编译期验证已知 AOT 类是否满足属性的可见性、类型和 `get`/`set` 能力。 - PHP 8.4 目标在 MINIT 注册原生 Zend Hook 元数据,使 Reflection 和动态 PHP 类获得相同契约。 -- 编译器前端解析和验证该语法不依赖 PHP 8.4;但使用 Property Hooks 的最终目标运行时必须链接 PHP 8.4 或更高版本。 -- PHP 8.3 不提供静默降级。缺少 Zend Hook 元数据会使动态属性访问、Reflection、JSON、序列化和 `eval()` 类链接的行为取决于执行路径,不能视为可靠支持。 +- TypePHP、PHPX 和最终目标运行时的最低版本均为 PHP 8.4,不提供旧版本降级路径。 ## 2. 语法与诊断 @@ -72,7 +71,7 @@ php::registerAbstractPropertyHooks( ); ``` -它只在 `PHP_VERSION_ID >= 80400` 下访问 PHP 8.4 ABI,并负责: +TypePHP/PHPX 已统一要求 PHP 8.4+,因此该 helper 直接访问 PHP 8.4 ABI,并负责: - 持久化分配 `zend_property_info::hooks`; - 创建没有 handler 的 abstract `get`/`set` `zend_internal_function` 元数据; @@ -84,17 +83,12 @@ php::registerAbstractPropertyHooks( ## 5. PHP 版本边界 -TypePHP 应区分编译器宿主与目标 PHP: +TypePHP 区分源码语言版本与链接运行时: -- PHP Parser 和 TypePHP 前端可以在 PHP 8.3 环境解析该语法; -- 构建后端以项目选择的 PHP language/target version 以及最终链接的 PHP headers/`libphp` 作为能力依据;编译器进程自身可以运行在更旧的 PHP 上; -- 发现 Property Hooks 且目标低于 PHP 8.4 时,在 C++ 编译前报告: - -```text -Property Hooks require PHP 8.4 or later as the target runtime -``` - -PHPX 仍使用条件编译作为 ABI 防线,但不应把清晰的功能诊断推迟为 C++ 编译错误。 +- `--php-version` 只允许 `8.4` 或 `8.5`,用于解析语法和处理项目条件; +- PHPX headers、`libphp` 与最终运行时必须为 PHP 8.4 或更高版本; +- `--php-version` 与 `libphp.so` 的小版本不要求完全一致,例如使用 8.5 语法模式并链接 PHP 8.4 时,最终能否构建仍由实际使用的 Zend API 决定; +- PHP 8.4 以下环境在 TypePHP/PHPX 构建入口直接拒绝。 ## 6. TDD 覆盖 @@ -107,8 +101,7 @@ PHPX 仍使用条件编译作为 ABI 防线,但不应把清晰的功能诊断 5. Reflection 的 abstract、virtual、hasHook/getHook 元数据; 6. PHP 8.4 动态 PHP 类的成功与失败链接; 7. O0/O3 结果一致,Interface 不生成属性槽或 Native Hook 实现; -8. PHP 8.3 目标得到明确的构建期错误; -9. PHPX helper 在 NTS/ZTS 和 PHP 8.4/8.5 下的生命周期与 ABI 回归。 +8. PHPX helper 在 NTS/ZTS 和 PHP 8.4/8.5 下的生命周期与 ABI 回归。 ## 7. 实施顺序 diff --git a/docs/PROPERTY_HOOKS.md b/docs/PROPERTY_HOOKS.md index 0c4fb8a1..a2019dcc 100644 --- a/docs/PROPERTY_HOOKS.md +++ b/docs/PROPERTY_HOOKS.md @@ -166,17 +166,9 @@ zend_string_init(data, length, true); - NTS 没有锁开销; - ZTS 下在工作线程处理请求前已完成注册,不会并发修改 class entry。 -## 7. PHP 版本隔离 +## 7. PHP 版本边界 -Property Hook 是 PHP 8.4 新特性。PHPX 对头文件、结构字段和实现均使用条件编译: - -```cpp -#if PHP_VERSION_ID >= 80400 -#include "zend_property_hooks.h" -#endif -``` - -PHP 8.2/8.3 构建不会引用以下 PHP 8.4 ABI: +TypePHP 与 PHPX 的最低版本均为 PHP 8.4,因此 Property Hook 实现直接使用以下 PHP 8.4 ABI: - `zend_property_info::hooks`; - `zend_class_entry::num_hooked_props`; @@ -185,7 +177,7 @@ PHP 8.2/8.3 构建不会引用以下 PHP 8.4 ABI: - `ZEND_VIRTUAL_PROPERTY_OFFSET`; - `zend_hooked_object_get_iterator()`。 -旧版本 fallback 会在错误地尝试注册 Hook 时抛出明确错误。TypePHP/PHPX 需要针对目标 PHP 版本分别编译,不能把链接 PHP 8.4 的 PHPX 二进制直接用于 PHP 8.2/8.3。 +PHPX 头文件和 CMake 配置会拒绝 PHP 8.4 以下的 headers/`php-config`。PHP 8.4 与 8.5 仍分别构建对应 PHPX 二进制;`--php-version` 只控制源码语法,不要求与 `libphp.so` 的小版本完全相同,但两者都必须不低于 8.4。 ## 8. ABI 风险和升级检查 @@ -196,7 +188,7 @@ PHP 8.2/8.3 构建不会引用以下 PHP 8.4 ABI: 1. TypePHP 与 PHPX 版本绑定,并针对具体 PHP 版本重新编译; 2. 注册流程与 Zend 编译器处理原生 Property Hook 的步骤一致; 3. 只复用 Zend 导出的 iterator,不复制其复杂实现; -4. PHP 8.4 以下版本已在编译期隔离; +4. PHP 8.4 以下版本在构建入口统一拒绝; 5. 所有注册均在 MINIT 完成,不增加请求热路径上的名称查找。 升级 PHP 版本时必须检查: @@ -221,7 +213,7 @@ Property Hook 改动至少需要覆盖: - 序列化只包含真实存储状态; - 动态 Zend 属性读写; - 继承和属性可见性; -- PHP 8.4 构建以及 PHP 8.2/8.3 条件编译。 +- PHP 8.4 与 PHP 8.5 构建。 当前核心回归测试位于: diff --git a/phpunit/BashCompletionTest.php b/phpunit/BashCompletionTest.php index 35c6d8e9..f852ffdf 100644 --- a/phpunit/BashCompletionTest.php +++ b/phpunit/BashCompletionTest.php @@ -61,6 +61,10 @@ final class BashCompletionTest extends TestCase "8.4\n", $this->complete($script, ['tpc', '--php-version', '8.4']), ); + self::assertSame( + "8.4\n8.5\n", + $this->complete($script, ['tpc', '--php-version', '']), + ); self::assertSame( "-O2\n", $this->complete($script, ['tpc', '-O2']), diff --git a/phpunit/README.md b/phpunit/README.md index 332ed03f..fa41ee0d 100644 --- a/phpunit/README.md +++ b/phpunit/README.md @@ -219,7 +219,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest, macos-latest] - php-version: ['8.2', '8.3'] + php-version: ['8.4', '8.5'] steps: - uses: actions/checkout@v2 diff --git a/phpunit/TEST_REPORT.md b/phpunit/TEST_REPORT.md index f9711444..5144235e 100644 --- a/phpunit/TEST_REPORT.md +++ b/phpunit/TEST_REPORT.md @@ -223,9 +223,9 @@ OK (66 tests, 200+ assertions) 已准备好 CI/CD 集成,配置文件见 `phpunit/README.md`。 ### 测试矩阵 -- ✅ Windows + PHP 8.2/8.3 -- ✅ Linux + PHP 8.2/8.3 -- ✅ macOS + PHP 8.2/8.3 +- ✅ Windows + PHP 8.4/8.5 +- ✅ Linux + PHP 8.4/8.5 +- ✅ macOS + PHP 8.4/8.5 ## 下一步计划 diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 2d0dbb7b..7b2ae76c 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -142,14 +142,36 @@ class CompilerBaseApiTest extends TestCase $parser->parse(' trim(...);'); } - public function testPhpLanguageVersionAcceptsPipeAt85AndRejectsInvalidValue(): void + public function testPhpLanguageVersionAcceptsPipeAt85AndRejectsPre84Target(): void { $this->compiler->setPhpVersion('8.5'); $parser = $this->getPropertyValue('parser'); $this->assertNotEmpty($parser->parse(' trim(...);')); $this->expectException(TestError::class); - $this->compiler->setPhpVersion('8.1'); + $this->expectExceptionMessage('Supported versions: 8.4, 8.5'); + $this->compiler->setPhpVersion('8.3'); + } + + public function testLinkedPhpRuntimeMustBeAtLeast84WithoutMatchingLanguageMinor(): void + { + $php83 = $this->testDir . '/php83'; + mkdir($php83 . '/include/php/main', 0777, true); + file_put_contents($php83 . '/include/php/main/php_version.h', "#define PHP_VERSION_ID 80399\n"); + + try { + $this->invokeMethod('validatePhpRuntimeMinimum', $php83); + self::fail('Expected PHP 8.3 target runtime to be rejected'); + } catch (TestError $error) { + self::assertStringContainsString('requires libphp 8.4 or later', $error->getMessage()); + } + + $php84 = $this->testDir . '/php84'; + mkdir($php84 . '/include/php/main', 0777, true); + file_put_contents($php84 . '/include/php/main/php_version.h', "#define PHP_VERSION_ID 80400\n"); + $this->compiler->setPhpVersion('8.5'); + $this->invokeMethod('validatePhpRuntimeMinimum', $php84); + $this->addToAssertionCount(1); } public function testMiscObjectCacheIsInvalidatedWhenCompileOptionsChange(): void diff --git a/phpunit/src/ConstantExpressionValidatorTest.php b/phpunit/src/ConstantExpressionValidatorTest.php index e7f81e06..cb7ec3fb 100644 --- a/phpunit/src/ConstantExpressionValidatorTest.php +++ b/phpunit/src/ConstantExpressionValidatorTest.php @@ -173,9 +173,9 @@ final class ConstantExpressionValidatorTest extends PHPUnit\Framework\TestCase yield 'parameter default allows new' => ['function f($value = new Value()) {}', '8.4']; yield 'global const allows new' => ['const VALUE = new Value();', '8.4']; yield 'static variable allows new' => ['function f() { static $value = new Value(); }', '8.4']; - yield 'PHP 8.3 static variable allows dynamic initializer' => [ + yield 'PHP 8.4 static variable allows dynamic initializer' => [ 'function f(int $seed) { static $value = loadValue($seed); }', - '8.3', + '8.4', ]; yield 'PHP 8.5 class constant allows static closure' => [ 'class C { const VALUE = static function (): int { return 1; }; }', @@ -202,11 +202,6 @@ final class ConstantExpressionValidatorTest extends PHPUnit\Framework\TestCase '8.4', 'New expressions are not supported in this context', ]; - yield 'PHP 8.2 static variable rejects dynamic initializer' => [ - 'function f(int $seed) { static $value = loadValue($seed); }', - '8.2', - 'Constant expression contains invalid operations', - ]; yield 'property rejects new' => [ 'class C { public mixed $value = new Value(); }', '8.4', diff --git a/src/Build/SourcePipelineTrait.php b/src/Build/SourcePipelineTrait.php index 2afae88b..966596e6 100644 --- a/src/Build/SourcePipelineTrait.php +++ b/src/Build/SourcePipelineTrait.php @@ -88,6 +88,10 @@ trait SourcePipelineTrait $phpDir = $this->getPhpDir(); } + if (!($this->getPlatform() instanceof Wasi)) { + $this->validatePhpRuntimeMinimum($phpDir); + } + if ($this->getPlatform() instanceof Linux) { try { (new LibPhpxInstaller())->ensure($this->getPhpxDir(), $phpDir); @@ -173,6 +177,41 @@ trait SourcePipelineTrait } } + /** Validate the selected headers/libphp independently of --php-version. */ + protected function validatePhpRuntimeMinimum(string $phpDir): void + { + $versionId = null; + $headers = [ + $phpDir . '/include/php/main/php_version.h', + $phpDir . '/include/main/php_version.h', + ]; + foreach ($headers as $header) { + if (!is_file($header)) { + continue; + } + $contents = file_get_contents($header); + if (is_string($contents) && preg_match('/^#define\s+PHP_VERSION_ID\s+(\d+)/m', $contents, $matches)) { + $versionId = (int) $matches[1]; + break; + } + } + + if ($versionId === null) { + $phpConfig = $phpDir . '/bin/php-config'; + if (is_executable($phpConfig)) { + $value = shell_exec(escapeshellarg($phpConfig) . ' --vernum 2>/dev/null'); + if (is_string($value) && ctype_digit(trim($value))) { + $versionId = (int) trim($value); + } + } + } + + if ($versionId !== null && $versionId < 80400) { + $version = intdiv($versionId, 10000) . '.' . intdiv($versionId % 10000, 100); + $this->error("TypePHP requires libphp 8.4 or later; selected PHP installation is {$version}: {$phpDir}"); + } + } + protected function shouldIgnoreFile(string $file): bool { foreach ($this->ignorePaths as $ignorePath) { diff --git a/src/Cli/CompletionMetadata.php b/src/Cli/CompletionMetadata.php index b86b3550..a2f7e4a6 100644 --- a/src/Cli/CompletionMetadata.php +++ b/src/Cli/CompletionMetadata.php @@ -45,7 +45,7 @@ final class CompletionMetadata '--optimize' => ['0', '1', '2', '3'], '-m' => ['bin', 'lib', 'ext'], '--mode' => ['bin', 'lib', 'ext'], - '--php-version' => ['8.2', '8.3', '8.4', '8.5'], + '--php-version' => ['8.4', '8.5'], '--cxx-std' => ['c++17', 'c++20', 'c++23'], '--sanitize' => ['address', 'undefined'], '--wasm=' => ['component', 'browser'], diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 6396e2db..2f033190 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -485,8 +485,8 @@ class CompilerBase implements PropertyAccessContext public function __construct(string $rootPath) { $this->osType = PHP_OS_FAMILY; - if (version_compare(PHP_VERSION, '8.2.0', '<')) { - $this->error('PHP 8.2.0 or later is required'); + if (version_compare(PHP_VERSION, '8.4.0', '<')) { + $this->error('PHP 8.4.0 or later is required'); } if (version_compare(PHP_VERSION, '8.6.0', '>=')) { $this->error('PHP 8.6.0 or later is not supported'); @@ -523,8 +523,8 @@ class CompilerBase implements PropertyAccessContext /** Set the PHP language version accepted by the parser. */ public function setPhpVersion(string $version): void { - if (!preg_match('/^8\.(2|3|4|5)(?:\.0)?$/', $version, $matches)) { - $this->error('Unsupported PHP language version: `' . $version . '`. Supported versions: 8.2, 8.3, 8.4, 8.5'); + if (!preg_match('/^8\.(4|5)(?:\.0)?$/', $version, $matches)) { + $this->error('Unsupported PHP language version: `' . $version . '`. Supported versions: 8.4, 8.5'); } $this->phpVersion = '8.' . $matches[1] . '.0'; diff --git a/src/Metadata/Constants.php b/src/Metadata/Constants.php index 511b8403..c2da960f 100644 --- a/src/Metadata/Constants.php +++ b/src/Metadata/Constants.php @@ -165,7 +165,7 @@ class Constants ], 'php-version' => [ 'longPrefix' => 'php-version', - 'description' => 'PHP language version to accept (8.2, 8.3, 8.4, or 8.5; default: 8.5)', + 'description' => 'PHP language version to accept (8.4 or 8.5; default: 8.5)', 'required' => false, ], 'force' => [ diff --git a/src/Parser/NullsafeAccessTrait.php b/src/Parser/NullsafeAccessTrait.php index 5ba0e215..eb98bd34 100644 --- a/src/Parser/NullsafeAccessTrait.php +++ b/src/Parser/NullsafeAccessTrait.php @@ -122,8 +122,8 @@ trait NullsafeAccessTrait $this->context->beforeStmtLines[] = $code; // C++ temporaries are function-scoped; release their zvals at the PHP statement boundary. - foreach (array_reverse($ownedTmpVars) as $tmpVar) { - $this->context->afterStmtLines[] = $tmpVar . '.unset();'; + foreach (array_reverse($ownedTmpVars) as $ownedTmpVar) { + $this->context->afterStmtLines[] = $ownedTmpVar . '.unset();'; } return "{$tmpFn}()"; } diff --git a/src/Preprocessor.php b/src/Preprocessor.php index adef7598..07f21134 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -1440,9 +1440,6 @@ class Preprocessor extends CompilerBase protected function parseClassPropertyDef(Node\Stmt\Property $v): void { - if ($v->hooks !== [] && version_compare($this->phpVersion, '8.4', '<')) { - $this->fatalError($v, 'Property Hooks require PHP 8.4 or later as the target runtime'); - } $oriCtx = $this->context; $this->context = $this->classDef->propertyContext; $nullable = $v->type instanceof NullableType; @@ -1666,9 +1663,6 @@ class Preprocessor extends CompilerBase if ($property->hooks === []) { $this->fatalError($property, 'Interfaces may only include hooked properties'); } - if (version_compare($this->phpVersion, '8.4', '<')) { - $this->fatalError($property, 'Property Hooks require PHP 8.4 or later as the target runtime'); - } if ($property->flags & (Modifiers::PRIVATE | Modifiers::PROTECTED)) { $this->fatalError($property, 'Property in interface cannot be protected or private'); } diff --git a/src/Transform/ConstantExpressionValidationVisitor.php b/src/Transform/ConstantExpressionValidationVisitor.php index 99fafd56..e8803a15 100644 --- a/src/Transform/ConstantExpressionValidationVisitor.php +++ b/src/Transform/ConstantExpressionValidationVisitor.php @@ -19,15 +19,13 @@ use TypePhp\Exception\SyntaxError; * false: class constants, property defaults and enum cases. * true: attributes, parameter defaults and global constants. * - * Static variable initializers are constant expressions on PHP 8.2. PHP 8.3 - * and later compile them as regular expressions and evaluate them only once. + * PHP 8.4+ compiles static variable initializers as regular expressions and + * evaluates them only once, so they do not use the constant-expression path. */ final class ConstantExpressionValidationVisitor extends NodeVisitorAbstract { private readonly ConstantExpressionValidator $validator; - private readonly bool $supportsDynamicStaticInitializers; - /** @param null|Closure(Node, string): never $fatalError */ public function __construct( string $phpVersion, @@ -35,7 +33,6 @@ final class ConstantExpressionValidationVisitor extends NodeVisitorAbstract ) { $this->validator = new ConstantExpressionValidator($phpVersion); - $this->supportsDynamicStaticInitializers = version_compare($phpVersion, '8.3', '>='); } public function enterNode(Node $node): null @@ -94,14 +91,6 @@ final class ConstantExpressionValidationVisitor extends NodeVisitorAbstract return null; } - if ($node instanceof Node\Stmt\Static_ && !$this->supportsDynamicStaticInitializers) { - foreach ($node->vars as $variable) { - if ($variable->default !== null) { - $this->validator->validate($variable->default, allowDynamic: true); - } - } - } - return null; } } diff --git a/src/Translator.php b/src/Translator.php index b374a08e..c1b84b08 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -279,7 +279,7 @@ class Translator extends Preprocessor $climate->tab()->out('--generate-completion=bash Generate Bash completion script'); $climate->tab()->out('--lto Enable Link Time Optimization (-flto)'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); - $climate->tab()->out('--php-version PHP language version to accept (8.2-8.5, default: 8.5)'); + $climate->tab()->out('--php-version PHP language version to accept (8.4-8.5, default: 8.5)'); $climate->tab()->out('--no-progress Disable progress bar, output per-file compilation progress line by line'); $climate->tab()->out('--no-console Hide console window (Windows only, GUI application)'); $climate->tab()->out('--no-color Disable ANSI color output'); @@ -1938,7 +1938,7 @@ CODE; $buildCreateBody = function () use ($classDef, $className, $handlers, $initBlock, $delegateToParentAllocator): string { $body = $classDef->ctorInit; $body .= "auto obj = typephp_create_object_with_defaults(\n"; - $body .= "class_type, create_object_{$className}, &{$handlers}, "; + $body .= "class_type, create_object_{$className}, "; $body .= ($delegateToParentAllocator ? 'true' : 'false') . ",\n"; $body .= "[&](zend_object *obj) {\n"; $body .= $initBlock; @@ -1948,19 +1948,12 @@ CODE; }; $code .= "typephp_install_property_handlers({$ce}, &{$handlers});\n"; - $code .= "#if (PHP_VERSION_ID < 80400)\n"; - $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; - $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; - $code .= $buildCreateBody(); - $code .= "};\n"; if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) { - $code .= "#else\n"; $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; $code .= $buildCreateBody(); $code .= "};\n"; } - $code .= "#endif\n"; } } return $code; diff --git a/src/gen_stub.php b/src/gen_stub.php index ef8acecd..e9dda816 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -27,11 +27,6 @@ const PHP_83_VERSION_ID = 80300; const PHP_84_VERSION_ID = 80400; const PHP_85_VERSION_ID = 80500; const ALL_PHP_VERSION_IDS = [ - PHP_70_VERSION_ID, - PHP_80_VERSION_ID, - PHP_81_VERSION_ID, - PHP_82_VERSION_ID, - PHP_83_VERSION_ID, PHP_84_VERSION_ID, PHP_85_VERSION_ID, ]; @@ -1196,18 +1191,8 @@ class ReturnInfo { $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; + public function beginArgInfo(string $funcInfoName, int $minArgs): string { + return $this->beginArgInfoCompatible($funcInfoName, $minArgs); } /** @@ -1553,11 +1538,6 @@ class FuncInfo { $code .= "\t{ 0 },\n"; $code .= "};\n"; - $php84MinimumCompatibility = $this->minimumPhpVersionIdCompatibility === null || $this->minimumPhpVersionIdCompatibility >= PHP_84_VERSION_ID; - if (!$php84MinimumCompatibility) { - return "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n$code#endif\n"; - } - return $code; } @@ -1628,26 +1608,11 @@ class FuncInfo { $docComment = $this->exposedDocComment ? '"' . $this->exposedDocComment->escape() . '"' : "NULL"; $framelessFuncInfosName = !empty($this->framelessFunctionInfos) ? $this->getFramelessFunctionInfosName() : "NULL"; - // Assume 8.4+ here, if older versions are supported this is conditional $code = $flagsByPhpVersions->generateVersionDependentFlagCode( "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s, $framelessFuncInfosName, $docComment)\n", PHP_84_VERSION_ID ); - $php84MinimumCompatibility = $this->minimumPhpVersionIdCompatibility === null || $this->minimumPhpVersionIdCompatibility >= PHP_84_VERSION_ID; - if (!$php84MinimumCompatibility) { - $code = "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n$code"; - $code .= "#else\n"; - - $code .= $flagsByPhpVersions->generateVersionDependentFlagCode( - "\tZEND_RAW_FENTRY($zendName, $name, $argInfoName, %s)\n", - $this->minimumPhpVersionIdCompatibility, - PHP_83_VERSION_ID - ); - - $code .= "#endif\n"; - } - return $code; } @@ -2291,7 +2256,6 @@ OUPUT_EXAMPLE $code = $this->return->beginArgInfo( $this->getArgInfoName(), $this->numRequiredArgs, - $minPHPCompatability === null || $minPHPCompatability >= PHP_81_VERSION_ID ); foreach ($this->args as $argInfo) { @@ -2948,12 +2912,6 @@ class ConstInfo extends VariableLike $commentCode = "NULL"; } - $php83MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_83_VERSION_ID; - - if ($this->type && !$php83MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_83_VERSION_ID . ")\n"; - } - if ($this->type) { $typeCode = $this->getTypeCode($constName, $code); @@ -2970,11 +2928,7 @@ class ConstInfo extends VariableLike ); } - if ($this->type && !$php83MinimumCompatibility) { - $code .= "#else\n"; - } - - if (!$this->type || !$php83MinimumCompatibility) { + if (!$this->type) { if (!empty($this->attributes)) { $template = "\tzend_class_constant *const_" . $this->name->getDeclarationName() . " = "; } else { @@ -2987,10 +2941,6 @@ class ConstInfo extends VariableLike ); } - if ($this->type && !$php83MinimumCompatibility) { - $code .= "#endif\n"; - } - $code .= "\tzend_string_release_ex(const_{$constName}_name, true);\n"; return $code; @@ -3766,16 +3716,6 @@ class ClassInfo { $code = ''; - $php80MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_80_VERSION_ID; - $php81MinimumCompatibility = $this->phpVersionIdMinimumCompatibility === null || $this->phpVersionIdMinimumCompatibility >= PHP_81_VERSION_ID; - // TypePHP classes may target a pre-8.4 Zend runtime even when parsing - // newer PHP syntax, so class registration retains the old API branch. - $php84MinimumCompatibility = false; - - if ($this->type === "enum" && !$php81MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_81_VERSION_ID . ")\n"; - } - if ($this->cond) { $code .= "#if {$this->cond}\n"; } @@ -3807,10 +3747,6 @@ class ClassInfo { } if ($this->type === "class" || $this->type === "trait") { - if (!$php84MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n"; - } - $template = "\tclass_entry = zend_register_internal_class_with_flags(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ", %s);\n"; $entries = $flags->generateVersionDependentFlagCode($template, $this->phpVersionIdMinimumCompatibility ? max($this->phpVersionIdMinimumCompatibility, PHP_84_VERSION_ID) : null); if ($entries !== '') { @@ -3819,15 +3755,6 @@ class ClassInfo { $code .= sprintf($template, "0"); } - if (!$php84MinimumCompatibility) { - $code .= "#else\n"; - - $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n"; - if (!$flags->isEmpty()) { - $code .= $flags->generateVersionDependentFlagCode("\tclass_entry->ce_flags |= %s;\n", $this->phpVersionIdMinimumCompatibility); - } - $code .= "#endif\n"; - } } else { $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n"; if (!$flags->isEmpty()) { @@ -3838,15 +3765,7 @@ class ClassInfo { } if ($this->exposedDocComment) { - if (!$php84MinimumCompatibility) { - $code .= "#if (PHP_VERSION_ID >= " . PHP_84_VERSION_ID . ")\n"; - } - $code .= "\tclass_entry->doc_comment = " . $this->exposedDocComment->getInitCode() . "\n"; - - if (!$php84MinimumCompatibility) { - $code .= "#endif\n"; - } } $code .= generateCodeWithConditions( @@ -3879,20 +3798,9 @@ class ClassInfo { if ($this->alias) { $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n"; } - // Reusable strings for wrapping conditional PHP 8.0+ code - if ($php80MinimumCompatibility) { - $php80CondStart = ''; - $php80CondEnd = ''; - } else { - $php80CondStart = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")"; - $php80CondEnd = "#endif\n"; - } - $declaredStrings = []; if (!empty($this->attributes)) { - $code .= $php80CondStart; - foreach ($this->attributes as $key => $attribute) { $code .= $attribute->generateCode( "zend_add_class_attribute(class_entry", @@ -3903,25 +3811,18 @@ class ClassInfo { ); } - $code .= $php80CondEnd; } if ($attributeInitializationCode = generateConstantAttributeInitialization($this->constInfos, $allConstInfos, $this->phpVersionIdMinimumCompatibility, $this->cond, $declaredStrings)) { - $code .= $php80CondStart; $code .= "\n" . $attributeInitializationCode; - $code .= $php80CondEnd; } if ($attributeInitializationCode = generatePropertyAttributeInitialization($this->propertyInfos, $allConstInfos, $this->phpVersionIdMinimumCompatibility, $declaredStrings)) { - $code .= $php80CondStart; $code .= "\n" . $attributeInitializationCode; - $code .= $php80CondEnd; } if ($attributeInitializationCode = generateFunctionAttributeInitialization($this->funcInfos, $allConstInfos, $this->phpVersionIdMinimumCompatibility, $this->cond, $declaredStrings)) { - $code .= $php80CondStart; $code .= "\n" . $attributeInitializationCode; - $code .= $php80CondEnd; } $code .= "\n\treturn class_entry;\n"; @@ -3932,10 +3833,6 @@ class ClassInfo { $code .= "#endif\n"; } - if ($this->type === "enum" && !$php81MinimumCompatibility) { - $code .= "#endif\n"; - } - return $code; } @@ -4483,13 +4380,12 @@ class FileInfo { } else if ($tag->name === 'generate-legacy-arginfo') { if ($tag->value && !in_array((int) $tag->value, ALL_PHP_VERSION_IDS, true)) { 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), " . - "\"" . PHP_84_VERSION_ID . "\" (PHP 8.4), \"" . PHP_85_VERSION_ID . "\" (PHP 8.5), \"" . $tag->value . "\" provided" + "Legacy PHP version must be one of: \"" . PHP_84_VERSION_ID . "\" (PHP 8.4), " . + "\"" . PHP_85_VERSION_ID . "\" (PHP 8.5), \"" . $tag->value . "\" provided" ); } - $this->minimumPhpVersionIdCompatibility = ($tag->value ? (int) $tag->value : PHP_70_VERSION_ID); + $this->minimumPhpVersionIdCompatibility = ($tag->value ? (int) $tag->value : PHP_84_VERSION_ID); } else if ($tag->name === 'generate-class-entries') { $this->generateClassEntries = true; $this->declarationPrefix = $tag->value ? $tag->value . " " : ""; @@ -5628,18 +5524,10 @@ function generateArgInfoCode( } } - $php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID; - if ($fileInfo->generateClassEntries) { $declaredStrings = []; $attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); $attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); - if ($attributeInitializationCode) { - if (!$php80MinimumCompatibility) { - $attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n"; - } - } - if ($attributeInitializationCode !== "") { $code .= "\nstatic void register_{$stubFilenameWithoutExtension}_symbols(int module_number)\n"; $code .= "{\n";