Skip to content
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
11 changes: 3 additions & 8 deletions lib/Compiler/NewWithoutParensCompileCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/CompilerVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
6 changes: 3 additions & 3 deletions test/compliance/cases/language/class_const_new_expr.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -9,5 +9,5 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
--EXPECT--
int(1)
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -9,5 +9,7 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
echo Holder::X === Holder::X ? "1\n" : "0\n";
--EXPECT--
int(1)
1
12 changes: 9 additions & 3 deletions test/compliance/cases/language/class_const_new_object.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -20,5 +20,11 @@ class D {
var_export(D::Y);
echo "\n";
echo D::Y->x === 7 ? "1\n" : "0\n";
--EXPECT_EXIT--
255
--EXPECT--
(object) array (
)
1
Foo::__set_state(array (
'x' => 7,
))
1
8 changes: 5 additions & 3 deletions test/compliance/cases/language/class_const_new_rejected.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -9,5 +9,7 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
echo Holder::X === Holder::X ? "same\n" : "diff\n";
--EXPECT--
int(1)
same
7 changes: 4 additions & 3 deletions test/compliance/cases/language/class_const_object.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
public const X = new stdClass();
}
var_export(C::X);
--EXPECT_EXIT--
255
--EXPECT--
(object) array (
)
9 changes: 6 additions & 3 deletions test/compliance/cases/language/class_const_object_jit.phpt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
--TEST--
Language: class constant `new` expression rejected under JIT (#9974, Zend/zend_compile.c)
Language: class constant `new` expression under JIT (#10198, Zend/zend_compile.c)
--FILE--
<?php
class C {
public const X = new stdClass();
}
var_export(C::X);
--EXPECT_EXIT--
255
echo C::X === C::X ? "\n1\n" : "\n0\n";
--EXPECT--
(object) array (
)
1
9 changes: 4 additions & 5 deletions test/compliance/cases/language/const_expr_new_reject.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
--TEST--
Language: bare `new` in class constant must compile-error before const fold (#10106, #6549, Zend/zend_compile.c)
Language: bare `new` in class constant allowed PHP 8.3+ (#10198, Zend/zend_compile.c)
--FILE--
<?php
class C {
public const X = new stdClass;
}
--EXPECT_EXIT--
255
--EXPECTF--
parseAndCompile failure: target=%s: New expressions are not supported in this context
echo C::X instanceof stdClass ? "1\n" : "0\n";
--EXPECT--
1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Language: new in class constant initializer rejected (#9484, #9517, #9974, Zend/zend_compile.c)
Language: new in class constant initializer — PHP 8.3+ (#10198, Zend/zend_compile.c)
--FILE--
<?php
class C {
Expand All @@ -9,5 +9,5 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
--EXPECT--
int(1)
6 changes: 3 additions & 3 deletions test/compliance/cases/language/new_in_constant.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -9,5 +9,5 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
--EXPECT--
int(1)
6 changes: 3 additions & 3 deletions test/compliance/cases/language/new_in_constant_expr.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
class C {
Expand All @@ -9,5 +9,5 @@ class Holder {
public const X = new C(1);
}
var_dump(Holder::X->n);
--EXPECT_EXIT--
255
--EXPECT--
int(1)
6 changes: 3 additions & 3 deletions test/repro-maintainer/parity_new_without_parens.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -45,7 +45,7 @@ function assertCompileSucceeds(string $label, string $code): void
}
}

assertCompileFails('class const', '<?php class C { const X = new stdClass; }');
assertCompileSucceeds('class const', '<?php class C { const X = new stdClass; }');
assertCompileFails('static property', '<?php class C { public static $s = new stdClass; }');
assertCompileSucceeds('instance property', '<?php class C { public $p = new stdClass; }');

Expand Down
10 changes: 4 additions & 6 deletions test/unit/ClassConstObjectJitCompileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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` compile and materialize at define time (#10198, Zend/zend_compile.c).
*
* @group llvm
*/
Expand All @@ -27,16 +26,15 @@ protected function setUp(): void
}
}

public function testClassConstObjectCompileErrors(): void
public function testClassConstObjectCompiles(): void
{
$runtime = new Runtime();
$this->expectException(\CompileError::class);
$this->expectExceptionMessage(NewWithoutParensCompileCheck::MESSAGE);
$runtime->parseAndCompile(<<<'PHP'
$block = $runtime->parseAndCompile(<<<'PHP'
<?php
class C {
public const X = new stdClass();
}
PHP, 'class_const_object.php');
$this->assertNotNull($block);
}
}
13 changes: 7 additions & 6 deletions test/unit/ClassConstObjectJitExecuteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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);
}
}
4 changes: 2 additions & 2 deletions test/unit/CompilerVersionGateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 21 additions & 13 deletions test/unit/NewWithoutParensCompileCheckTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'
<?php
class C {
const X = new stdClass;
}
PHP);
PHP, 'new_without_parens_class_const.php');
$this->assertNotNull($block);
}

public function testStaticPropertyDefaultNewWithoutParensCompileErrors(): void
Expand Down Expand Up @@ -65,40 +67,46 @@ class C {
$this->assertNotNull($block);
}

public function testClassConstNewWithParensCompileErrors(): void
public function testClassConstNewWithParensCompiles(): void
{
$this->expectCompileError(<<<'PHP'
$runtime = new Runtime();
$block = $runtime->parseAndCompile(<<<'PHP'
<?php
class C {
public function __construct(public int $n = 0) {}
}
class Holder {
public const X = new C(1);
}
PHP);
PHP, 'class_const_new_with_parens.php');
$this->assertNotNull($block);
}

public function testClassConstNewEmptyArgsWithParensCompileErrors(): void
public function testClassConstNewEmptyArgsWithParensCompiles(): void
{
$this->expectCompileError(<<<'PHP'
$runtime = new Runtime();
$block = $runtime->parseAndCompile(<<<'PHP'
<?php
class C {
public const X = new stdClass();
}
PHP);
PHP, 'class_const_new_empty_args.php');
$this->assertNotNull($block);
}

public function testClassConstArrayWithNewCompileErrors(): void
public function testClassConstArrayWithNewCompiles(): void
{
$this->expectCompileError(<<<'PHP'
$runtime = new Runtime();
$block = $runtime->parseAndCompile(<<<'PHP'
<?php
class C {
public function __construct(public int $n = 0) {}
}
class Holder {
public const X = [new C()];
}
PHP);
PHP, 'class_const_array_with_new.php');
$this->assertNotNull($block);
}

private function expectCompileError(string $code): void
Expand Down