diff --git a/lib/Compiler/NewWithoutParensCompileCheck.php b/lib/Compiler/NewWithoutParensCompileCheck.php index fc993f6d000..0e95b2294b2 100644 --- a/lib/Compiler/NewWithoutParensCompileCheck.php +++ b/lib/Compiler/NewWithoutParensCompileCheck.php @@ -10,10 +10,10 @@ use PHPCfg\Script; /** - * Reject invalid `new` in class **constant** and **static property** initializers (#6549, #9484, #9804, #9974, #10095). + * Reject invalid `new` in **static property** initializers (#6549, #9484, #10095). * - * php-src rejects all `new` in class constant expressions with - * "New expressions are not supported in this context" (zend_compile_const_expr). + * PHP 8.3+ allows `new` in class constant initializers (#10198, RFC new_in_initializers); + * those are lowered by the compiler and materialized at TYPE_DECLARE_CLASS_CONST (#3196). * Static property defaults remain forbidden; instance property/param defaults may use * `new` with or without `()` per PHP 8.1+ (#3391, #5362). * @@ -42,11 +42,6 @@ private function walkClassLike( Op\Stmt\Class_|Op\Stmt\Interface_|Op\Stmt\Trait_|Op\Stmt\Enum_ $class ): void { foreach ($class->stmts->children as $stmt) { - if ($stmt instanceof Op\Terminal\Const_) { - $ops = $stmt->valueBlock->children ?? []; - $this->walkOpsRejectAllNew($ops); - continue; - } if ($stmt instanceof Property && $stmt->static) { $this->rejectStaticPropertyDefaultNew($stmt); } diff --git a/lib/CompilerVersion.php b/lib/CompilerVersion.php index b6c7e09f9e9..4a4b554eb0a 100644 --- a/lib/CompilerVersion.php +++ b/lib/CompilerVersion.php @@ -55,10 +55,10 @@ public static function supportsOverrideAttribute(): bool return version_compare(self::VERSION, '8.3', '>='); } - /** @deprecated Zend rejects `new` in class constants at compile time (#9974); always false. */ + /** PHP 8.3+ `new` expressions in class constant initializers (Zend/zend_compile.c, #10198). */ public static function supportsClassConstObjectExpressions(): bool { - return false; + return version_compare(self::VERSION, '8.3', '>='); } /** PHP 8.4+ hexadecimal floating-point literals (Zend/zend_language_scanner.l, issue #7041). */ diff --git a/test/compliance/cases/language/class_const_new_expr.phpt b/test/compliance/cases/language/class_const_new_expr.phpt index 0c6baf81121..e8ca945536c 100644 --- a/test/compliance/cases/language/class_const_new_expr.phpt +++ b/test/compliance/cases/language/class_const_new_expr.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: class constant `new` expression rejected (#9974, Zend/zend_compile.c) +Language: class constant `new` expression with ctor args (#10198, Zend/zend_compile.c) --FILE-- n); ---EXPECT_EXIT-- -255 +--EXPECT-- +int(1) diff --git a/test/compliance/cases/language/class_const_new_expression.phpt b/test/compliance/cases/language/class_const_new_expression.phpt index 4de0e148536..d2a01040164 100644 --- a/test/compliance/cases/language/class_const_new_expression.phpt +++ b/test/compliance/cases/language/class_const_new_expression.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: class constant object expression const X = new C() — compile-error (#9804, #9974, Zend/zend_compile.c) +Language: class constant `new` expression — PHP 8.3+ (#10198, Zend/zend_compile.c) --FILE-- n); ---EXPECT_EXIT-- -255 +echo Holder::X === Holder::X ? "1\n" : "0\n"; +--EXPECT-- +int(1) +1 diff --git a/test/compliance/cases/language/class_const_new_object.phpt b/test/compliance/cases/language/class_const_new_object.phpt index 632fd1d9616..1dffdd20c64 100644 --- a/test/compliance/cases/language/class_const_new_object.phpt +++ b/test/compliance/cases/language/class_const_new_object.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: class constant object `new` expression rejected (#9974, Zend/zend_compile.c) +Language: class constant object `new` expression (#10198, Zend/zend_compile.c) --FILE-- x === 7 ? "1\n" : "0\n"; ---EXPECT_EXIT-- -255 +--EXPECT-- +(object) array ( +) +1 +Foo::__set_state(array ( + 'x' => 7, +)) +1 diff --git a/test/compliance/cases/language/class_const_new_rejected.phpt b/test/compliance/cases/language/class_const_new_rejected.phpt index d1786a36767..e7147f66c0a 100644 --- a/test/compliance/cases/language/class_const_new_rejected.phpt +++ b/test/compliance/cases/language/class_const_new_rejected.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: class constant expression must reject `new` (#9517, #9974, Zend/zend_compile.c) +Language: class constant `new` expression — shared object identity (#10198, Zend/zend_constants.c) --FILE-- n); ---EXPECT_EXIT-- -255 +echo Holder::X === Holder::X ? "same\n" : "diff\n"; +--EXPECT-- +int(1) +same diff --git a/test/compliance/cases/language/class_const_object.phpt b/test/compliance/cases/language/class_const_object.phpt index 9825defc88c..0c9b21f186b 100644 --- a/test/compliance/cases/language/class_const_object.phpt +++ b/test/compliance/cases/language/class_const_object.phpt @@ -1,10 +1,11 @@ --TEST-- -Language: class constants with object expressions rejected at compile (#9804, Zend/zend_compile.c) +Language: class constants with object expressions (#10198, Zend/zend_constants.c) --FILE-- n); ---EXPECT_EXIT-- -255 +--EXPECT-- +int(1) diff --git a/test/compliance/cases/language/new_in_constant.phpt b/test/compliance/cases/language/new_in_constant.phpt index 81d41e2d3e2..95c1e24d187 100644 --- a/test/compliance/cases/language/new_in_constant.phpt +++ b/test/compliance/cases/language/new_in_constant.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: new in class constant initializer rejected (#9804, #9974, Zend/zend_compile.c) +Language: new in class constant initializer — PHP 8.3+ (#10198, Zend/zend_compile.c) --FILE-- n); ---EXPECT_EXIT-- -255 +--EXPECT-- +int(1) diff --git a/test/compliance/cases/language/new_in_constant_expr.phpt b/test/compliance/cases/language/new_in_constant_expr.phpt index 00fe258f0c7..95c1e24d187 100644 --- a/test/compliance/cases/language/new_in_constant_expr.phpt +++ b/test/compliance/cases/language/new_in_constant_expr.phpt @@ -1,5 +1,5 @@ --TEST-- -Language: new in class constant initializer rejected (#9517, Zend/zend_compile.c) +Language: new in class constant initializer — PHP 8.3+ (#10198, Zend/zend_compile.c) --FILE-- n); ---EXPECT_EXIT-- -255 +--EXPECT-- +int(1) diff --git a/test/repro-maintainer/parity_new_without_parens.php b/test/repro-maintainer/parity_new_without_parens.php index 5065c818026..6dbb9e6170a 100644 --- a/test/repro-maintainer/parity_new_without_parens.php +++ b/test/repro-maintainer/parity_new_without_parens.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * Issue #6549 — `new` without `()` in class constant initializers must compile-error. - * Property defaults may use bare `new` per PHP 8.1+ (#5362). + * Issue #6549 — `new` without `()` in static property initializers must compile-error. + * Class constants and property defaults may use bare `new` per PHP 8.1+/8.3+ (#10198). * * Zend reference: Zend/zend_compile.c, Zend/zend_ast.c */ @@ -45,7 +45,7 @@ function assertCompileSucceeds(string $label, string $code): void } } -assertCompileFails('class const', 'expectException(\CompileError::class); - $this->expectExceptionMessage(NewWithoutParensCompileCheck::MESSAGE); - $runtime->parseAndCompile(<<<'PHP' + $block = $runtime->parseAndCompile(<<<'PHP' assertNotNull($block); } } diff --git a/test/unit/ClassConstObjectJitExecuteTest.php b/test/unit/ClassConstObjectJitExecuteTest.php index ba8e88ec9b7..b729edf4a39 100644 --- a/test/unit/ClassConstObjectJitExecuteTest.php +++ b/test/unit/ClassConstObjectJitExecuteTest.php @@ -4,13 +4,12 @@ namespace PHPCompiler; -use PHPCompiler\Compiler\NewWithoutParensCompileCheck; use PHPUnit\Framework\TestCase; require_once __DIR__.'/../LlvmToolchain.php'; /** - * Class constants with `new` are rejected at compile time (#9974, Zend/zend_compile.c). + * Class constants with `new` execute on VM before JIT lowering (#10198, Zend/zend_constants.c). * * @group llvm */ @@ -28,13 +27,15 @@ protected function setUp(): void } } - public function testClassConstObjectCompileErrorsBeforeJit(): void + public function testClassConstObjectVmExecutesBeforeJit(): void { $code = file_get_contents($this->repoRoot.'/test/compliance/cases/language/class_const_new_object_run.php'); $this->assertNotFalse($code); $runtime = new Runtime(); - $this->expectException(\CompileError::class); - $this->expectExceptionMessage(NewWithoutParensCompileCheck::MESSAGE); - $runtime->parseAndCompile($code, 'class_const_new_object_run.php'); + $block = $runtime->parseAndCompile($code, 'class_const_new_object_run.php'); + ob_start(); + $runtime->run($block); + $output = ob_get_clean(); + $this->assertStringContainsString('1', $output); } } diff --git a/test/unit/CompilerVersionGateTest.php b/test/unit/CompilerVersionGateTest.php index 7b53a4869eb..b4b93a2a3e3 100644 --- a/test/unit/CompilerVersionGateTest.php +++ b/test/unit/CompilerVersionGateTest.php @@ -24,9 +24,9 @@ public function testSupportsTypedTraitConstantsTrueOn83Target(): void $this->assertTrue(CompilerVersion::supportsTypedTraitConstants()); } - public function testSupportsClassConstObjectExpressionsFalse(): void + public function testSupportsClassConstObjectExpressionsTrueOn83Target(): void { - $this->assertFalse(CompilerVersion::supportsClassConstObjectExpressions()); + $this->assertTrue(CompilerVersion::supportsClassConstObjectExpressions()); } public function testSupportsInterfaceTypedConstantsTrueOn83Target(): void diff --git a/test/unit/NewWithoutParensCompileCheckTest.php b/test/unit/NewWithoutParensCompileCheckTest.php index 3e2a2df65db..7892b37f2e4 100644 --- a/test/unit/NewWithoutParensCompileCheckTest.php +++ b/test/unit/NewWithoutParensCompileCheckTest.php @@ -8,17 +8,19 @@ use PHPCompiler\Runtime; use PHPUnit\Framework\TestCase; -/** `new` in class constant + static property initializers (#6549, #9484, #9804, #9850, #10095); instance defaults allowed (#5362). */ +/** `new` in static property initializers rejected; class constants allowed PHP 8.3+ (#10198). */ final class NewWithoutParensCompileCheckTest extends TestCase { - public function testClassConstNewWithoutParensCompileErrors(): void + public function testClassConstNewWithoutParensCompiles(): void { - $this->expectCompileError(<<<'PHP' + $runtime = new Runtime(); + $block = $runtime->parseAndCompile(<<<'PHP' assertNotNull($block); } public function testStaticPropertyDefaultNewWithoutParensCompileErrors(): void @@ -65,9 +67,10 @@ class C { $this->assertNotNull($block); } - public function testClassConstNewWithParensCompileErrors(): void + public function testClassConstNewWithParensCompiles(): void { - $this->expectCompileError(<<<'PHP' + $runtime = new Runtime(); + $block = $runtime->parseAndCompile(<<<'PHP' assertNotNull($block); } - public function testClassConstNewEmptyArgsWithParensCompileErrors(): void + public function testClassConstNewEmptyArgsWithParensCompiles(): void { - $this->expectCompileError(<<<'PHP' + $runtime = new Runtime(); + $block = $runtime->parseAndCompile(<<<'PHP' assertNotNull($block); } - public function testClassConstArrayWithNewCompileErrors(): void + public function testClassConstArrayWithNewCompiles(): void { - $this->expectCompileError(<<<'PHP' + $runtime = new Runtime(); + $block = $runtime->parseAndCompile(<<<'PHP' assertNotNull($block); } private function expectCompileError(string $code): void